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