taler-helper-auditor-aggregation.c (54420B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2016-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero Public License for more details. 12 13 You should have received a copy of the GNU Affero Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file auditor/taler-helper-auditor-aggregation.c 18 * @brief audits an exchange's aggregations. 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include "auditordb_lib.h" 24 /* WIRE_TRANSFER_OUT's callback typedef also lives in exchangedb_lib.h, so its 25 closure override must be established before that header is included. */ 26 struct AggregationContext; 27 #define TALER_EXCHANGEDB_WIRE_TRANSFER_OUT_RESULT_CLOSURE struct \ 28 AggregationContext 29 #include "exchangedb_lib.h" 30 #include "taler/taler_bank_service.h" 31 #include "taler/taler_signatures.h" 32 #include "taler/taler_dbevents.h" 33 #include "report-lib.h" 34 #include "auditor-database/event_listen.h" 35 #include "auditor-database/get_auditor_progress.h" 36 #include "auditor-database/get_balance.h" 37 #include "auditor-database/insert_amount_arithmetic_inconsistency.h" 38 #include "auditor-database/insert_auditor_progress.h" 39 #include "auditor-database/insert_bad_sig_losses.h" 40 #include "auditor-database/insert_balance.h" 41 #include "auditor-database/insert_coin_inconsistency.h" 42 #include "auditor-database/insert_fee_time_inconsistency.h" 43 #include "auditor-database/insert_row_inconsistency.h" 44 #include "auditor-database/insert_wire_out_inconsistency.h" 45 #include "exchange-database/get_coin_transactions.h" 46 #include "exchange-database/get_known_coin.h" 47 #include "exchange-database/get_wire_fee.h" 48 struct WireCheckContext; 49 #define TALER_EXCHANGEDB_AGGREGATION_DATA_RESULT_CLOSURE struct WireCheckContext 50 #include "exchange-database/iterate_wire_transfers.h" 51 #include "exchange-database/iterate_wire_outs_above_serial_id.h" 52 53 /** 54 * Return value from main(). 55 */ 56 static int global_ret; 57 58 /** 59 * Run in test mode. Exit when idle instead of 60 * going to sleep and waiting for more work. 61 */ 62 static int test_mode; 63 64 /** 65 * Checkpointing our progress for aggregations. 66 */ 67 static TALER_ARL_DEF_PP (aggregation_last_wire_out_serial_id); 68 69 /** 70 * Total aggregation fees (wire fees) earned. 71 */ 72 static TALER_ARL_DEF_AB (aggregation_total_wire_fee_revenue); 73 74 /** 75 * Total delta between calculated and stored wire out transfers, 76 * for positive deltas. 77 */ 78 static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_plus); 79 80 /** 81 * Total delta between calculated and stored wire out transfers 82 * for negative deltas. 83 */ 84 static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_minus); 85 86 /** 87 * Profits the exchange made by bad amount calculations on coins. 88 */ 89 static TALER_ARL_DEF_AB (aggregation_total_coin_delta_plus); 90 91 /** 92 * Losses the exchange made by bad amount calculations on coins. 93 */ 94 static TALER_ARL_DEF_AB (aggregation_total_coin_delta_minus); 95 96 /** 97 * Profits the exchange made by bad amount calculations. 98 */ 99 static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_plus); 100 101 /** 102 * Losses the exchange made by bad amount calculations. 103 */ 104 static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_minus); 105 106 /** 107 * Total amount lost by operations for which signatures were invalid. 108 */ 109 static TALER_ARL_DEF_AB (aggregation_total_bad_sig_loss); 110 111 /** 112 * Should we run checks that only work for exchange-internal audits? 113 */ 114 static int internal_checks; 115 116 static struct GNUNET_DB_EventHandler *eh; 117 118 /** 119 * The auditors's configuration. 120 */ 121 static const struct GNUNET_CONFIGURATION_Handle *cfg; 122 123 /** 124 * Report a (serious) inconsistency in the exchange's database with 125 * respect to calculations involving amounts. 126 * 127 * @param operation what operation had the inconsistency 128 * @param rowid affected row, 0 if row is missing 129 * @param exchange amount calculated by exchange 130 * @param auditor amount calculated by auditor 131 * @param profitable 1 if @a exchange being larger than @a auditor is 132 * profitable for the exchange for this operation, 133 * -1 if @a exchange being smaller than @a auditor is 134 * profitable for the exchange, and 0 if it is unclear 135 * @return transaction status 136 */ 137 static enum GNUNET_DB_QueryStatus 138 report_amount_arithmetic_inconsistency ( 139 const char *operation, 140 uint64_t rowid, 141 const struct TALER_Amount *exchange, 142 const struct TALER_Amount *auditor, 143 int profitable) 144 { 145 struct TALER_Amount delta; 146 struct TALER_Amount *target; 147 148 if (0 < TALER_amount_cmp (exchange, 149 auditor)) 150 { 151 /* exchange > auditor */ 152 TALER_ARL_amount_subtract (&delta, 153 exchange, 154 auditor); 155 } 156 else 157 { 158 /* exchange <= auditor */ 159 profitable = -profitable; 160 TALER_ARL_amount_subtract (&delta, 161 auditor, 162 exchange); 163 } 164 165 { 166 struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = { 167 .problem_row_id = rowid, 168 .profitable = profitable, 169 .operation = (char *) operation, 170 .exchange_amount = *exchange, 171 .auditor_amount = *auditor 172 }; 173 enum GNUNET_DB_QueryStatus qs; 174 175 qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency ( 176 TALER_ARL_adb, 177 &aai); 178 179 if (qs < 0) 180 { 181 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 182 return qs; 183 } 184 } 185 if (0 != profitable) 186 { 187 target = (1 == profitable) 188 ? &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_plus) 189 : &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_minus); 190 TALER_ARL_amount_add (target, 191 target, 192 &delta); 193 } 194 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 195 } 196 197 198 /** 199 * Report a (serious) inconsistency in the exchange's database with 200 * respect to calculations involving amounts of a coin. 201 * 202 * @param operation what operation had the inconsistency 203 * @param coin_pub affected coin 204 * @param exchange amount calculated by exchange 205 * @param auditor amount calculated by auditor 206 * @param profitable 1 if @a exchange being larger than @a auditor is 207 * profitable for the exchange for this operation, 208 * -1 if @a exchange being smaller than @a auditor is 209 * profitable for the exchange, and 0 if it is unclear 210 * @return transaction status 211 */ 212 static enum GNUNET_DB_QueryStatus 213 report_coin_arithmetic_inconsistency ( 214 const char *operation, 215 const struct TALER_CoinSpendPublicKeyP *coin_pub, 216 const struct TALER_Amount *exchange, 217 const struct TALER_Amount *auditor, 218 int profitable) 219 { 220 struct TALER_Amount delta; 221 struct TALER_Amount *target; 222 223 if (0 < TALER_amount_cmp (exchange, 224 auditor)) 225 { 226 /* exchange > auditor */ 227 TALER_ARL_amount_subtract (&delta, 228 exchange, 229 auditor); 230 } 231 else 232 { 233 /* exchange <= auditor */ 234 profitable = -profitable; 235 TALER_ARL_amount_subtract (&delta, 236 auditor, 237 exchange); 238 } 239 240 { 241 enum GNUNET_DB_QueryStatus qs; 242 struct TALER_AUDITORDB_CoinInconsistency ci = { 243 .operation = (char *) operation, 244 .auditor_amount = *auditor, 245 .exchange_amount = *exchange, 246 .profitable = profitable, 247 .coin_pub = coin_pub->eddsa_pub 248 }; 249 250 qs = TALER_AUDITORDB_insert_coin_inconsistency ( 251 TALER_ARL_adb, 252 &ci); 253 254 if (qs < 0) 255 { 256 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 257 return qs; 258 } 259 } 260 if (0 != profitable) 261 { 262 target = (1 == profitable) 263 ? &TALER_ARL_USE_AB (aggregation_total_coin_delta_plus) 264 : &TALER_ARL_USE_AB (aggregation_total_coin_delta_minus); 265 TALER_ARL_amount_add (target, 266 target, 267 &delta); 268 } 269 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 270 } 271 272 273 /** 274 * Report a (serious) inconsistency in the exchange's database. 275 * 276 * @param table affected table 277 * @param rowid affected row, 0 if row is missing 278 * @param diagnostic message explaining the problem 279 * @return transaction status 280 */ 281 static enum GNUNET_DB_QueryStatus 282 report_row_inconsistency (const char *table, 283 uint64_t rowid, 284 const char *diagnostic) 285 { 286 enum GNUNET_DB_QueryStatus qs; 287 struct TALER_AUDITORDB_RowInconsistency ri = { 288 .diagnostic = (char *) diagnostic, 289 .row_table = (char *) table, 290 .row_id = rowid 291 }; 292 293 qs = TALER_AUDITORDB_insert_row_inconsistency ( 294 TALER_ARL_adb, 295 &ri); 296 297 if (qs < 0) 298 { 299 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 300 return qs; 301 } 302 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 303 } 304 305 306 /* *********************** Analyze aggregations ******************** */ 307 /* This logic checks that the aggregator did the right thing 308 paying each merchant what they were due (and on time). */ 309 310 311 /** 312 * Information about wire fees charged by the exchange. 313 */ 314 struct WireFeeInfo 315 { 316 317 /** 318 * Kept in a DLL. 319 */ 320 struct WireFeeInfo *next; 321 322 /** 323 * Kept in a DLL. 324 */ 325 struct WireFeeInfo *prev; 326 327 /** 328 * When does the fee go into effect (inclusive). 329 */ 330 struct GNUNET_TIME_Timestamp start_date; 331 332 /** 333 * When does the fee stop being in effect (exclusive). 334 */ 335 struct GNUNET_TIME_Timestamp end_date; 336 337 /** 338 * How high are the wire fees. 339 */ 340 struct TALER_WireFeeSet fees; 341 342 }; 343 344 345 /** 346 * Closure for callbacks during #analyze_merchants(). 347 */ 348 struct AggregationContext 349 { 350 351 /** 352 * DLL of wire fees charged by the exchange. 353 */ 354 struct WireFeeInfo *fee_head; 355 356 /** 357 * DLL of wire fees charged by the exchange. 358 */ 359 struct WireFeeInfo *fee_tail; 360 361 /** 362 * Final result status. 363 */ 364 enum GNUNET_DB_QueryStatus qs; 365 }; 366 367 368 /** 369 * Closure for #wire_transfer_information_cb. 370 */ 371 struct WireCheckContext 372 { 373 374 /** 375 * Corresponding merchant context. 376 */ 377 struct AggregationContext *ac; 378 379 /** 380 * Total deposits claimed by all transactions that were aggregated 381 * under the given @e wtid. 382 */ 383 struct TALER_Amount total_deposits; 384 385 /** 386 * Target account details of the receiver. 387 */ 388 struct TALER_FullPayto payto_uri; 389 390 /** 391 * Execution time of the wire transfer. 392 */ 393 struct GNUNET_TIME_Timestamp date; 394 395 /** 396 * Database transaction status. 397 */ 398 enum GNUNET_DB_QueryStatus qs; 399 400 }; 401 402 403 /** 404 * Check coin's transaction history for plausibility. Does NOT check 405 * the signatures (those are checked independently), but does calculate 406 * the amounts for the aggregation table and checks that the total 407 * claimed coin value is within the value of the coin's denomination. 408 * 409 * @param coin_pub public key of the coin (for reporting) 410 * @param h_contract_terms hash of the proposal for which we calculate the amount 411 * @param merchant_pub public key of the merchant (who is allowed to issue refunds) 412 * @param issue denomination information about the coin 413 * @param tl_head head of transaction history to verify 414 * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant 415 * @param[out] deposit_gain amount the coin contributes excluding refunds 416 * @return database transaction status 417 */ 418 static enum GNUNET_DB_QueryStatus 419 check_transaction_history_for_deposit ( 420 const struct TALER_CoinSpendPublicKeyP *coin_pub, 421 const struct TALER_PrivateContractHashP *h_contract_terms, 422 const struct TALER_MerchantPublicKeyP *merchant_pub, 423 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue, 424 const struct TALER_EXCHANGEDB_TransactionList *tl_head, 425 struct TALER_Amount *merchant_gain, 426 struct TALER_Amount *deposit_gain) 427 { 428 struct TALER_Amount expenditures; 429 struct TALER_Amount refunds; 430 struct TALER_Amount spent; 431 struct TALER_Amount *deposited = NULL; 432 struct TALER_Amount merchant_loss; 433 const struct TALER_Amount *deposit_fee; 434 enum GNUNET_DB_QueryStatus qs; 435 436 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 437 "Checking transaction history of coin %s\n", 438 TALER_B2S (coin_pub)); 439 GNUNET_assert (GNUNET_OK == 440 TALER_amount_set_zero (TALER_ARL_currency, 441 &expenditures)); 442 GNUNET_assert (GNUNET_OK == 443 TALER_amount_set_zero (TALER_ARL_currency, 444 &refunds)); 445 GNUNET_assert (GNUNET_OK == 446 TALER_amount_set_zero (TALER_ARL_currency, 447 merchant_gain)); 448 GNUNET_assert (GNUNET_OK == 449 TALER_amount_set_zero (TALER_ARL_currency, 450 &merchant_loss)); 451 /* Go over transaction history to compute totals; note that we do not bother 452 to reconstruct the order of the events, so instead of subtracting we 453 compute positive (deposit, melt) and negative (refund) values separately 454 here, and then subtract the negative from the positive at the end (after 455 the loops). */ 456 deposit_fee = NULL; 457 for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head; 458 NULL != tl; 459 tl = tl->next) 460 { 461 const struct TALER_Amount *fee_claimed; 462 463 switch (tl->type) 464 { 465 case TALER_EXCHANGEDB_TT_DEPOSIT: 466 /* check wire and h_wire are consistent */ 467 if (NULL != deposited) 468 { 469 struct TALER_AUDITORDB_RowInconsistency ri = { 470 .row_id = tl->serial_id, 471 .diagnostic = (char *) 472 "multiple deposits of the same coin into the same contract detected", 473 .row_table = (char *) "deposits" 474 }; 475 476 qs = TALER_AUDITORDB_insert_row_inconsistency ( 477 TALER_ARL_adb, 478 &ri); 479 480 if (qs < 0) 481 { 482 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 483 return qs; 484 } 485 } 486 deposited = &tl->details.deposit->amount_with_fee; /* according to exchange*/ 487 fee_claimed = &tl->details.deposit->deposit_fee; /* Fee according to exchange DB */ 488 TALER_ARL_amount_add (&expenditures, 489 &expenditures, 490 deposited); 491 /* Check if this deposit is within the remit of the aggregation 492 we are investigating, if so, include it in the totals. */ 493 if ((0 == GNUNET_memcmp (merchant_pub, 494 &tl->details.deposit->merchant_pub)) && 495 (0 == GNUNET_memcmp (h_contract_terms, 496 &tl->details.deposit->h_contract_terms))) 497 { 498 struct TALER_Amount amount_without_fee; 499 500 TALER_ARL_amount_subtract (&amount_without_fee, 501 deposited, 502 fee_claimed); 503 TALER_ARL_amount_add (merchant_gain, 504 merchant_gain, 505 &amount_without_fee); 506 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 507 "Detected applicable deposit of %s\n", 508 TALER_amount2s (&amount_without_fee)); 509 deposit_fee = fee_claimed; /* We had a deposit, remember the fee, we may need it */ 510 } 511 /* Check that the fees given in the transaction list and in dki match */ 512 if (0 != 513 TALER_amount_cmp (&issue->fees.deposit, 514 fee_claimed)) 515 { 516 /* Disagreement in fee structure between auditor and exchange DB! */ 517 qs = report_amount_arithmetic_inconsistency ("deposit fee", 518 0, 519 fee_claimed, 520 &issue->fees.deposit, 521 1); 522 if (0 > qs) 523 return qs; 524 } 525 break; 526 case TALER_EXCHANGEDB_TT_MELT: 527 { 528 const struct TALER_Amount *amount_with_fee; 529 530 amount_with_fee = &tl->details.melt->amount_with_fee; 531 fee_claimed = &tl->details.melt->melt_fee; 532 TALER_ARL_amount_add (&expenditures, 533 &expenditures, 534 amount_with_fee); 535 /* Check that the fees given in the transaction list and in dki match */ 536 if (0 != 537 TALER_amount_cmp (&issue->fees.refresh, 538 fee_claimed)) 539 { 540 /* Disagreement in fee structure between exchange and auditor */ 541 qs = report_amount_arithmetic_inconsistency ("melt fee", 542 0, 543 fee_claimed, 544 &issue->fees.refresh, 545 1); 546 if (0 > qs) 547 return qs; 548 } 549 break; 550 } 551 case TALER_EXCHANGEDB_TT_REFUND: 552 { 553 const struct TALER_Amount *amount_with_fee; 554 555 amount_with_fee = &tl->details.refund->refund_amount; 556 fee_claimed = &tl->details.refund->refund_fee; 557 TALER_ARL_amount_add (&refunds, 558 &refunds, 559 amount_with_fee); 560 TALER_ARL_amount_add (&expenditures, 561 &expenditures, 562 fee_claimed); 563 /* Check if this refund is within the remit of the aggregation 564 we are investigating, if so, include it in the totals. */ 565 if ((0 == GNUNET_memcmp (merchant_pub, 566 &tl->details.refund->merchant_pub)) && 567 (0 == GNUNET_memcmp (h_contract_terms, 568 &tl->details.refund->h_contract_terms))) 569 { 570 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 571 "Detected applicable refund of %s\n", 572 TALER_amount2s (amount_with_fee)); 573 TALER_ARL_amount_add (&merchant_loss, 574 &merchant_loss, 575 amount_with_fee); 576 } 577 /* Check that the fees given in the transaction list and in dki match */ 578 if (0 != 579 TALER_amount_cmp (&issue->fees.refund, 580 fee_claimed)) 581 { 582 /* Disagreement in fee structure between exchange and auditor! */ 583 qs = report_amount_arithmetic_inconsistency ("refund fee", 584 0, 585 fee_claimed, 586 &issue->fees.refund, 587 1); 588 if (0 > qs) 589 return qs; 590 } 591 break; 592 } 593 case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER: 594 { 595 const struct TALER_Amount *amount_with_fee; 596 597 amount_with_fee = &tl->details.old_coin_recoup->value; 598 /* We count recoups of refreshed coins like refunds for the dirty old 599 coin, as they equivalently _increase_ the remaining value on the 600 _old_ coin */ 601 TALER_ARL_amount_add (&refunds, 602 &refunds, 603 amount_with_fee); 604 break; 605 } 606 case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW: 607 { 608 const struct TALER_Amount *amount_with_fee; 609 610 /* We count recoups of the coin as expenditures, as it 611 equivalently decreases the remaining value of the recouped coin. */ 612 amount_with_fee = &tl->details.recoup->value; 613 TALER_ARL_amount_add (&expenditures, 614 &expenditures, 615 amount_with_fee); 616 break; 617 } 618 case TALER_EXCHANGEDB_TT_RECOUP_REFRESH: 619 { 620 const struct TALER_Amount *amount_with_fee; 621 622 /* We count recoups of the coin as expenditures, as it 623 equivalently decreases the remaining value of the recouped coin. */ 624 amount_with_fee = &tl->details.recoup_refresh->value; 625 TALER_ARL_amount_add (&expenditures, 626 &expenditures, 627 amount_with_fee); 628 break; 629 } 630 case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT: 631 { 632 const struct TALER_Amount *amount_with_fee; 633 634 amount_with_fee = &tl->details.purse_deposit->amount; 635 if (! tl->details.purse_deposit->refunded) 636 TALER_ARL_amount_add (&expenditures, 637 &expenditures, 638 amount_with_fee); 639 break; 640 } 641 642 case TALER_EXCHANGEDB_TT_PURSE_REFUND: 643 { 644 const struct TALER_Amount *amount_with_fee; 645 646 amount_with_fee = &tl->details.purse_refund->refund_amount; 647 fee_claimed = &tl->details.purse_refund->refund_fee; 648 TALER_ARL_amount_add (&refunds, 649 &refunds, 650 amount_with_fee); 651 TALER_ARL_amount_add (&expenditures, 652 &expenditures, 653 fee_claimed); 654 /* Check that the fees given in the transaction list and in dki match */ 655 if (0 != 656 TALER_amount_cmp (&issue->fees.refund, 657 fee_claimed)) 658 { 659 /* Disagreement in fee structure between exchange and auditor! */ 660 qs = report_amount_arithmetic_inconsistency ("refund fee", 661 0, 662 fee_claimed, 663 &issue->fees.refund, 664 1); 665 if (0 > qs) 666 return qs; 667 } 668 break; 669 } 670 671 case TALER_EXCHANGEDB_TT_RESERVE_OPEN: 672 { 673 const struct TALER_Amount *amount_with_fee; 674 675 amount_with_fee = &tl->details.reserve_open->coin_contribution; 676 TALER_ARL_amount_add (&expenditures, 677 &expenditures, 678 amount_with_fee); 679 break; 680 } 681 } /* switch (tl->type) */ 682 } /* for 'tl' */ 683 684 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 685 "Deposits for this aggregation (after fees) are %s\n", 686 TALER_amount2s (merchant_gain)); 687 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 688 "Aggregation loss due to refunds is %s\n", 689 TALER_amount2s (&merchant_loss)); 690 *deposit_gain = *merchant_gain; 691 if ((NULL != deposited) && 692 (NULL != deposit_fee) && 693 (0 == TALER_amount_cmp (&refunds, 694 deposited))) 695 { 696 /* We had a /deposit operation AND /refund operations adding up to the 697 total deposited value including deposit fee. Thus, we should not 698 subtract the /deposit fee from the merchant gain (as it was also 699 refunded). */ 700 TALER_ARL_amount_add (merchant_gain, 701 merchant_gain, 702 deposit_fee); 703 } 704 { 705 struct TALER_Amount final_gain; 706 707 if (TALER_ARL_SR_INVALID_NEGATIVE == 708 TALER_ARL_amount_subtract_neg (&final_gain, 709 merchant_gain, 710 &merchant_loss)) 711 { 712 /* refunds above deposits? Bad! */ 713 qs = report_coin_arithmetic_inconsistency ("refund (merchant)", 714 coin_pub, 715 merchant_gain, 716 &merchant_loss, 717 1); 718 if (0 > qs) 719 return qs; 720 /* For the overall aggregation, we should not count this 721 as a NEGATIVE contribution as that is not allowed; so 722 let's count it as zero as that's the best we can do. */ 723 GNUNET_assert (GNUNET_OK == 724 TALER_amount_set_zero (TALER_ARL_currency, 725 merchant_gain)); 726 } 727 else 728 { 729 *merchant_gain = final_gain; 730 } 731 } 732 733 734 /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh) 735 minus refunds (refunds, recoup-to-old) */ 736 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 737 "Subtracting refunds of %s from coin value loss\n", 738 TALER_amount2s (&refunds)); 739 if (TALER_ARL_SR_INVALID_NEGATIVE == 740 TALER_ARL_amount_subtract_neg (&spent, 741 &expenditures, 742 &refunds)) 743 { 744 /* refunds above expenditures? Bad! */ 745 qs = report_coin_arithmetic_inconsistency ("refund (balance)", 746 coin_pub, 747 &expenditures, 748 &refunds, 749 1); 750 if (0 > qs) 751 return qs; 752 } 753 else 754 { 755 /* Now check that 'spent' is less or equal than the total coin value */ 756 if (1 == TALER_amount_cmp (&spent, 757 &issue->value)) 758 { 759 /* spent > value */ 760 qs = report_coin_arithmetic_inconsistency ("spend", 761 coin_pub, 762 &spent, 763 &issue->value, 764 -1); 765 if (0 > qs) 766 return qs; 767 } 768 } 769 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 770 "Final merchant gain after refunds is %s\n", 771 TALER_amount2s (deposit_gain)); 772 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 773 "Coin %s contributes %s to contract %s\n", 774 TALER_B2S (coin_pub), 775 TALER_amount2s (merchant_gain), 776 GNUNET_h2s (&h_contract_terms->hash)); 777 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 778 } 779 780 781 /** 782 * Function called with the results of the lookup of the 783 * transaction data associated with a wire transfer identifier. 784 * 785 * @param[in,out] wcc a `struct WireCheckContext` 786 * @param rowid which row in the table is the information from (for diagnostics) 787 * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) 788 * @param account_pay_uri where did we transfer the funds? 789 * @param h_payto hash over @a account_payto_uri as it is in the DB 790 * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) 791 * @param h_contract_terms which proposal was this payment about 792 * @param denom_pub denomination of @a coin_pub 793 * @param coin_pub which public key was this payment about 794 * @param coin_value amount contributed by this coin in total (with fee), 795 * but excluding refunds by this coin 796 * @param deposit_fee applicable deposit fee for this coin, actual 797 * fees charged may differ if coin was refunded 798 */ 799 static void 800 wire_transfer_information_cb ( 801 struct WireCheckContext *wcc, 802 uint64_t rowid, 803 const struct TALER_MerchantPublicKeyP *merchant_pub, 804 const struct TALER_FullPayto account_pay_uri, 805 const struct TALER_FullPaytoHashP *h_payto, 806 struct GNUNET_TIME_Timestamp exec_time, 807 const struct TALER_PrivateContractHashP *h_contract_terms, 808 const struct TALER_DenominationPublicKey *denom_pub, 809 const struct TALER_CoinSpendPublicKeyP *coin_pub, 810 const struct TALER_Amount *coin_value, 811 const struct TALER_Amount *deposit_fee) 812 { 813 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue; 814 struct TALER_Amount computed_value; 815 struct TALER_Amount total_deposit_without_refunds; 816 struct TALER_EXCHANGEDB_TransactionList *tl; 817 struct TALER_CoinPublicInfo coin; 818 enum GNUNET_DB_QueryStatus qs; 819 struct TALER_FullPaytoHashP hpt; 820 uint64_t etag_out; 821 822 if (0 > wcc->qs) 823 return; 824 TALER_full_payto_hash (account_pay_uri, 825 &hpt); 826 if (0 != 827 GNUNET_memcmp (&hpt, 828 h_payto)) 829 { 830 qs = report_row_inconsistency ("wire_targets", 831 rowid, 832 "h-payto does not match payto URI"); 833 if (0 > qs) 834 { 835 wcc->qs = qs; 836 return; 837 } 838 } 839 /* Obtain coin's transaction history */ 840 /* FIXME-Optimization: could use 'start' mechanism to only fetch 841 transactions we did not yet process, instead of going over them again and 842 again.*/ 843 844 { 845 struct TALER_Amount balance; 846 struct TALER_DenominationHashP h_denom_pub; 847 848 qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb, 849 false, 850 coin_pub, 851 0, 852 0, 853 &etag_out, 854 &balance, 855 &h_denom_pub, 856 &tl); 857 } 858 if (0 > qs) 859 { 860 wcc->qs = qs; 861 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 862 return; 863 } 864 if (NULL == tl) 865 { 866 qs = report_row_inconsistency ("aggregation", 867 rowid, 868 "no transaction history for coin claimed in aggregation"); 869 if (0 > qs) 870 wcc->qs = qs; 871 return; 872 } 873 qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb, 874 coin_pub, 875 &coin); 876 if (0 > qs) 877 { 878 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 879 wcc->qs = qs; 880 return; 881 } 882 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 883 { 884 /* this should be a foreign key violation at this point! */ 885 qs = report_row_inconsistency ("aggregation", 886 rowid, 887 "could not get coin details for coin claimed in aggregation"); 888 if (0 > qs) 889 wcc->qs = qs; 890 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 891 return; 892 } 893 qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash, 894 &issue); 895 if (0 > qs) 896 { 897 wcc->qs = qs; 898 TALER_denom_sig_free (&coin.denom_sig); 899 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 900 return; 901 } 902 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 903 { 904 TALER_denom_sig_free (&coin.denom_sig); 905 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 906 qs = report_row_inconsistency ("aggregation", 907 rowid, 908 "could not find denomination key for coin claimed in aggregation"); 909 if (0 > qs) 910 wcc->qs = qs; 911 return; 912 } 913 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 914 "Testing coin `%s' for validity\n", 915 TALER_B2S (&coin.coin_pub)); 916 if (GNUNET_OK != 917 TALER_test_coin_valid (&coin, 918 denom_pub)) 919 { 920 struct TALER_AUDITORDB_BadSigLosses bsl = { 921 .problem_row_id = rowid, 922 .operation = (char *) "wire", 923 .loss = *coin_value, 924 .operation_specific_pub = coin.coin_pub.eddsa_pub 925 }; 926 927 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 928 TALER_ARL_adb, 929 &bsl); 930 if (qs < 0) 931 { 932 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 933 wcc->qs = qs; 934 TALER_denom_sig_free (&coin.denom_sig); 935 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 936 return; 937 } 938 TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss), 939 &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss), 940 coin_value); 941 qs = report_row_inconsistency ("deposit", 942 rowid, 943 "coin denomination signature invalid"); 944 if (0 > qs) 945 { 946 wcc->qs = qs; 947 TALER_denom_sig_free (&coin.denom_sig); 948 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 949 return; 950 } 951 } 952 TALER_denom_sig_free (&coin.denom_sig); 953 GNUNET_assert (NULL != issue); /* mostly to help static analysis */ 954 /* Check transaction history to see if it supports aggregate 955 valuation */ 956 qs = check_transaction_history_for_deposit ( 957 coin_pub, 958 h_contract_terms, 959 merchant_pub, 960 issue, 961 tl, 962 &computed_value, 963 &total_deposit_without_refunds); 964 if (0 > qs) 965 { 966 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 967 wcc->qs = qs; 968 return; 969 } 970 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 971 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 972 "Coin contributes %s to aggregate (deposits after fees and refunds)\n", 973 TALER_amount2s (&computed_value)); 974 { 975 struct TALER_Amount coin_value_without_fee; 976 977 if (TALER_ARL_SR_INVALID_NEGATIVE == 978 TALER_ARL_amount_subtract_neg (&coin_value_without_fee, 979 coin_value, 980 deposit_fee)) 981 { 982 qs = report_amount_arithmetic_inconsistency ( 983 "aggregation (fee structure)", 984 rowid, 985 coin_value, 986 deposit_fee, 987 -1); 988 if (0 > qs) 989 { 990 wcc->qs = qs; 991 return; 992 } 993 } 994 else if (0 != 995 TALER_amount_cmp (&total_deposit_without_refunds, 996 &coin_value_without_fee)) 997 { 998 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 999 "Expected coin contribution of %s to aggregate\n", 1000 TALER_amount2s (&coin_value_without_fee)); 1001 qs = report_amount_arithmetic_inconsistency ( 1002 "aggregation (contribution)", 1003 rowid, 1004 &coin_value_without_fee, 1005 &total_deposit_without_refunds, 1006 -1); 1007 if (0 > qs) 1008 { 1009 wcc->qs = qs; 1010 return; 1011 } 1012 } 1013 } 1014 /* Check other details of wire transfer match */ 1015 if (0 != TALER_full_payto_cmp (account_pay_uri, 1016 wcc->payto_uri)) 1017 { 1018 qs = report_row_inconsistency ("aggregation", 1019 rowid, 1020 "target of outgoing wire transfer do not match hash of wire from deposit"); 1021 if (0 > qs) 1022 { 1023 wcc->qs = qs; 1024 return; 1025 } 1026 } 1027 if (GNUNET_TIME_timestamp_cmp (exec_time, 1028 !=, 1029 wcc->date)) 1030 { 1031 /* This should be impossible from database constraints */ 1032 GNUNET_break (0); 1033 qs = report_row_inconsistency ("aggregation", 1034 rowid, 1035 "date given in aggregate does not match wire transfer date"); 1036 if (0 > qs) 1037 { 1038 wcc->qs = qs; 1039 return; 1040 } 1041 } 1042 1043 /* Add coin's contribution to total aggregate value */ 1044 { 1045 struct TALER_Amount res; 1046 1047 TALER_ARL_amount_add (&res, 1048 &wcc->total_deposits, 1049 &computed_value); 1050 wcc->total_deposits = res; 1051 } 1052 } 1053 1054 1055 /** 1056 * Lookup the wire fee that the exchange charges at @a timestamp. 1057 * 1058 * @param ac context for caching the result 1059 * @param method method of the wire plugin 1060 * @param timestamp time for which we need the fee 1061 * @return NULL on error (fee unknown) 1062 */ 1063 static const struct TALER_Amount * 1064 get_wire_fee (struct AggregationContext *ac, 1065 const char *method, 1066 struct GNUNET_TIME_Timestamp timestamp) 1067 { 1068 struct WireFeeInfo *wfi; 1069 struct WireFeeInfo *pos; 1070 struct TALER_MasterSignatureP master_sig; 1071 enum GNUNET_DB_QueryStatus qs; 1072 uint64_t rowid; 1073 1074 /* Check if fee is already loaded in cache */ 1075 for (pos = ac->fee_head; NULL != pos; pos = pos->next) 1076 { 1077 if (GNUNET_TIME_timestamp_cmp (pos->start_date, 1078 <=, 1079 timestamp) && 1080 GNUNET_TIME_timestamp_cmp (pos->end_date, 1081 >, 1082 timestamp)) 1083 return &pos->fees.wire; 1084 if (GNUNET_TIME_timestamp_cmp (pos->start_date, 1085 >, 1086 timestamp)) 1087 break; 1088 } 1089 1090 /* Lookup fee in exchange database */ 1091 wfi = GNUNET_new (struct WireFeeInfo); 1092 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1093 TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb, 1094 method, 1095 timestamp, 1096 &rowid, 1097 &wfi->start_date, 1098 &wfi->end_date, 1099 &wfi->fees, 1100 &master_sig)) 1101 { 1102 GNUNET_break (0); 1103 GNUNET_free (wfi); 1104 return NULL; 1105 } 1106 1107 /* Check signature. (This is not terribly meaningful as the exchange can 1108 easily make this one up, but it means that we have proof that the master 1109 key was used for inconsistent wire fees if a merchant complains.) */ 1110 if (GNUNET_OK != 1111 TALER_exchange_offline_wire_fee_verify ( 1112 method, 1113 wfi->start_date, 1114 wfi->end_date, 1115 &wfi->fees, 1116 &TALER_ARL_master_pub, 1117 &master_sig)) 1118 { 1119 ac->qs = report_row_inconsistency ("wire-fee", 1120 timestamp.abs_time.abs_value_us, 1121 "wire fee signature invalid at given time"); 1122 /* Note: continue with the fee despite the signature 1123 being invalid here; hopefully it is really only the 1124 signature that is bad ... */ 1125 } 1126 1127 /* Established fee, keep in sorted list */ 1128 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1129 "Wire fee is %s starting at %s\n", 1130 TALER_amount2s (&wfi->fees.wire), 1131 GNUNET_TIME_timestamp2s (wfi->start_date)); 1132 if ((NULL == pos) || 1133 (NULL == pos->prev)) 1134 GNUNET_CONTAINER_DLL_insert (ac->fee_head, 1135 ac->fee_tail, 1136 wfi); 1137 else 1138 GNUNET_CONTAINER_DLL_insert_after (ac->fee_head, 1139 ac->fee_tail, 1140 pos->prev, 1141 wfi); 1142 /* Check non-overlaping fee invariant */ 1143 if ((NULL != wfi->prev) && 1144 GNUNET_TIME_timestamp_cmp (wfi->prev->end_date, 1145 >, 1146 wfi->start_date)) 1147 { 1148 struct TALER_AUDITORDB_FeeTimeInconsistency ftib = { 1149 .problem_row_id = rowid, 1150 .diagnostic = (char *) "start date before previous end date", 1151 .time = wfi->start_date.abs_time, 1152 .type = (char *) method 1153 }; 1154 1155 qs = TALER_AUDITORDB_insert_fee_time_inconsistency ( 1156 TALER_ARL_adb, 1157 &ftib); 1158 if (qs < 0) 1159 { 1160 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1161 ac->qs = qs; 1162 return NULL; 1163 } 1164 } 1165 if ((NULL != wfi->next) && 1166 GNUNET_TIME_timestamp_cmp (wfi->next->start_date, 1167 >=, 1168 wfi->end_date)) 1169 { 1170 struct TALER_AUDITORDB_FeeTimeInconsistency ftia = { 1171 .problem_row_id = rowid, 1172 .diagnostic = (char *) "end date date after next start date", 1173 .time = wfi->end_date.abs_time, 1174 .type = (char *) method 1175 }; 1176 1177 qs = TALER_AUDITORDB_insert_fee_time_inconsistency ( 1178 TALER_ARL_adb, 1179 &ftia); 1180 1181 if (qs < 0) 1182 { 1183 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1184 ac->qs = qs; 1185 return NULL; 1186 } 1187 } 1188 return &wfi->fees.wire; 1189 } 1190 1191 1192 /** 1193 * Check that a wire transfer made by the exchange is valid 1194 * (has matching deposits). 1195 * 1196 * @param ac a `struct AggregationContext` 1197 * @param rowid identifier of the respective row in the database 1198 * @param date timestamp of the wire transfer (roughly) 1199 * @param wtid wire transfer subject 1200 * @param payto_uri bank account details of the receiver 1201 * @param amount amount that was wired 1202 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration 1203 */ 1204 static enum GNUNET_GenericReturnValue 1205 check_wire_out_cb (struct AggregationContext *ac, 1206 uint64_t rowid, 1207 struct GNUNET_TIME_Timestamp date, 1208 const struct TALER_WireTransferIdentifierRawP *wtid, 1209 const struct TALER_FullPayto payto_uri, 1210 const struct TALER_Amount *amount) 1211 { 1212 struct WireCheckContext wcc; 1213 struct TALER_Amount final_amount; 1214 struct TALER_Amount exchange_gain; 1215 enum GNUNET_DB_QueryStatus qs; 1216 char *method; 1217 1218 /* should be monotonically increasing */ 1219 GNUNET_assert (rowid >= 1220 TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id)); 1221 TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1; 1222 1223 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1224 "Checking wire transfer %s over %s performed on %s\n", 1225 TALER_B2S (wtid), 1226 TALER_amount2s (amount), 1227 GNUNET_TIME_timestamp2s (date)); 1228 if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto))) 1229 { 1230 qs = report_row_inconsistency ("wire_out", 1231 rowid, 1232 "specified wire address lacks method"); 1233 if (0 > qs) 1234 ac->qs = qs; 1235 return GNUNET_OK; 1236 } 1237 1238 wcc.ac = ac; 1239 wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1240 wcc.date = date; 1241 GNUNET_assert (GNUNET_OK == 1242 TALER_amount_set_zero (amount->currency, 1243 &wcc.total_deposits)); 1244 wcc.payto_uri = payto_uri; 1245 qs = TALER_EXCHANGEDB_iterate_wire_transfers (TALER_ARL_edb, 1246 wtid, 1247 &wire_transfer_information_cb, 1248 &wcc); 1249 if (0 > qs) 1250 { 1251 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1252 ac->qs = qs; 1253 GNUNET_free (method); 1254 return GNUNET_SYSERR; 1255 } 1256 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs) 1257 { 1258 /* Note: detailed information was already logged 1259 in #wire_transfer_information_cb, so here we 1260 only log for debugging */ 1261 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1262 "Inconsistency for wire_out %llu (WTID %s) detected\n", 1263 (unsigned long long) rowid, 1264 TALER_B2S (wtid)); 1265 } 1266 1267 1268 /* Subtract aggregation fee from total (if possible) */ 1269 { 1270 const struct TALER_Amount *wire_fee; 1271 1272 wire_fee = get_wire_fee (ac, 1273 method, 1274 date); 1275 if (0 > ac->qs) 1276 { 1277 GNUNET_free (method); 1278 return GNUNET_SYSERR; 1279 } 1280 if (NULL == wire_fee) 1281 { 1282 qs = report_row_inconsistency ("wire-fee", 1283 date.abs_time.abs_value_us, 1284 "wire fee unavailable for given time"); 1285 if (qs < 0) 1286 { 1287 ac->qs = qs; 1288 GNUNET_free (method); 1289 return GNUNET_SYSERR; 1290 } 1291 /* If fee is unknown, we just assume the fee is zero */ 1292 final_amount = wcc.total_deposits; 1293 } 1294 else if (TALER_ARL_SR_INVALID_NEGATIVE == 1295 TALER_ARL_amount_subtract_neg (&final_amount, 1296 &wcc.total_deposits, 1297 wire_fee)) 1298 { 1299 qs = report_amount_arithmetic_inconsistency ( 1300 "wire out (fee structure)", 1301 rowid, 1302 &wcc.total_deposits, 1303 wire_fee, 1304 -1); 1305 /* If fee arithmetic fails, we just assume the fee is zero */ 1306 if (0 > qs) 1307 { 1308 ac->qs = qs; 1309 GNUNET_free (method); 1310 return GNUNET_SYSERR; 1311 } 1312 final_amount = wcc.total_deposits; 1313 } 1314 } 1315 GNUNET_free (method); 1316 1317 /* Round down to amount supported by wire method */ 1318 GNUNET_break (GNUNET_SYSERR != 1319 TALER_amount_round_down (&final_amount, 1320 &TALER_ARL_currency_round_unit)); 1321 1322 /* Calculate the exchange's gain as the fees plus rounding differences! */ 1323 TALER_ARL_amount_subtract (&exchange_gain, 1324 &wcc.total_deposits, 1325 &final_amount); 1326 1327 /* Sum up aggregation fees (we simply include the rounding gains) */ 1328 TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue), 1329 &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue), 1330 &exchange_gain); 1331 1332 /* Check that calculated amount matches actual amount */ 1333 if (0 != TALER_amount_cmp (amount, 1334 &final_amount)) 1335 { 1336 struct TALER_Amount delta; 1337 1338 if (0 < TALER_amount_cmp (amount, 1339 &final_amount)) 1340 { 1341 /* amount > final_amount */ 1342 TALER_ARL_amount_subtract (&delta, 1343 amount, 1344 &final_amount); 1345 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1346 aggregation_total_wire_out_delta_plus), 1347 &TALER_ARL_USE_AB ( 1348 aggregation_total_wire_out_delta_plus), 1349 &delta); 1350 } 1351 else 1352 { 1353 /* amount < final_amount */ 1354 TALER_ARL_amount_subtract (&delta, 1355 &final_amount, 1356 amount); 1357 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1358 aggregation_total_wire_out_delta_minus), 1359 &TALER_ARL_USE_AB ( 1360 aggregation_total_wire_out_delta_minus), 1361 &delta); 1362 } 1363 1364 { 1365 struct TALER_AUDITORDB_WireOutInconsistency woi = { 1366 .destination_account = payto_uri, 1367 .diagnostic = (char *) "aggregated amount does not match expectations", 1368 .wire_out_row_id = rowid, 1369 .expected = final_amount, 1370 .claimed = *amount 1371 }; 1372 1373 qs = TALER_AUDITORDB_insert_wire_out_inconsistency ( 1374 TALER_ARL_adb, 1375 &woi); 1376 1377 if (qs < 0) 1378 { 1379 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1380 ac->qs = qs; 1381 return GNUNET_SYSERR; 1382 } 1383 } 1384 return GNUNET_OK; 1385 } 1386 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1387 "Aggregation unit %s is OK\n", 1388 TALER_B2S (wtid)); 1389 return GNUNET_OK; 1390 } 1391 1392 1393 /** 1394 * Analyze the exchange aggregator's payment processing. 1395 * 1396 * @param cls closure 1397 * @return transaction status code 1398 */ 1399 static enum GNUNET_DB_QueryStatus 1400 analyze_aggregations (void *cls) 1401 { 1402 struct AggregationContext ac; 1403 struct WireFeeInfo *wfi; 1404 enum GNUNET_DB_QueryStatus qs; 1405 1406 (void) cls; 1407 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1408 "Analyzing aggregations\n"); 1409 qs = TALER_AUDITORDB_get_auditor_progress ( 1410 TALER_ARL_adb, 1411 TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id), 1412 NULL); 1413 if (0 > qs) 1414 { 1415 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1416 return qs; 1417 } 1418 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1419 { 1420 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1421 "First analysis using this auditor, starting audit from scratch\n"); 1422 } 1423 else 1424 { 1425 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1426 "Resuming aggregation audit at %llu\n", 1427 (unsigned long long) TALER_ARL_USE_PP ( 1428 aggregation_last_wire_out_serial_id)); 1429 } 1430 1431 memset (&ac, 1432 0, 1433 sizeof (ac)); 1434 qs = TALER_AUDITORDB_get_balance ( 1435 TALER_ARL_adb, 1436 TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue), 1437 TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus), 1438 TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus), 1439 TALER_ARL_GET_AB (aggregation_total_bad_sig_loss), 1440 TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus), 1441 TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus), 1442 TALER_ARL_GET_AB (aggregation_total_coin_delta_plus), 1443 TALER_ARL_GET_AB (aggregation_total_coin_delta_minus), 1444 NULL); 1445 if (0 > qs) 1446 { 1447 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1448 return qs; 1449 } 1450 1451 ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1452 qs = TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id ( 1453 TALER_ARL_edb, 1454 TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id), 1455 &check_wire_out_cb, 1456 &ac); 1457 if (0 > qs) 1458 { 1459 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1460 ac.qs = qs; 1461 } 1462 while (NULL != (wfi = ac.fee_head)) 1463 { 1464 GNUNET_CONTAINER_DLL_remove (ac.fee_head, 1465 ac.fee_tail, 1466 wfi); 1467 GNUNET_free (wfi); 1468 } 1469 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1470 { 1471 /* there were no wire out entries to be looked at, we are done */ 1472 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1473 "No wire out entries found\n"); 1474 return qs; 1475 } 1476 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs) 1477 { 1478 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs); 1479 return ac.qs; 1480 } 1481 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1482 "Finished aggregation audit at %llu\n", 1483 (unsigned long long) TALER_ARL_USE_PP ( 1484 aggregation_last_wire_out_serial_id)); 1485 qs = TALER_AUDITORDB_insert_balance ( 1486 TALER_ARL_adb, 1487 TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue), 1488 TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus), 1489 TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus), 1490 TALER_ARL_SET_AB (aggregation_total_bad_sig_loss), 1491 TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus), 1492 TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus), 1493 TALER_ARL_SET_AB (aggregation_total_coin_delta_plus), 1494 TALER_ARL_SET_AB (aggregation_total_coin_delta_minus), 1495 NULL); 1496 if (0 > qs) 1497 { 1498 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1499 "Failed to update auditor DB, not recording progress\n"); 1500 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1501 return qs; 1502 } 1503 1504 qs = TALER_AUDITORDB_insert_auditor_progress ( 1505 TALER_ARL_adb, 1506 TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id), 1507 NULL); 1508 if (0 > qs) 1509 { 1510 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1511 "Failed to update auditor DB, not recording progress\n"); 1512 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1513 return qs; 1514 } 1515 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1516 "Concluded aggregation audit step at %llu\n", 1517 (unsigned long long) TALER_ARL_USE_PP ( 1518 aggregation_last_wire_out_serial_id)); 1519 1520 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1521 } 1522 1523 1524 /** 1525 * Function called on events received from Postgres. 1526 * 1527 * @param cls closure, NULL 1528 * @param extra additional event data provided 1529 * @param extra_size number of bytes in @a extra 1530 */ 1531 static void 1532 db_notify (void *cls, 1533 const void *extra, 1534 size_t extra_size) 1535 { 1536 (void) cls; 1537 (void) extra; 1538 (void) extra_size; 1539 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1540 "Received notification to wake aggregation helper\n"); 1541 if (GNUNET_OK != 1542 TALER_ARL_setup_sessions_and_run (&analyze_aggregations, 1543 NULL)) 1544 { 1545 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1546 "Audit failed\n"); 1547 GNUNET_SCHEDULER_shutdown (); 1548 global_ret = EXIT_FAILURE; 1549 return; 1550 } 1551 } 1552 1553 1554 /** 1555 * Function called on shutdown. 1556 */ 1557 static void 1558 do_shutdown (void *cls) 1559 { 1560 (void) cls; 1561 if (NULL != eh) 1562 { 1563 TALER_AUDITORDB_event_listen_cancel (eh); 1564 eh = NULL; 1565 } 1566 TALER_ARL_done (); 1567 } 1568 1569 1570 /** 1571 * Main function that will be run. 1572 * 1573 * @param cls closure 1574 * @param args remaining command-line arguments 1575 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1576 * @param c configuration 1577 */ 1578 static void 1579 run (void *cls, 1580 char *const *args, 1581 const char *cfgfile, 1582 const struct GNUNET_CONFIGURATION_Handle *c) 1583 { 1584 (void) cls; 1585 (void) args; 1586 (void) cfgfile; 1587 1588 cfg = c; 1589 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1590 NULL); 1591 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1592 "Launching aggregation auditor\n"); 1593 if (GNUNET_OK != 1594 TALER_ARL_init (c)) 1595 { 1596 global_ret = EXIT_FAILURE; 1597 return; 1598 } 1599 1600 if (test_mode != 1) 1601 { 1602 struct GNUNET_DB_EventHeaderP es = { 1603 .size = htons (sizeof (es)), 1604 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION) 1605 }; 1606 1607 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1608 "Running helper indefinitely\n"); 1609 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 1610 &es, 1611 GNUNET_TIME_UNIT_FOREVER_REL, 1612 &db_notify, 1613 NULL); 1614 } 1615 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1616 "Starting audit\n"); 1617 if (GNUNET_OK != 1618 TALER_ARL_setup_sessions_and_run (&analyze_aggregations, 1619 NULL)) 1620 { 1621 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1622 "Audit failed\n"); 1623 GNUNET_SCHEDULER_shutdown (); 1624 global_ret = EXIT_FAILURE; 1625 return; 1626 } 1627 } 1628 1629 1630 /** 1631 * The main function to audit the exchange's aggregation processing. 1632 * 1633 * @param argc number of arguments from the command line 1634 * @param argv command line arguments 1635 * @return 0 ok, 1 on error 1636 */ 1637 int 1638 main (int argc, 1639 char *const *argv) 1640 { 1641 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1642 GNUNET_GETOPT_option_flag ('i', 1643 "internal", 1644 "perform checks only applicable for exchange-internal audits", 1645 &internal_checks), 1646 GNUNET_GETOPT_option_flag ('t', 1647 "test", 1648 "run in test mode and exit when idle", 1649 &test_mode), 1650 GNUNET_GETOPT_option_timetravel ('T', 1651 "timetravel"), 1652 GNUNET_GETOPT_OPTION_END 1653 }; 1654 enum GNUNET_GenericReturnValue ret; 1655 1656 ret = GNUNET_PROGRAM_run ( 1657 TALER_AUDITOR_project_data (), 1658 argc, 1659 argv, 1660 "taler-helper-auditor-aggregation", 1661 gettext_noop ("Audit Taler exchange aggregation activity"), 1662 options, 1663 &run, 1664 NULL); 1665 if (GNUNET_SYSERR == ret) 1666 return EXIT_INVALIDARGUMENT; 1667 if (GNUNET_NO == ret) 1668 return EXIT_SUCCESS; 1669 return global_ret; 1670 } 1671 1672 1673 /* end of taler-helper-auditor-aggregation.c */