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