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