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