taler-helper-auditor-wire-credit.c (43208B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017-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 General 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 General Public License for more details. 12 13 You should have received a copy of the GNU General 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-wire-credit.c 18 * @brief audits that wire transfers match those from an exchange database. 19 * @author Christian Grothoff 20 * 21 * This auditor verifies that 'reserves_in' actually matches 22 * the incoming wire transfers from the bank. 23 */ 24 #include "platform.h" 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "auditordb_lib.h" 28 #include "exchangedb_lib.h" 29 #include "taler/taler_json_lib.h" 30 #include "taler/taler_bank_service.h" 31 #include "taler/taler_signatures.h" 32 #include "report-lib.h" 33 #include "taler/taler_dbevents.h" 34 #include \ 35 "exchange-database/select_reserves_in_above_serial_id_by_account.h" 36 #include "auditor-database/delete_reserve_in_inconsistency.h" 37 #include "auditor-database/event_listen.h" 38 #include "auditor-database/get_auditor_progress.h" 39 #include "auditor-database/get_balance.h" 40 #include "auditor-database/insert_auditor_progress.h" 41 #include "auditor-database/insert_balance.h" 42 #include "auditor-database/insert_misattribution_in_inconsistency.h" 43 #include "auditor-database/insert_reserve_in_inconsistency.h" 44 #include "auditor-database/insert_row_inconsistency.h" 45 #include "auditor-database/insert_row_minor_inconsistencies.h" 46 #include "auditor-database/preflight.h" 47 #include "auditor-database/select_reserve_in_inconsistency.h" 48 #include "auditor-database/start.h" 49 #include "auditor-database/update_auditor_progress.h" 50 #include "auditor-database/update_balance.h" 51 #include "exchange-database/preflight.h" 52 #include "exchange-database/rollback.h" 53 #include \ 54 "exchange-database/select_reserves_in_above_serial_id_by_account.h" 55 #include "exchange-database/start_read_only.h" 56 57 /** 58 * How much time do we allow the aggregator to lag behind? If 59 * wire transfers should have been made more than #GRACE_PERIOD 60 * before, we issue warnings. 61 */ 62 #define GRACE_PERIOD GNUNET_TIME_UNIT_HOURS 63 64 /** 65 * Maximum number of wire transfers we process per 66 * (database) transaction. 67 */ 68 #define MAX_PER_TRANSACTION 1024 69 70 /** 71 * How much do we allow the bank and the exchange to disagree about 72 * timestamps? Should be sufficiently large to avoid bogus reports from deltas 73 * created by imperfect clock synchronization and network delay. 74 */ 75 #define TIME_TOLERANCE GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, \ 76 15) 77 78 79 /** 80 * Run in test mode. Exit when idle instead of 81 * going to sleep and waiting for more work. 82 */ 83 static int test_mode; 84 85 /** 86 * Information we keep for each supported account. 87 */ 88 struct WireAccount 89 { 90 /** 91 * Accounts are kept in a DLL. 92 */ 93 struct WireAccount *next; 94 95 /** 96 * Plugins are kept in a DLL. 97 */ 98 struct WireAccount *prev; 99 100 /** 101 * Account details. 102 */ 103 const struct TALER_EXCHANGEDB_AccountInfo *ai; 104 105 /** 106 * Active wire request for the transaction history. 107 */ 108 struct TALER_BANK_CreditHistoryHandle *chh; 109 110 /** 111 * Progress point for this account. 112 */ 113 uint64_t last_reserve_in_serial_id; 114 115 /** 116 * Initial progress point for this account. 117 */ 118 uint64_t start_reserve_in_serial_id; 119 120 /** 121 * Where we are in the inbound transaction history. 122 */ 123 uint64_t wire_off_in; 124 125 /** 126 * Label under which we store our pp's reserve_in_serial_id. 127 */ 128 char *label_reserve_in_serial_id; 129 130 /** 131 * Label under which we store our wire_off_in. 132 */ 133 char *label_wire_off_in; 134 135 }; 136 137 138 /** 139 * Return value from main(). 140 */ 141 static int global_ret; 142 143 /** 144 * State of the current database transaction with 145 * the auditor DB. 146 */ 147 static enum GNUNET_DB_QueryStatus global_qs; 148 149 /** 150 * Map with information about incoming wire transfers. 151 * Maps hashes of the wire offsets to `struct ReserveInInfo`s. 152 */ 153 static struct GNUNET_CONTAINER_MultiHashMap *in_map; 154 155 /** 156 * Head of list of wire accounts we still need to look at. 157 */ 158 static struct WireAccount *wa_head; 159 160 /** 161 * Tail of list of wire accounts we still need to look at. 162 */ 163 static struct WireAccount *wa_tail; 164 165 /** 166 * Amount that is considered "tiny" 167 */ 168 static struct TALER_Amount tiny_amount; 169 170 /** 171 * Total amount that was transferred too much to the exchange. 172 */ 173 static TALER_ARL_DEF_AB (total_bad_amount_in_plus); 174 175 /** 176 * Total amount that was transferred too little to the exchange. 177 */ 178 static TALER_ARL_DEF_AB (total_bad_amount_in_minus); 179 180 /** 181 * Total amount where the exchange has the wrong sender account 182 * for incoming funds and may thus wire funds to the wrong 183 * destination when closing the reserve. 184 */ 185 static TALER_ARL_DEF_AB (total_misattribution_in); 186 187 /** 188 * Total amount credited to exchange accounts. 189 */ 190 static TALER_ARL_DEF_AB (total_wire_in); 191 192 /** 193 * Total amount credited to exchange accounts via KYCAUTH 194 */ 195 static TALER_ARL_DEF_AB (total_kycauth_in); 196 197 /** 198 * Total wire credit fees charged to the exchange account. 199 */ 200 static TALER_ARL_DEF_AB (total_wire_credit_fees); 201 202 /** 203 * Amount of zero in our currency. 204 */ 205 static struct TALER_Amount zero; 206 207 /** 208 * Handle to the context for interacting with the bank. 209 */ 210 static struct GNUNET_CURL_Context *ctx; 211 212 /** 213 * Scheduler context for running the @e ctx. 214 */ 215 static struct GNUNET_CURL_RescheduleContext *rc; 216 217 /** 218 * Should we run checks that only work for exchange-internal audits? 219 */ 220 static int internal_checks; 221 222 /** 223 * Should we ignore if the bank does not know our bank 224 * account? 225 */ 226 static int ignore_account_404; 227 228 /** 229 * Database event handler to wake us up again. 230 */ 231 static struct GNUNET_DB_EventHandler *eh; 232 233 /** 234 * The auditors's configuration. 235 */ 236 static const struct GNUNET_CONFIGURATION_Handle *cfg; 237 238 /* ***************************** Shutdown **************************** */ 239 240 /** 241 * Entry in map with wire information we expect to obtain from the 242 * bank later. 243 */ 244 struct ReserveInInfo 245 { 246 247 /** 248 * Hash of expected row offset. 249 */ 250 struct GNUNET_HashCode row_off_hash; 251 252 /** 253 * Expected details about the wire transfer. 254 * The member "account_url" is to be allocated 255 * at the end of this struct! 256 */ 257 struct TALER_BANK_CreditDetails credit_details; 258 259 /** 260 * RowID in reserves_in table. 261 */ 262 uint64_t rowid; 263 264 }; 265 266 267 /** 268 * Free entry in #in_map. 269 * 270 * @param cls NULL 271 * @param key unused key 272 * @param value the `struct ReserveInInfo` to free 273 * @return #GNUNET_OK 274 */ 275 static enum GNUNET_GenericReturnValue 276 free_rii (void *cls, 277 const struct GNUNET_HashCode *key, 278 void *value) 279 { 280 struct ReserveInInfo *rii = value; 281 282 (void) cls; 283 GNUNET_assert (GNUNET_YES == 284 GNUNET_CONTAINER_multihashmap_remove (in_map, 285 key, 286 rii)); 287 GNUNET_free (rii); 288 return GNUNET_OK; 289 } 290 291 292 /** 293 * Task run on shutdown. 294 * 295 * @param cls NULL 296 */ 297 static void 298 do_shutdown (void *cls) 299 { 300 struct WireAccount *wa; 301 302 (void) cls; 303 if (NULL != eh) 304 { 305 TALER_AUDITORDB_event_listen_cancel (eh); 306 eh = NULL; 307 } 308 TALER_ARL_done (); 309 if (NULL != in_map) 310 { 311 GNUNET_CONTAINER_multihashmap_iterate (in_map, 312 &free_rii, 313 NULL); 314 GNUNET_CONTAINER_multihashmap_destroy (in_map); 315 in_map = NULL; 316 } 317 while (NULL != (wa = wa_head)) 318 { 319 if (NULL != wa->chh) 320 { 321 TALER_BANK_credit_history_cancel (wa->chh); 322 wa->chh = NULL; 323 } 324 GNUNET_CONTAINER_DLL_remove (wa_head, 325 wa_tail, 326 wa); 327 GNUNET_free (wa->label_reserve_in_serial_id); 328 GNUNET_free (wa->label_wire_off_in); 329 GNUNET_free (wa); 330 } 331 if (NULL != ctx) 332 { 333 GNUNET_CURL_fini (ctx); 334 ctx = NULL; 335 } 336 if (NULL != rc) 337 { 338 GNUNET_CURL_gnunet_rc_destroy (rc); 339 rc = NULL; 340 } 341 TALER_EXCHANGEDB_unload_accounts (); 342 TALER_ARL_cfg = NULL; 343 } 344 345 346 /** 347 * Start the database transactions and begin the audit. 348 * 349 * @return transaction status code 350 */ 351 static enum GNUNET_DB_QueryStatus 352 begin_transaction (void); 353 354 355 /** 356 * Rollback the current transaction, reset our state and try 357 * again (we had a serialization error). 358 */ 359 static void 360 rollback_and_reset (void) 361 { 362 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 363 "Serialization issue, trying again\n"); 364 TALER_AUDITORDB_rollback (TALER_ARL_adb); 365 for (unsigned int max_retries = 3; max_retries>0; max_retries--) 366 { 367 enum GNUNET_DB_QueryStatus qs; 368 369 if (NULL != in_map) 370 { 371 GNUNET_CONTAINER_multihashmap_iterate (in_map, 372 &free_rii, 373 NULL); 374 GNUNET_CONTAINER_multihashmap_destroy (in_map); 375 in_map = NULL; 376 } 377 qs = begin_transaction (); 378 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 379 break; 380 } 381 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 382 "Hard database error, terminating\n"); 383 GNUNET_SCHEDULER_shutdown (); 384 } 385 386 387 /** 388 * Commit the transaction, checkpointing our progress in the auditor DB. 389 * 390 * @param qs transaction status so far 391 */ 392 static void 393 commit (enum GNUNET_DB_QueryStatus qs) 394 { 395 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 396 "Transaction logic ended with status %d\n", 397 qs); 398 TALER_EXCHANGEDB_rollback (TALER_ARL_edb); 399 if (qs < 0) 400 goto handle_db_error; 401 qs = TALER_AUDITORDB_update_balance ( 402 TALER_ARL_adb, 403 TALER_ARL_SET_AB (total_wire_in), 404 TALER_ARL_SET_AB (total_kycauth_in), 405 TALER_ARL_SET_AB (total_wire_credit_fees), 406 TALER_ARL_SET_AB (total_bad_amount_in_plus), 407 TALER_ARL_SET_AB (total_bad_amount_in_minus), 408 TALER_ARL_SET_AB (total_misattribution_in), 409 NULL); 410 if (0 > qs) 411 goto handle_db_error; 412 qs = TALER_AUDITORDB_insert_balance ( 413 TALER_ARL_adb, 414 TALER_ARL_SET_AB (total_wire_in), 415 TALER_ARL_SET_AB (total_kycauth_in), 416 TALER_ARL_SET_AB (total_wire_credit_fees), 417 TALER_ARL_SET_AB (total_bad_amount_in_plus), 418 TALER_ARL_SET_AB (total_bad_amount_in_minus), 419 TALER_ARL_SET_AB (total_misattribution_in), 420 NULL); 421 if (0 > qs) 422 goto handle_db_error; 423 for (struct WireAccount *wa = wa_head; 424 NULL != wa; 425 wa = wa->next) 426 { 427 qs = TALER_AUDITORDB_update_auditor_progress ( 428 TALER_ARL_adb, 429 wa->label_reserve_in_serial_id, 430 wa->last_reserve_in_serial_id, 431 wa->label_wire_off_in, 432 wa->wire_off_in, 433 NULL); 434 if (0 > qs) 435 goto handle_db_error; 436 qs = TALER_AUDITORDB_insert_auditor_progress ( 437 TALER_ARL_adb, 438 wa->label_reserve_in_serial_id, 439 wa->last_reserve_in_serial_id, 440 wa->label_wire_off_in, 441 wa->wire_off_in, 442 NULL); 443 if (0 > qs) 444 goto handle_db_error; 445 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 446 "Transaction ends at %s=%llu for account `%s'\n", 447 wa->label_reserve_in_serial_id, 448 (unsigned long long) wa->last_reserve_in_serial_id, 449 wa->ai->section_name); 450 } 451 qs = TALER_AUDITORDB_commit (TALER_ARL_adb); 452 if (0 > qs) 453 { 454 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 455 goto handle_db_error; 456 } 457 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 458 "Transaction concluded!\n"); 459 if (1 == test_mode) 460 GNUNET_SCHEDULER_shutdown (); 461 return; 462 handle_db_error: 463 rollback_and_reset (); 464 } 465 466 467 /** 468 * Conclude the credit history check by logging entries that 469 * were not found and freeing resources. Then move on to 470 * processing debits. 471 */ 472 static void 473 conclude_credit_history (void) 474 { 475 if (NULL != in_map) 476 { 477 GNUNET_assert (0 == 478 GNUNET_CONTAINER_multihashmap_size (in_map)); 479 GNUNET_CONTAINER_multihashmap_destroy (in_map); 480 in_map = NULL; 481 } 482 commit (global_qs); 483 } 484 485 486 /** 487 * Check if the given wire transfers are equivalent. 488 * 489 * @param credit amount that was received 490 * @param credit2 2nd amount that was received 491 * @param reserve_pub public key of the reserve (also the WTID) 492 * @param reserve_pub2 2nd public key of the reserve (also the WTID) 493 * @param sender_account_details payto://-URL of the sender's bank account 494 * @param sender_account_details2 2nd payto://-URL of the sender's bank account 495 * @param execution_date when did we receive the funds 496 * @param execution_date2 2nd when did we receive the funds 497 * @return #GNUNET_YES if so, 498 * #GNUNET_NO if not 499 * #GNUNET_SYSERR on internal error 500 */ 501 static enum GNUNET_GenericReturnValue 502 check_equality (const struct TALER_Amount *credit, 503 const struct TALER_Amount *credit2, 504 const struct TALER_ReservePublicKeyP *reserve_pub, 505 const struct TALER_ReservePublicKeyP *reserve_pub2, 506 const struct TALER_FullPayto sender_account_details, 507 const struct TALER_FullPayto sender_account_details2, 508 struct GNUNET_TIME_Timestamp execution_date, 509 struct GNUNET_TIME_Timestamp execution_date2) 510 { 511 if (0 != TALER_amount_cmp (credit, 512 credit2)) 513 return GNUNET_NO; 514 if (0 != GNUNET_memcmp (reserve_pub, 515 reserve_pub2)) 516 return GNUNET_NO; 517 { 518 struct TALER_NormalizedPayto np; 519 struct TALER_NormalizedPayto np2; 520 bool fail; 521 522 np = TALER_payto_normalize (sender_account_details); 523 np2 = TALER_payto_normalize (sender_account_details2); 524 fail = (0 != TALER_normalized_payto_cmp (np, 525 np2)); 526 GNUNET_free (np.normalized_payto); 527 GNUNET_free (np2.normalized_payto); 528 if (fail) 529 return GNUNET_NO; 530 } 531 if (GNUNET_TIME_timestamp_cmp (execution_date, 532 !=, 533 execution_date2)) 534 return GNUNET_NO; 535 return GNUNET_YES; 536 } 537 538 539 /** 540 * Function called with details about incoming wire transfers 541 * as claimed by the exchange DB. 542 * 543 * @param cls a `struct WireAccount` we are processing 544 * @param rowid unique serial ID for the entry in our DB 545 * @param reserve_pub public key of the reserve (also the WTID) 546 * @param credit amount that was received 547 * @param sender_account_details payto://-URL of the sender's bank account 548 * @param wire_reference unique identifier for the wire transfer 549 * @param execution_date when did we receive the funds 550 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 551 */ 552 static enum GNUNET_GenericReturnValue 553 reserve_in_cb (void *cls, 554 uint64_t rowid, 555 const struct TALER_ReservePublicKeyP *reserve_pub, 556 const struct TALER_Amount *credit, 557 const struct TALER_FullPayto sender_account_details, 558 uint64_t wire_reference, 559 struct GNUNET_TIME_Timestamp execution_date) 560 { 561 struct WireAccount *wa = cls; 562 struct ReserveInInfo *rii; 563 size_t slen; 564 565 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 566 "Analyzing exchange wire IN (%llu) at %s of %s with reserve_pub %s\n", 567 (unsigned long long) rowid, 568 GNUNET_TIME_timestamp2s (execution_date), 569 TALER_amount2s (credit), 570 TALER_B2S (reserve_pub)); 571 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_wire_in), 572 &TALER_ARL_USE_AB (total_wire_in), 573 credit); 574 { 575 enum GNUNET_DB_QueryStatus qs; 576 struct TALER_AUDITORDB_ReserveInInconsistency dc; 577 578 qs = TALER_AUDITORDB_select_reserve_in_inconsistency ( 579 TALER_ARL_adb, 580 wire_reference, 581 &dc); 582 switch (qs) 583 { 584 case GNUNET_DB_STATUS_HARD_ERROR: 585 case GNUNET_DB_STATUS_SOFT_ERROR: 586 global_qs = qs; 587 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 588 return GNUNET_SYSERR; 589 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 590 break; 591 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 592 if (TALER_amount_is_zero (&dc.amount_exchange_expected)) 593 { 594 /* database entry indicates unmatched transaction */ 595 enum GNUNET_GenericReturnValue ret; 596 597 ret = check_equality (&dc.amount_wired, 598 credit, 599 &dc.reserve_pub, 600 reserve_pub, 601 dc.account, 602 sender_account_details, 603 GNUNET_TIME_absolute_to_timestamp (dc.timestamp), 604 execution_date); 605 if (GNUNET_SYSERR == ret) 606 return GNUNET_SYSERR; 607 if (GNUNET_YES == ret) 608 { 609 qs = TALER_AUDITORDB_delete_reserve_in_inconsistency ( 610 TALER_ARL_adb, 611 dc.serial_id); 612 if (qs < 0) 613 { 614 global_qs = qs; 615 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 616 return GNUNET_SYSERR; 617 } 618 return GNUNET_OK; 619 } 620 } 621 break; 622 } 623 } 624 slen = strlen (sender_account_details.full_payto) + 1; 625 rii = GNUNET_malloc (sizeof (struct ReserveInInfo) + slen); 626 rii->rowid = rowid; 627 rii->credit_details.type = TALER_BANK_CT_RESERVE; 628 rii->credit_details.amount = *credit; 629 rii->credit_details.execution_date = execution_date; 630 rii->credit_details.details.reserve.reserve_pub = *reserve_pub; 631 rii->credit_details.debit_account_uri.full_payto = (char *) &rii[1]; 632 GNUNET_memcpy (&rii[1], 633 sender_account_details.full_payto, 634 slen); 635 GNUNET_CRYPTO_hash (&wire_reference, 636 sizeof (uint64_t), 637 &rii->row_off_hash); 638 if (GNUNET_OK != 639 GNUNET_CONTAINER_multihashmap_put (in_map, 640 &rii->row_off_hash, 641 rii, 642 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) 643 { 644 struct TALER_AUDITORDB_RowInconsistency ri = { 645 .row_id = rowid, 646 .row_table = (char *) "reserves_in", 647 .diagnostic = (char *) "duplicate wire offset" 648 }; 649 enum GNUNET_DB_QueryStatus qs; 650 651 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 652 "Duplicate wire offset\n"); 653 qs = TALER_AUDITORDB_insert_row_inconsistency ( 654 TALER_ARL_adb, 655 &ri); 656 GNUNET_free (rii); 657 if (qs < 0) 658 { 659 global_qs = qs; 660 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 661 return GNUNET_SYSERR; 662 } 663 return GNUNET_OK; 664 } 665 wa->last_reserve_in_serial_id = rowid + 1; 666 return GNUNET_OK; 667 } 668 669 670 /** 671 * Complain that we failed to match an entry from #in_map. 672 * 673 * @param cls a `struct WireAccount` 674 * @param key unused key 675 * @param value the `struct ReserveInInfo` to free 676 * @return #GNUNET_OK 677 */ 678 static enum GNUNET_GenericReturnValue 679 complain_in_not_found (void *cls, 680 const struct GNUNET_HashCode *key, 681 void *value) 682 { 683 struct WireAccount *wa = cls; 684 struct ReserveInInfo *rii = value; 685 enum GNUNET_DB_QueryStatus qs; 686 struct TALER_AUDITORDB_ReserveInInconsistency riiDb = { 687 .bank_row_id = rii->rowid, 688 .diagnostic = (char *) 689 "incoming wire transfer claimed by exchange not found", 690 .account = wa->ai->payto_uri, 691 .amount_exchange_expected = rii->credit_details.amount, 692 .amount_wired = zero, 693 .reserve_pub = rii->credit_details.details.reserve.reserve_pub, 694 .timestamp = rii->credit_details.execution_date.abs_time 695 }; 696 697 (void) key; 698 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 699 "Incoming wire transfer #%llu claimed by exchange not found\n", 700 (unsigned long long) rii->rowid); 701 GNUNET_assert (TALER_BANK_CT_RESERVE == 702 rii->credit_details.type); 703 qs = TALER_AUDITORDB_insert_reserve_in_inconsistency ( 704 TALER_ARL_adb, 705 &riiDb); 706 if (qs < 0) 707 { 708 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 709 global_qs = qs; 710 return GNUNET_SYSERR; 711 } 712 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_minus), 713 &TALER_ARL_USE_AB (total_bad_amount_in_minus), 714 &rii->credit_details.amount); 715 return GNUNET_OK; 716 } 717 718 719 /** 720 * Start processing the next wire account. 721 * Shuts down if we are done. 722 * 723 * @param cls `struct WireAccount` with a wire account list to process 724 */ 725 static void 726 process_credits (void *cls); 727 728 729 /** 730 * We got all of the incoming transactions for @a wa, 731 * finish processing the account. 732 * 733 * @param[in,out] wa wire account to process 734 */ 735 static void 736 conclude_account (struct WireAccount *wa) 737 { 738 GNUNET_assert (NULL == wa->chh); 739 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 740 "Reconciling CREDIT processing of account `%s'\n", 741 wa->ai->section_name); 742 if (NULL != in_map) 743 { 744 GNUNET_CONTAINER_multihashmap_iterate (in_map, 745 &complain_in_not_found, 746 wa); 747 /* clean up before 2nd phase */ 748 GNUNET_CONTAINER_multihashmap_iterate (in_map, 749 &free_rii, 750 NULL); 751 if (global_qs < 0) 752 { 753 commit (global_qs); 754 return; 755 } 756 } 757 process_credits (wa->next); 758 } 759 760 761 /** 762 * Analyze credit transaction @a details into @a wa. 763 * 764 * @param[in,out] wa account that received the transfer 765 * @param credit_details transfer details 766 * @return true on success, false to stop loop at this point 767 */ 768 static bool 769 analyze_credit ( 770 struct WireAccount *wa, 771 const struct TALER_BANK_CreditDetails *credit_details) 772 { 773 struct ReserveInInfo *rii; 774 struct GNUNET_HashCode key; 775 776 switch (credit_details->type) 777 { 778 case TALER_BANK_CT_RESERVE: 779 break; 780 case TALER_BANK_CT_KYCAUTH: 781 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_kycauth_in), 782 &TALER_ARL_USE_AB (total_kycauth_in), 783 &credit_details->amount); 784 return true; 785 case TALER_BANK_CT_WAD: 786 GNUNET_break (0); /* FIXME: Wad not yet supported */ 787 return false; 788 } 789 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 790 "Analyzing bank CREDIT #%llu at %s of %s with Reserve-pub %s\n", 791 (unsigned long long) credit_details->serial_id, 792 GNUNET_TIME_timestamp2s (credit_details->execution_date), 793 TALER_amount2s (&credit_details->amount), 794 TALER_B2S (&credit_details->details.reserve.reserve_pub)); 795 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_wire_credit_fees), 796 &TALER_ARL_USE_AB (total_wire_credit_fees), 797 &credit_details->credit_fee); 798 GNUNET_CRYPTO_hash (&credit_details->serial_id, 799 sizeof (credit_details->serial_id), 800 &key); 801 rii = GNUNET_CONTAINER_multihashmap_get (in_map, 802 &key); 803 if (NULL == rii) 804 { 805 struct TALER_AUDITORDB_ReserveInInconsistency dc = { 806 .bank_row_id = credit_details->serial_id, 807 .amount_exchange_expected = zero, 808 .amount_wired = credit_details->amount, 809 .reserve_pub = credit_details->details.reserve.reserve_pub, 810 .timestamp = credit_details->execution_date.abs_time, 811 .account = credit_details->debit_account_uri, 812 .diagnostic = (char *) "unknown to exchange" 813 }; 814 enum GNUNET_DB_QueryStatus qs; 815 816 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 817 "Failed to find wire transfer at `%s' in exchange database.\n", 818 GNUNET_TIME_timestamp2s (credit_details->execution_date)); 819 qs = TALER_AUDITORDB_insert_reserve_in_inconsistency (TALER_ARL_adb, 820 &dc); 821 if (qs <= 0) 822 { 823 global_qs = qs; 824 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 825 return false; 826 } 827 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_plus), 828 &TALER_ARL_USE_AB (total_bad_amount_in_plus), 829 &credit_details->amount); 830 return true; 831 } 832 833 /* Update offset */ 834 wa->wire_off_in = credit_details->serial_id; 835 /* compare records with expected data */ 836 if (0 != GNUNET_memcmp (&credit_details->details.reserve.reserve_pub, 837 &rii->credit_details.details.reserve.reserve_pub)) 838 { 839 struct TALER_AUDITORDB_ReserveInInconsistency riiDb = { 840 .bank_row_id = credit_details->serial_id, 841 .amount_exchange_expected = rii->credit_details.amount, 842 .amount_wired = zero, 843 .reserve_pub = rii->credit_details.details.reserve.reserve_pub, 844 .timestamp = rii->credit_details.execution_date.abs_time, 845 .account = wa->ai->payto_uri, 846 .diagnostic = (char *) "wire subject does not match" 847 }; 848 enum GNUNET_DB_QueryStatus qs; 849 850 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 851 "Reserve public key differs\n"); 852 qs = TALER_AUDITORDB_insert_reserve_in_inconsistency ( 853 TALER_ARL_adb, 854 &riiDb); 855 if (qs <= 0) 856 { 857 global_qs = qs; 858 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 859 return false; 860 } 861 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_minus), 862 &TALER_ARL_USE_AB (total_bad_amount_in_minus), 863 &rii->credit_details.amount); 864 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_plus), 865 &TALER_ARL_USE_AB (total_bad_amount_in_plus), 866 &credit_details->amount); 867 GNUNET_assert (GNUNET_OK == 868 free_rii (NULL, 869 &key, 870 rii)); 871 return true; 872 } 873 if (0 != TALER_amount_cmp (&rii->credit_details.amount, 874 &credit_details->amount)) 875 { 876 struct TALER_AUDITORDB_ReserveInInconsistency riiDb = { 877 .diagnostic = (char *) "wire amount does not match", 878 .account = wa->ai->payto_uri, 879 .bank_row_id = credit_details->serial_id, 880 .amount_exchange_expected = rii->credit_details.amount, 881 .amount_wired = credit_details->amount, 882 .reserve_pub = rii->credit_details.details.reserve.reserve_pub, 883 .timestamp = rii->credit_details.execution_date.abs_time 884 }; 885 enum GNUNET_DB_QueryStatus qs; 886 887 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 888 "Wire transfer amount differs\n"); 889 qs = TALER_AUDITORDB_insert_reserve_in_inconsistency ( 890 TALER_ARL_adb, 891 &riiDb); 892 if (qs <= 0) 893 { 894 global_qs = qs; 895 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 896 return false; 897 } 898 if (0 < TALER_amount_cmp (&credit_details->amount, 899 &rii->credit_details.amount)) 900 { 901 /* details->amount > rii->details.amount: wire transfer was larger than it should have been */ 902 struct TALER_Amount delta; 903 904 TALER_ARL_amount_subtract (&delta, 905 &credit_details->amount, 906 &rii->credit_details.amount); 907 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_plus), 908 &TALER_ARL_USE_AB (total_bad_amount_in_plus), 909 &delta); 910 } 911 else 912 { 913 /* rii->details.amount < details->amount: wire transfer was smaller than it should have been */ 914 struct TALER_Amount delta; 915 916 TALER_ARL_amount_subtract (&delta, 917 &rii->credit_details.amount, 918 &credit_details->amount); 919 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_bad_amount_in_minus), 920 &TALER_ARL_USE_AB (total_bad_amount_in_minus), 921 &delta); 922 } 923 } 924 925 { 926 struct TALER_NormalizedPayto np; 927 struct TALER_NormalizedPayto np2; 928 929 np = TALER_payto_normalize (credit_details->debit_account_uri); 930 np2 = TALER_payto_normalize (rii->credit_details.debit_account_uri); 931 if (0 != TALER_normalized_payto_cmp (np, 932 np2)) 933 { 934 struct TALER_AUDITORDB_MisattributionInInconsistency mii = { 935 .reserve_pub = rii->credit_details.details.reserve.reserve_pub, 936 .amount = rii->credit_details.amount, 937 .bank_row = credit_details->serial_id 938 }; 939 enum GNUNET_DB_QueryStatus qs; 940 941 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 942 "Origin bank account differs\n"); 943 qs = TALER_AUDITORDB_insert_misattribution_in_inconsistency ( 944 TALER_ARL_adb, 945 &mii); 946 if (qs <= 0) 947 { 948 global_qs = qs; 949 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 950 GNUNET_free (np.normalized_payto); 951 GNUNET_free (np2.normalized_payto); 952 return false; 953 } 954 TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_misattribution_in), 955 &TALER_ARL_USE_AB (total_misattribution_in), 956 &rii->credit_details.amount); 957 } 958 GNUNET_free (np.normalized_payto); 959 GNUNET_free (np2.normalized_payto); 960 } 961 if (GNUNET_TIME_timestamp_cmp (credit_details->execution_date, 962 !=, 963 rii->credit_details.execution_date)) 964 { 965 struct TALER_AUDITORDB_RowMinorInconsistencies rmi = { 966 .problem_row = rii->rowid, 967 .diagnostic = (char *) "execution date mismatch", 968 .row_table = (char *) "reserves_in" 969 }; 970 enum GNUNET_DB_QueryStatus qs; 971 972 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 973 "Execution date differs\n"); 974 qs = TALER_AUDITORDB_insert_row_minor_inconsistencies ( 975 TALER_ARL_adb, 976 &rmi); 977 978 if (qs < 0) 979 { 980 global_qs = qs; 981 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 982 return false; 983 } 984 } 985 GNUNET_assert (GNUNET_OK == 986 free_rii (NULL, 987 &key, 988 rii)); 989 return true; 990 } 991 992 993 /** 994 * This function is called for all transactions that 995 * are credited to the exchange's account (incoming 996 * transactions). 997 * 998 * @param cls `struct WireAccount` we are processing 999 * @param chr HTTP response returned by the bank 1000 */ 1001 static void 1002 history_credit_cb (void *cls, 1003 const struct TALER_BANK_CreditHistoryResponse *chr) 1004 { 1005 struct WireAccount *wa = cls; 1006 1007 wa->chh = NULL; 1008 switch (chr->http_status) 1009 { 1010 case MHD_HTTP_OK: 1011 for (unsigned int i = 0; i < chr->details.ok.details_length; i++) 1012 { 1013 const struct TALER_BANK_CreditDetails *cd 1014 = &chr->details.ok.details[i]; 1015 1016 if (! analyze_credit (wa, 1017 cd)) 1018 { 1019 switch (global_qs) 1020 { 1021 case GNUNET_DB_STATUS_SOFT_ERROR: 1022 rollback_and_reset (); 1023 return; 1024 case GNUNET_DB_STATUS_HARD_ERROR: 1025 GNUNET_SCHEDULER_shutdown (); 1026 return; 1027 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1028 /* perfectly fine */ 1029 break; 1030 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1031 /* perfectly fine */ 1032 break; 1033 } 1034 break; 1035 } 1036 } 1037 conclude_account (wa); 1038 return; 1039 case MHD_HTTP_NO_CONTENT: 1040 conclude_account (wa); 1041 return; 1042 case MHD_HTTP_NOT_FOUND: 1043 if (ignore_account_404) 1044 { 1045 conclude_account (wa); 1046 return; 1047 } 1048 break; 1049 default: 1050 break; 1051 } 1052 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1053 "Error fetching credit history of account %s: %u (%s)\n", 1054 wa->ai->section_name, 1055 chr->http_status, 1056 TALER_ErrorCode_get_hint (chr->ec)); 1057 commit (GNUNET_DB_STATUS_HARD_ERROR); 1058 global_ret = EXIT_FAILURE; 1059 GNUNET_SCHEDULER_shutdown (); 1060 } 1061 1062 1063 /* ***************************** Setup logic ************************ */ 1064 1065 1066 /** 1067 * Start processing the next wire account. 1068 * Shuts down if we are done. 1069 * 1070 * @param cls `struct WireAccount` with a wire account list to process 1071 */ 1072 static void 1073 process_credits (void *cls) 1074 { 1075 struct WireAccount *wa = cls; 1076 enum GNUNET_DB_QueryStatus qs; 1077 1078 /* skip accounts where CREDIT is not enabled */ 1079 while ( (NULL != wa) && 1080 (GNUNET_NO == wa->ai->credit_enabled) ) 1081 wa = wa->next; 1082 if (NULL == wa) 1083 { 1084 /* done with all accounts, conclude check */ 1085 conclude_credit_history (); 1086 return; 1087 } 1088 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1089 "Analyzing exchange's wire IN table for account `%s'\n", 1090 wa->ai->section_name); 1091 qs = TALER_TALER_EXCHANGEDB_select_reserves_in_above_serial_id_by_account ( 1092 TALER_ARL_edb, 1093 wa->ai->section_name, 1094 wa->last_reserve_in_serial_id, 1095 &reserve_in_cb, 1096 wa); 1097 if (0 > qs) 1098 { 1099 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1100 global_ret = EXIT_FAILURE; 1101 GNUNET_SCHEDULER_shutdown (); 1102 return; 1103 } 1104 1105 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1106 "Starting bank CREDIT history of account `%s'\n", 1107 wa->ai->section_name); 1108 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1109 "user `%s'\n", 1110 wa->ai->auth->details.basic.username); 1111 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1112 "pass `%s'\n", 1113 wa->ai->auth->details.basic.password); 1114 GNUNET_assert (NULL == wa->chh); 1115 wa->chh = TALER_BANK_credit_history (ctx, 1116 wa->ai->auth, 1117 wa->wire_off_in, 1118 MAX_PER_TRANSACTION, 1119 GNUNET_TIME_UNIT_ZERO, 1120 &history_credit_cb, 1121 wa); 1122 if (NULL == wa->chh) 1123 { 1124 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1125 "Failed to obtain bank transaction history\n"); 1126 commit (GNUNET_DB_STATUS_HARD_ERROR); 1127 global_ret = EXIT_FAILURE; 1128 GNUNET_SCHEDULER_shutdown (); 1129 return; 1130 } 1131 } 1132 1133 1134 /** 1135 * Begin audit of CREDITs to the exchange. 1136 */ 1137 static void 1138 begin_credit_audit (void) 1139 { 1140 GNUNET_assert (NULL == in_map); 1141 in_map = GNUNET_CONTAINER_multihashmap_create (1024, 1142 GNUNET_YES); 1143 /* now go over all bank accounts and check delta with in_map */ 1144 process_credits (wa_head); 1145 } 1146 1147 1148 static enum GNUNET_DB_QueryStatus 1149 begin_transaction (void) 1150 { 1151 enum GNUNET_DB_QueryStatus qs; 1152 1153 if (GNUNET_SYSERR == 1154 TALER_EXCHANGEDB_preflight (TALER_ARL_edb)) 1155 { 1156 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1157 "Failed to initialize exchange database connection.\n"); 1158 return GNUNET_DB_STATUS_HARD_ERROR; 1159 } 1160 if (GNUNET_SYSERR == 1161 TALER_AUDITORDB_preflight (TALER_ARL_adb)) 1162 { 1163 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1164 "Failed to initialize auditor database session.\n"); 1165 return GNUNET_DB_STATUS_HARD_ERROR; 1166 } 1167 global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 1168 if (GNUNET_OK != 1169 TALER_AUDITORDB_start (TALER_ARL_adb)) 1170 { 1171 GNUNET_break (0); 1172 return GNUNET_DB_STATUS_HARD_ERROR; 1173 } 1174 if (GNUNET_OK != 1175 TALER_TALER_EXCHANGEDB_start_read_only (TALER_ARL_edb, 1176 "wire credit auditor")) 1177 { 1178 GNUNET_break (0); 1179 return GNUNET_DB_STATUS_HARD_ERROR; 1180 } 1181 qs = TALER_AUDITORDB_get_balance ( 1182 TALER_ARL_adb, 1183 TALER_ARL_GET_AB (total_wire_in), 1184 TALER_ARL_GET_AB (total_kycauth_in), 1185 TALER_ARL_GET_AB (total_wire_credit_fees), 1186 TALER_ARL_GET_AB (total_bad_amount_in_plus), 1187 TALER_ARL_GET_AB (total_bad_amount_in_minus), 1188 TALER_ARL_GET_AB (total_misattribution_in), 1189 NULL); 1190 switch (qs) 1191 { 1192 case GNUNET_DB_STATUS_HARD_ERROR: 1193 GNUNET_break (0); 1194 return qs; 1195 case GNUNET_DB_STATUS_SOFT_ERROR: 1196 GNUNET_break (0); 1197 return qs; 1198 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1199 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1200 break; 1201 } 1202 for (struct WireAccount *wa = wa_head; 1203 NULL != wa; 1204 wa = wa->next) 1205 { 1206 GNUNET_asprintf (&wa->label_reserve_in_serial_id, 1207 "wire-%s-%s", 1208 wa->ai->section_name, 1209 "reserve_in_serial_id"); 1210 GNUNET_asprintf (&wa->label_wire_off_in, 1211 "wire-%s-%s", 1212 wa->ai->section_name, 1213 "wire_off_in"); 1214 qs = TALER_AUDITORDB_get_auditor_progress ( 1215 TALER_ARL_adb, 1216 wa->label_reserve_in_serial_id, 1217 &wa->last_reserve_in_serial_id, 1218 wa->label_wire_off_in, 1219 &wa->wire_off_in, 1220 NULL); 1221 if (0 > qs) 1222 { 1223 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1224 return qs; 1225 } 1226 wa->start_reserve_in_serial_id = wa->last_reserve_in_serial_id; 1227 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1228 "Starting from reserve_in at %s=%llu for account `%s'\n", 1229 wa->label_reserve_in_serial_id, 1230 (unsigned long long) wa->start_reserve_in_serial_id, 1231 wa->ai->section_name); 1232 } 1233 1234 begin_credit_audit (); 1235 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 1236 } 1237 1238 1239 /** 1240 * Function called with information about a wire account. Adds the 1241 * account to our list for processing (if it is enabled and we can 1242 * load the plugin). 1243 * 1244 * @param cls closure, NULL 1245 * @param ai account information 1246 */ 1247 static void 1248 process_account_cb (void *cls, 1249 const struct TALER_EXCHANGEDB_AccountInfo *ai) 1250 { 1251 struct WireAccount *wa; 1252 1253 (void) cls; 1254 if ((! ai->debit_enabled) && 1255 (! ai->credit_enabled)) 1256 return; /* not an active exchange account */ 1257 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1258 "Found exchange account `%s'\n", 1259 ai->section_name); 1260 wa = GNUNET_new (struct WireAccount); 1261 wa->ai = ai; 1262 GNUNET_CONTAINER_DLL_insert (wa_head, 1263 wa_tail, 1264 wa); 1265 } 1266 1267 1268 /** 1269 * Function called on events received from Postgres. 1270 * 1271 * @param cls closure, NULL 1272 * @param extra additional event data provided 1273 * @param extra_size number of bytes in @a extra 1274 */ 1275 static void 1276 db_notify (void *cls, 1277 const void *extra, 1278 size_t extra_size) 1279 { 1280 (void) cls; 1281 (void) extra; 1282 (void) extra_size; 1283 1284 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1285 "Received notification to wake wire helper\n"); 1286 /* If there are accounts we are still processing, abort 1287 the HTTP requests so we can start afresh. */ 1288 for (struct WireAccount *wa = wa_head; 1289 NULL != wa; 1290 wa = wa->next) 1291 { 1292 if (NULL != wa->chh) 1293 { 1294 TALER_BANK_credit_history_cancel (wa->chh); 1295 wa->chh = NULL; 1296 } 1297 conclude_account (wa); 1298 } 1299 1300 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1301 begin_transaction ()) 1302 { 1303 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1304 "Audit failed\n"); 1305 GNUNET_break (0); 1306 global_ret = EXIT_FAILURE; 1307 GNUNET_SCHEDULER_shutdown (); 1308 } 1309 } 1310 1311 1312 /** 1313 * Main function that will be run. 1314 * 1315 * @param cls closure 1316 * @param args remaining command-line arguments 1317 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1318 * @param c configuration 1319 */ 1320 static void 1321 run (void *cls, 1322 char *const *args, 1323 const char *cfgfile, 1324 const struct GNUNET_CONFIGURATION_Handle *c) 1325 { 1326 (void) cls; 1327 (void) args; 1328 (void) cfgfile; 1329 cfg = c; 1330 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1331 "Launching wire-credit auditor\n"); 1332 if (GNUNET_OK != 1333 TALER_ARL_init (c)) 1334 { 1335 global_ret = EXIT_FAILURE; 1336 return; 1337 } 1338 if (GNUNET_OK != 1339 TALER_config_get_amount (TALER_ARL_cfg, 1340 "auditor", 1341 "TINY_AMOUNT", 1342 &tiny_amount)) 1343 { 1344 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1345 "auditor", 1346 "TINY_AMOUNT"); 1347 global_ret = EXIT_NOTCONFIGURED; 1348 return; 1349 } 1350 GNUNET_assert (GNUNET_OK == 1351 TALER_amount_set_zero (TALER_ARL_currency, 1352 &zero)); 1353 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1354 NULL); 1355 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1356 &rc); 1357 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1358 if (NULL == ctx) 1359 { 1360 GNUNET_break (0); 1361 global_ret = EXIT_FAILURE; 1362 return; 1363 } 1364 if (GNUNET_OK != 1365 TALER_EXCHANGEDB_load_accounts (TALER_ARL_cfg, 1366 TALER_EXCHANGEDB_ALO_CREDIT 1367 | TALER_EXCHANGEDB_ALO_AUTHDATA)) 1368 { 1369 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1370 "No bank accounts configured\n"); 1371 global_ret = EXIT_NOTCONFIGURED; 1372 GNUNET_SCHEDULER_shutdown (); 1373 return; 1374 } 1375 TALER_EXCHANGEDB_find_accounts (&process_account_cb, 1376 NULL); 1377 1378 if (0 == test_mode) 1379 { 1380 struct GNUNET_DB_EventHeaderP es = { 1381 .size = htons (sizeof (es)), 1382 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_WIRE) 1383 }; 1384 1385 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 1386 &es, 1387 GNUNET_TIME_UNIT_FOREVER_REL, 1388 &db_notify, 1389 NULL); 1390 GNUNET_assert (NULL != eh); 1391 } 1392 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1393 begin_transaction ()) 1394 { 1395 GNUNET_break (0); 1396 global_ret = EXIT_FAILURE; 1397 GNUNET_SCHEDULER_shutdown (); 1398 return; 1399 } 1400 } 1401 1402 1403 /** 1404 * The main function of the wire auditing tool. Checks that 1405 * the exchange's records of wire transfers match that of 1406 * the wire gateway. 1407 * 1408 * @param argc number of arguments from the command line 1409 * @param argv command line arguments 1410 * @return 0 ok, 1 on error 1411 */ 1412 int 1413 main (int argc, 1414 char *const *argv) 1415 { 1416 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1417 GNUNET_GETOPT_option_flag ('i', 1418 "internal", 1419 "perform checks only applicable for exchange-internal audits", 1420 &internal_checks), 1421 GNUNET_GETOPT_option_flag ('I', 1422 "ignore-not-found", 1423 "continue, even if the bank account of the exchange was not found", 1424 &ignore_account_404), 1425 GNUNET_GETOPT_option_flag ('t', 1426 "test", 1427 "run in test mode and exit when idle", 1428 &test_mode), 1429 GNUNET_GETOPT_option_timetravel ('T', 1430 "timetravel"), 1431 GNUNET_GETOPT_OPTION_END 1432 }; 1433 enum GNUNET_GenericReturnValue ret; 1434 1435 ret = GNUNET_PROGRAM_run ( 1436 TALER_AUDITOR_project_data (), 1437 argc, 1438 argv, 1439 "taler-helper-auditor-wire-credit", 1440 gettext_noop ( 1441 "Audit exchange database for consistency with the bank's wire transfers"), 1442 options, 1443 &run, 1444 NULL); 1445 if (GNUNET_SYSERR == ret) 1446 return EXIT_INVALIDARGUMENT; 1447 if (GNUNET_NO == ret) 1448 return EXIT_SUCCESS; 1449 return global_ret; 1450 } 1451 1452 1453 /* end of taler-helper-auditor-wire-credit.c */