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