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