taler-helper-auditor-purses.c (56579B)
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-purses.c 18 * @brief audits the purses of an exchange database 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 "report-lib.h" 28 #include "taler/taler_dbevents.h" 29 #include "auditor-database/delete_purse_info.h" 30 #include "auditor-database/event_listen.h" 31 #include "auditor-database/get_auditor_progress.h" 32 #include "auditor-database/get_balance.h" 33 #include "auditor-database/get_purse_info.h" 34 #include "auditor-database/insert_amount_arithmetic_inconsistency.h" 35 #include "auditor-database/insert_auditor_progress.h" 36 #include "auditor-database/insert_bad_sig_losses.h" 37 #include "auditor-database/insert_balance.h" 38 #include "auditor-database/insert_purse_info.h" 39 #include "auditor-database/insert_purse_not_closed_inconsistencies.h" 40 #include "auditor-database/insert_row_inconsistency.h" 41 #include "auditor-database/select_purse_expired.h" 42 #include "auditor-database/update_auditor_progress.h" 43 #include "auditor-database/update_balance.h" 44 #include "auditor-database/update_purse_info.h" 45 #include "exchange-database/get_global_fee.h" 46 #include "exchange-database/select_account_merges_above_serial_id.h" 47 #include "exchange-database/select_all_purse_decisions_above_serial_id.h" 48 #include "exchange-database/select_all_purse_deletions_above_serial_id.h" 49 #include "exchange-database/select_purse.h" 50 #include "exchange-database/select_purse_deposits_above_serial_id.h" 51 #include "exchange-database/select_purse_merges_above_serial_id.h" 52 #include "exchange-database/select_purse_requests_above_serial_id.h" 53 54 55 /** 56 * Use a 1 day grace period to deal with clocks not being perfectly synchronized. 57 */ 58 #define EXPIRATION_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS 59 60 /** 61 * Return value from main(). 62 */ 63 static int global_ret; 64 65 /** 66 * Run in test mode. Exit when idle instead of 67 * going to sleep and waiting for more work. 68 */ 69 static int test_mode; 70 71 /** 72 * Checkpointing our progress for purses. 73 */ 74 static TALER_ARL_DEF_PP (purse_account_merge_serial_id); 75 static TALER_ARL_DEF_PP (purse_decision_serial_id); 76 static TALER_ARL_DEF_PP (purse_deletion_serial_id); 77 static TALER_ARL_DEF_PP (purse_deposits_serial_id); 78 static TALER_ARL_DEF_PP (purse_merges_serial_id); 79 static TALER_ARL_DEF_PP (purse_request_serial_id); 80 static TALER_ARL_DEF_PP (purse_open_counter); 81 static TALER_ARL_DEF_AB (purse_global_balance); 82 83 /** 84 * Total amount purses were merged with insufficient balance. 85 */ 86 static TALER_ARL_DEF_AB (purse_total_balance_insufficient_loss); 87 88 /** 89 * Total amount purse decisions are delayed past deadline. 90 */ 91 static TALER_ARL_DEF_AB (purse_total_delayed_decisions); 92 93 /** 94 * Total amount affected by purses not having been closed on time. 95 */ 96 static TALER_ARL_DEF_AB (purse_total_balance_purse_not_closed); 97 98 /** 99 * Profits the exchange made by bad amount calculations. 100 */ 101 static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_plus); 102 103 /** 104 * Losses the exchange made by bad amount calculations. 105 */ 106 static TALER_ARL_DEF_AB (purse_total_arithmetic_delta_minus); 107 108 /** 109 * Total amount lost by operations for which signatures were invalid. 110 */ 111 static TALER_ARL_DEF_AB (purse_total_bad_sig_loss); 112 113 /** 114 * Should we run checks that only work for exchange-internal audits? 115 */ 116 static int internal_checks; 117 118 static struct GNUNET_DB_EventHandler *eh; 119 120 /** 121 * The auditors's configuration. 122 */ 123 static const struct GNUNET_CONFIGURATION_Handle *cfg; 124 125 /* ***************************** Report logic **************************** */ 126 127 128 /** 129 * Report a (serious) inconsistency in the exchange's database with 130 * respect to calculations involving amounts. 131 * 132 * @param operation what operation had the inconsistency 133 * @param rowid affected row, 0 if row is missing 134 * @param exchange amount calculated by exchange 135 * @param auditor amount calculated by auditor 136 * @param profitable 1 if @a exchange being larger than @a auditor is 137 * profitable for the exchange for this operation, 138 * -1 if @a exchange being smaller than @a auditor is 139 * profitable for the exchange, and 0 if it is unclear 140 * @return transaction status 141 */ 142 static enum GNUNET_DB_QueryStatus 143 report_amount_arithmetic_inconsistency ( 144 const char *operation, 145 uint64_t rowid, 146 const struct TALER_Amount *exchange, 147 const struct TALER_Amount *auditor, 148 int profitable) 149 { 150 struct TALER_Amount delta; 151 struct TALER_Amount *target; 152 enum GNUNET_DB_QueryStatus qs; 153 154 if (0 < TALER_amount_cmp (exchange, 155 auditor)) 156 { 157 /* exchange > auditor */ 158 TALER_ARL_amount_subtract (&delta, 159 exchange, 160 auditor); 161 } 162 else 163 { 164 /* auditor < exchange */ 165 profitable = -profitable; 166 TALER_ARL_amount_subtract (&delta, 167 auditor, 168 exchange); 169 } 170 171 { 172 struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = { 173 .profitable = profitable, 174 .problem_row_id = rowid, 175 .operation = (char *) operation, 176 .exchange_amount = *exchange, 177 .auditor_amount = *auditor 178 }; 179 180 qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency ( 181 TALER_ARL_adb, 182 &aai); 183 184 if (qs < 0) 185 { 186 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 187 return qs; 188 } 189 } 190 191 if (0 != profitable) 192 { 193 target = (1 == profitable) 194 ? &TALER_ARL_USE_AB (purse_total_arithmetic_delta_plus) 195 : &TALER_ARL_USE_AB (purse_total_arithmetic_delta_minus); 196 TALER_ARL_amount_add (target, 197 target, 198 &delta); 199 } 200 return qs; 201 } 202 203 204 /** 205 * Report a (serious) inconsistency in the exchange's database. 206 * 207 * @param table affected table 208 * @param rowid affected row, 0 if row is missing 209 * @param diagnostic message explaining the problem 210 * @return transaction status 211 */ 212 static enum GNUNET_DB_QueryStatus 213 report_row_inconsistency (const char *table, 214 uint64_t rowid, 215 const char *diagnostic) 216 { 217 enum GNUNET_DB_QueryStatus qs; 218 struct TALER_AUDITORDB_RowInconsistency ri = { 219 .diagnostic = (char *) diagnostic, 220 .row_table = (char *) table, 221 .row_id = rowid 222 }; 223 224 qs = TALER_AUDITORDB_insert_row_inconsistency ( 225 TALER_ARL_adb, 226 &ri); 227 228 if (qs < 0) 229 { 230 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 231 return qs; 232 } 233 return qs; 234 } 235 236 237 /** 238 * Obtain the purse fee for a purse created at @a time. 239 * 240 * @param atime when was the purse created 241 * @param[out] fee set to the purse fee 242 * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success 243 */ 244 static enum GNUNET_DB_QueryStatus 245 get_purse_fee (struct GNUNET_TIME_Timestamp atime, 246 struct TALER_Amount *fee) 247 { 248 enum GNUNET_DB_QueryStatus qs; 249 struct TALER_MasterSignatureP master_sig; 250 struct GNUNET_TIME_Timestamp start_date; 251 struct GNUNET_TIME_Timestamp end_date; 252 struct TALER_GlobalFeeSet fees; 253 struct GNUNET_TIME_Relative ptimeout; 254 struct GNUNET_TIME_Relative hexp; 255 uint32_t pacl; 256 257 qs = TALER_EXCHANGEDB_get_global_fee (TALER_ARL_edb, 258 atime, 259 &start_date, 260 &end_date, 261 &fees, 262 &ptimeout, 263 &hexp, 264 &pacl, 265 &master_sig); 266 if (0 > qs) 267 { 268 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 269 return qs; 270 } 271 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 272 { 273 char *diag; 274 275 GNUNET_asprintf (&diag, 276 "purse fee unavailable at %s\n", 277 GNUNET_TIME_timestamp2s (atime)); 278 qs = report_row_inconsistency ("purse-fee", 279 atime.abs_time.abs_value_us, 280 diag); 281 GNUNET_free (diag); 282 if (0 > qs) 283 { 284 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 285 return qs; 286 } 287 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 288 } 289 *fee = fees.purse; 290 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 291 } 292 293 294 /* ***************************** Analyze purses ************************ */ 295 /* This logic checks the purses_requests, purse_deposits, 296 purse_refunds, purse_merges and account_merges */ 297 298 /** 299 * Summary data we keep per purse. 300 */ 301 struct PurseSummary 302 { 303 /** 304 * Public key of the purse. 305 * Always set when the struct is first initialized. 306 */ 307 struct TALER_PurseContractPublicKeyP purse_pub; 308 309 /** 310 * Balance of the purse from deposits (includes purse fee, excludes deposit 311 * fees), as calculated by auditor. 312 */ 313 struct TALER_Amount balance; 314 315 /** 316 * Expected value of the purse, excludes purse fee. 317 */ 318 struct TALER_Amount total_value; 319 320 /** 321 * Purse balance according to exchange DB. 322 */ 323 struct TALER_Amount exchange_balance; 324 325 /** 326 * Contract terms of the purse. 327 */ 328 struct TALER_PrivateContractHashP h_contract_terms; 329 330 /** 331 * Merge timestamp (as per exchange DB). 332 */ 333 struct GNUNET_TIME_Timestamp merge_timestamp; 334 335 /** 336 * Purse creation date. This is when the merge 337 * fee is applied. 338 */ 339 struct GNUNET_TIME_Timestamp creation_date; 340 341 /** 342 * Purse expiration date. 343 */ 344 struct GNUNET_TIME_Timestamp expiration_date; 345 346 /** 347 * Did we have a previous purse info? Used to decide between UPDATE and 348 * INSERT later. Initialized in #load_auditor_purse_summary(). 349 */ 350 bool had_pi; 351 352 /** 353 * Was the purse deleted? Note: as this is set via an UPDATE, it 354 * may be false at the auditor even if the purse was deleted. Thus, 355 * this value is only meaningful for *internal* checks. 356 */ 357 bool purse_deleted; 358 359 /** 360 * Was the purse refunded? Note: as this is set via an UPDATE, it 361 * may be false at the auditor even if the purse was deleted. Thus, 362 * this value is only meaningful for *internal* checks. 363 */ 364 bool purse_refunded; 365 366 }; 367 368 369 /** 370 * Load the auditor's remembered state about the purse into @a ps. 371 * 372 * @param[in,out] ps purse summary to (fully) initialize 373 * @return transaction status code 374 */ 375 static enum GNUNET_DB_QueryStatus 376 load_auditor_purse_summary (struct PurseSummary *ps) 377 { 378 enum GNUNET_DB_QueryStatus qs; 379 uint64_t rowid; 380 381 qs = TALER_AUDITORDB_get_purse_info (TALER_ARL_adb, 382 &ps->purse_pub, 383 &rowid, 384 &ps->balance, 385 &ps->expiration_date); 386 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 387 "Loaded purse `%s' info (%d)\n", 388 TALER_B2S (&ps->purse_pub), 389 (int) qs); 390 if (0 > qs) 391 { 392 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 393 return qs; 394 } 395 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 396 { 397 ps->had_pi = false; 398 GNUNET_assert (GNUNET_OK == 399 TALER_amount_set_zero (TALER_ARL_currency, 400 &ps->balance)); 401 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 402 "Creating fresh purse `%s'\n", 403 TALER_B2S (&ps->purse_pub)); 404 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 405 } 406 ps->had_pi = true; 407 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 408 "Auditor remembers purse `%s' has balance %s\n", 409 TALER_B2S (&ps->purse_pub), 410 TALER_amount2s (&ps->balance)); 411 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 412 } 413 414 415 /** 416 * Closure to the various callbacks we make while checking a purse. 417 */ 418 struct PurseContext 419 { 420 /** 421 * Map from hash of purse's public key to a `struct PurseSummary`. 422 */ 423 struct GNUNET_CONTAINER_MultiHashMap *purses; 424 425 /** 426 * Transaction status code, set to error codes if applicable. 427 */ 428 enum GNUNET_DB_QueryStatus qs; 429 430 }; 431 432 433 /** 434 * Create a new purse for @a purse_pub in @a pc. 435 * 436 * @param[in,out] pc context to update 437 * @param purse_pub key for which to create a purse 438 * @return NULL on error 439 */ 440 static struct PurseSummary * 441 setup_purse (struct PurseContext *pc, 442 const struct TALER_PurseContractPublicKeyP *purse_pub) 443 { 444 struct PurseSummary *ps; 445 struct GNUNET_HashCode key; 446 enum GNUNET_DB_QueryStatus qs; 447 448 GNUNET_CRYPTO_hash (purse_pub, 449 sizeof (*purse_pub), 450 &key); 451 ps = GNUNET_CONTAINER_multihashmap_get (pc->purses, 452 &key); 453 if (NULL != ps) 454 { 455 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 456 "Found purse `%s' summary in cache\n", 457 TALER_B2S (&ps->purse_pub)); 458 return ps; 459 } 460 ps = GNUNET_new (struct PurseSummary); 461 ps->purse_pub = *purse_pub; 462 GNUNET_assert (GNUNET_OK == 463 TALER_amount_set_zero (TALER_ARL_currency, 464 &ps->balance)); 465 /* get purse meta-data from exchange DB */ 466 qs = TALER_EXCHANGEDB_select_purse (TALER_ARL_edb, 467 purse_pub, 468 &ps->creation_date, 469 &ps->expiration_date, 470 &ps->total_value, 471 &ps->exchange_balance, 472 &ps->h_contract_terms, 473 &ps->merge_timestamp, 474 &ps->purse_deleted, 475 &ps->purse_refunded); 476 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 477 "Loaded purse `%s' meta-data (%d)\n", 478 TALER_B2S (purse_pub), 479 (int) qs); 480 if (0 >= qs) 481 { 482 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 483 "Failed to load meta-data of purse `%s'\n", 484 TALER_B2S (&ps->purse_pub)); 485 GNUNET_free (ps); 486 pc->qs = qs; 487 return NULL; 488 } 489 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 490 qs = load_auditor_purse_summary (ps); 491 if (0 > qs) 492 { 493 GNUNET_free (ps); 494 pc->qs = qs; 495 return NULL; 496 } 497 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 498 "Starting purse `%s' analysis\n", 499 TALER_B2S (purse_pub)); 500 GNUNET_assert (GNUNET_OK == 501 GNUNET_CONTAINER_multihashmap_put (pc->purses, 502 &key, 503 ps, 504 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY) 505 ); 506 return ps; 507 } 508 509 510 /** 511 * Function called on purse requests. 512 * 513 * @param cls closure 514 * @param rowid which row in the database was the request stored in 515 * @param purse_pub public key of the purse 516 * @param merge_pub public key representing the merge capability 517 * @param purse_creation when was the purse created 518 * @param purse_expiration when would an unmerged purse expire 519 * @param h_contract_terms contract associated with the purse 520 * @param age_limit the age limit for deposits into the purse 521 * @param target_amount amount to be put into the purse 522 * @param purse_sig signature of the purse over the initialization data 523 * @return #GNUNET_OK to continue to iterate 524 */ 525 static enum GNUNET_GenericReturnValue 526 handle_purse_requested ( 527 void *cls, 528 uint64_t rowid, 529 const struct TALER_PurseContractPublicKeyP *purse_pub, 530 const struct TALER_PurseMergePublicKeyP *merge_pub, 531 struct GNUNET_TIME_Timestamp purse_creation, 532 struct GNUNET_TIME_Timestamp purse_expiration, 533 const struct TALER_PrivateContractHashP *h_contract_terms, 534 uint32_t age_limit, 535 const struct TALER_Amount *target_amount, 536 const struct TALER_PurseContractSignatureP *purse_sig) 537 { 538 struct PurseContext *pc = cls; 539 struct PurseSummary *ps; 540 struct GNUNET_HashCode key; 541 542 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 543 "Handling purse request `%s'\n", 544 TALER_B2S (purse_pub)); 545 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_request_serial_id)); 546 TALER_ARL_USE_PP (purse_request_serial_id) = rowid + 1; 547 if (GNUNET_OK != 548 TALER_wallet_purse_create_verify (purse_expiration, 549 h_contract_terms, 550 merge_pub, 551 age_limit, 552 target_amount, 553 purse_pub, 554 purse_sig)) 555 { 556 struct TALER_AUDITORDB_BadSigLosses bsl = { 557 .problem_row_id = rowid, 558 .operation = (char *) "purse-request", 559 .loss = *target_amount, 560 .operation_specific_pub = purse_pub->eddsa_pub 561 }; 562 enum GNUNET_DB_QueryStatus qs; 563 564 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 565 TALER_ARL_adb, 566 &bsl); 567 if (qs < 0) 568 { 569 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 570 pc->qs = qs; 571 return GNUNET_SYSERR; 572 } 573 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 574 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 575 target_amount); 576 } 577 GNUNET_CRYPTO_hash (purse_pub, 578 sizeof (*purse_pub), 579 &key); 580 ps = GNUNET_new (struct PurseSummary); 581 ps->purse_pub = *purse_pub; 582 GNUNET_assert (GNUNET_OK == 583 TALER_amount_set_zero (TALER_ARL_currency, 584 &ps->balance)); 585 ps->creation_date = purse_creation; 586 ps->expiration_date = purse_expiration; 587 ps->total_value = *target_amount; 588 ps->h_contract_terms = *h_contract_terms; 589 { 590 enum GNUNET_DB_QueryStatus qs; 591 592 qs = load_auditor_purse_summary (ps); 593 if (0 > qs) 594 { 595 GNUNET_free (ps); 596 pc->qs = qs; 597 return GNUNET_SYSERR; 598 } 599 } 600 GNUNET_assert (GNUNET_OK == 601 GNUNET_CONTAINER_multihashmap_put (pc->purses, 602 &key, 603 ps, 604 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY) 605 ); 606 return GNUNET_OK; 607 } 608 609 610 /** 611 * Function called with details about purse deposits that have been made, with 612 * the goal of auditing the deposit's execution. 613 * 614 * @param cls closure 615 * @param rowid unique serial ID for the deposit in our DB 616 * @param deposit deposit details 617 * @param reserve_pub which reserve is the purse merged into, NULL if unknown 618 * @param flags purse flags 619 * @param auditor_balance purse balance (according to the 620 * auditor during auditing) 621 * @param purse_total target amount the purse should reach 622 * @param denom_pub denomination public key of @a coin_pub 623 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 624 */ 625 static enum GNUNET_GenericReturnValue 626 handle_purse_deposits ( 627 void *cls, 628 uint64_t rowid, 629 const struct TALER_EXCHANGEDB_PurseDeposit *deposit, 630 const struct TALER_ReservePublicKeyP *reserve_pub, 631 enum TALER_WalletAccountMergeFlags flags, 632 const struct TALER_Amount *auditor_balance, 633 const struct TALER_Amount *purse_total, 634 const struct TALER_DenominationPublicKey *denom_pub) 635 { 636 struct PurseContext *pc = cls; 637 struct TALER_Amount amount_minus_fee; 638 const char *base_url 639 = (NULL == deposit->exchange_base_url) 640 ? TALER_ARL_exchange_url 641 : deposit->exchange_base_url; 642 struct TALER_DenominationHashP h_denom_pub; 643 644 /* should be monotonically increasing */ 645 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 646 "Handling purse deposit `%s'\n", 647 TALER_B2S (&deposit->purse_pub)); 648 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id)); 649 TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1; 650 651 { 652 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue; 653 enum GNUNET_DB_QueryStatus qs; 654 655 qs = TALER_ARL_get_denomination_info (denom_pub, 656 &issue, 657 &h_denom_pub); 658 if (0 > qs) 659 { 660 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 661 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 662 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 663 "Hard database error trying to get denomination %s from database!\n", 664 TALER_B2S (denom_pub)); 665 pc->qs = qs; 666 return GNUNET_SYSERR; 667 } 668 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 669 { 670 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 671 "Failed to find denomination key for purse deposit `%s' in record %llu\n", 672 TALER_B2S (&deposit->purse_pub), 673 (unsigned long long) rowid); 674 qs = report_row_inconsistency ("purse-deposit", 675 rowid, 676 "denomination key not found"); 677 if (0 > qs) 678 { 679 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 680 pc->qs = qs; 681 return GNUNET_SYSERR; 682 } 683 return GNUNET_OK; 684 } 685 TALER_ARL_amount_subtract (&amount_minus_fee, 686 &deposit->amount, 687 &issue->fees.deposit); 688 } 689 690 if (GNUNET_OK != 691 TALER_wallet_purse_deposit_verify (base_url, 692 &deposit->purse_pub, 693 &deposit->amount, 694 &h_denom_pub, 695 &deposit->h_age_commitment, 696 &deposit->coin_pub, 697 &deposit->coin_sig)) 698 { 699 struct TALER_AUDITORDB_BadSigLosses bsl = { 700 .problem_row_id = rowid, 701 .operation = (char *) "purse-deposit", 702 .loss = deposit->amount, 703 .operation_specific_pub = deposit->coin_pub.eddsa_pub 704 }; 705 enum GNUNET_DB_QueryStatus qs; 706 707 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 708 "Failed to verify purse deposit signature on `%s' in record %llu\n", 709 TALER_B2S (&deposit->purse_pub), 710 (unsigned long long) rowid); 711 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 712 TALER_ARL_adb, 713 &bsl); 714 if (qs < 0) 715 { 716 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 717 pc->qs = qs; 718 return GNUNET_SYSERR; 719 } 720 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 721 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 722 &deposit->amount); 723 return GNUNET_OK; 724 } 725 726 { 727 struct PurseSummary *ps; 728 729 ps = setup_purse (pc, 730 &deposit->purse_pub); 731 if (NULL == ps) 732 { 733 enum GNUNET_DB_QueryStatus qs; 734 735 if (0 > pc->qs) 736 { 737 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 738 return GNUNET_SYSERR; 739 } 740 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 741 qs = report_row_inconsistency ("purse_deposit", 742 rowid, 743 "purse not found"); 744 if (0 > qs) 745 { 746 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 747 pc->qs = qs; 748 return GNUNET_SYSERR; 749 } 750 return GNUNET_OK; 751 } 752 TALER_ARL_amount_add (&ps->balance, 753 &ps->balance, 754 &amount_minus_fee); 755 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance), 756 &TALER_ARL_USE_AB (purse_global_balance), 757 &amount_minus_fee); 758 } 759 return GNUNET_OK; 760 } 761 762 763 /** 764 * Function called with details about purse merges that have been made, with 765 * the goal of auditing the purse merge execution. 766 * 767 * @param cls closure 768 * @param rowid unique serial ID for the deposit in our DB 769 * @param partner_base_url where is the reserve, NULL for this exchange 770 * @param amount total amount expected in the purse 771 * @param balance current balance in the purse 772 * @param flags purse flags 773 * @param merge_pub merge capability key 774 * @param reserve_pub reserve the merge affects 775 * @param merge_sig signature affirming the merge 776 * @param purse_pub purse key 777 * @param merge_timestamp when did the merge happen 778 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 779 */ 780 static enum GNUNET_GenericReturnValue 781 handle_purse_merged ( 782 void *cls, 783 uint64_t rowid, 784 const char *partner_base_url, 785 const struct TALER_Amount *amount, 786 const struct TALER_Amount *balance, 787 enum TALER_WalletAccountMergeFlags flags, 788 const struct TALER_PurseMergePublicKeyP *merge_pub, 789 const struct TALER_ReservePublicKeyP *reserve_pub, 790 const struct TALER_PurseMergeSignatureP *merge_sig, 791 const struct TALER_PurseContractPublicKeyP *purse_pub, 792 struct GNUNET_TIME_Timestamp merge_timestamp) 793 { 794 struct PurseContext *pc = cls; 795 struct PurseSummary *ps; 796 enum GNUNET_DB_QueryStatus qs; 797 798 /* should be monotonically increasing */ 799 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 800 "Handling purse merged `%s'\n", 801 TALER_B2S (purse_pub)); 802 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id)); 803 TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1; 804 805 { 806 struct TALER_NormalizedPayto reserve_url; 807 808 reserve_url 809 = TALER_reserve_make_payto (NULL == partner_base_url 810 ? TALER_ARL_exchange_url 811 : partner_base_url, 812 reserve_pub); 813 if (GNUNET_OK != 814 TALER_wallet_purse_merge_verify (reserve_url, 815 merge_timestamp, 816 purse_pub, 817 merge_pub, 818 merge_sig)) 819 { 820 struct TALER_AUDITORDB_BadSigLosses bsl = { 821 .problem_row_id = rowid, 822 .operation = (char *) "merge-purse", 823 .loss = *amount, 824 .operation_specific_pub = merge_pub->eddsa_pub 825 }; 826 827 GNUNET_free (reserve_url.normalized_payto); 828 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 829 TALER_ARL_adb, 830 &bsl); 831 if (qs < 0) 832 { 833 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 834 pc->qs = qs; 835 return GNUNET_SYSERR; 836 } 837 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 838 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 839 amount); 840 return GNUNET_OK; 841 } 842 GNUNET_free (reserve_url.normalized_payto); 843 } 844 845 ps = setup_purse (pc, 846 purse_pub); 847 if (NULL == ps) 848 { 849 if (pc->qs < 0) 850 { 851 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 852 return GNUNET_SYSERR; 853 } 854 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 855 qs = report_row_inconsistency ("purse-merge", 856 rowid, 857 "purse not found"); 858 if (qs < 0) 859 { 860 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 861 pc->qs = qs; 862 return GNUNET_SYSERR; 863 } 864 return GNUNET_OK; 865 } 866 GNUNET_break (0 == 867 GNUNET_TIME_timestamp_cmp (merge_timestamp, 868 ==, 869 ps->merge_timestamp)); 870 TALER_ARL_amount_add (&ps->balance, 871 &ps->balance, 872 amount); 873 return GNUNET_OK; 874 } 875 876 877 /** 878 * Function called with details about account merge requests that have been 879 * made, with the goal of auditing the account merge execution. 880 * 881 * @param cls closure 882 * @param rowid unique serial ID for the deposit in our DB 883 * @param reserve_pub reserve affected by the merge 884 * @param purse_pub purse being merged 885 * @param h_contract_terms hash over contract of the purse 886 * @param purse_expiration when would the purse expire 887 * @param amount total amount in the purse 888 * @param min_age minimum age of all coins deposited into the purse 889 * @param flags how was the purse created 890 * @param purse_fee if a purse fee was paid, how high is it 891 * @param merge_timestamp when was the merge approved 892 * @param reserve_sig signature by reserve approving the merge 893 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 894 */ 895 static enum GNUNET_GenericReturnValue 896 handle_account_merged ( 897 void *cls, 898 uint64_t rowid, 899 const struct TALER_ReservePublicKeyP *reserve_pub, 900 const struct TALER_PurseContractPublicKeyP *purse_pub, 901 const struct TALER_PrivateContractHashP *h_contract_terms, 902 struct GNUNET_TIME_Timestamp purse_expiration, 903 const struct TALER_Amount *amount, 904 uint32_t min_age, 905 enum TALER_WalletAccountMergeFlags flags, 906 const struct TALER_Amount *purse_fee, 907 struct GNUNET_TIME_Timestamp merge_timestamp, 908 const struct TALER_ReserveSignatureP *reserve_sig) 909 { 910 struct PurseContext *pc = cls; 911 struct PurseSummary *ps; 912 enum GNUNET_DB_QueryStatus qs; 913 914 /* should be monotonically increasing */ 915 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 916 "Handling account merge on purse `%s'\n", 917 TALER_B2S (purse_pub)); 918 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id)); 919 TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1; 920 if (GNUNET_OK != 921 TALER_wallet_account_merge_verify (merge_timestamp, 922 purse_pub, 923 purse_expiration, 924 h_contract_terms, 925 amount, 926 purse_fee, 927 min_age, 928 flags, 929 reserve_pub, 930 reserve_sig)) 931 { 932 struct TALER_AUDITORDB_BadSigLosses bsl = { 933 .problem_row_id = rowid, 934 .operation = (char *) "account-merge", 935 .loss = *purse_fee, 936 .operation_specific_pub = reserve_pub->eddsa_pub 937 }; 938 939 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 940 TALER_ARL_adb, 941 &bsl); 942 if (qs < 0) 943 { 944 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 945 pc->qs = qs; 946 return GNUNET_SYSERR; 947 } 948 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 949 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 950 purse_fee); 951 return GNUNET_OK; 952 } 953 ps = setup_purse (pc, 954 purse_pub); 955 if (NULL == ps) 956 { 957 if (0 > pc->qs) 958 { 959 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 960 return GNUNET_SYSERR; 961 } 962 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 963 qs = report_row_inconsistency ("account-merge", 964 rowid, 965 "purse not found"); 966 if (0 > qs) 967 { 968 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 969 pc->qs = qs; 970 return GNUNET_SYSERR; 971 } 972 return GNUNET_OK; 973 } 974 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance), 975 &TALER_ARL_USE_AB (purse_global_balance), 976 purse_fee); 977 TALER_ARL_amount_add (&ps->balance, 978 &ps->balance, 979 purse_fee); 980 return GNUNET_OK; 981 } 982 983 984 /** 985 * Function called with details about purse decisions that have been made. 986 * 987 * @param cls closure 988 * @param rowid unique serial ID for the deposit in our DB 989 * @param purse_pub which purse was the decision made on 990 * @param refunded true if decision was to refund 991 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 992 */ 993 static enum GNUNET_GenericReturnValue 994 handle_purse_decision ( 995 void *cls, 996 uint64_t rowid, 997 const struct TALER_PurseContractPublicKeyP *purse_pub, 998 bool refunded) 999 { 1000 struct PurseContext *pc = cls; 1001 struct PurseSummary *ps; 1002 struct GNUNET_HashCode key; 1003 enum GNUNET_DB_QueryStatus qs; 1004 struct TALER_Amount purse_fee; 1005 struct TALER_Amount balance_without_purse_fee; 1006 1007 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_decision_serial_id)); 1008 TALER_ARL_USE_PP (purse_decision_serial_id) = rowid + 1; 1009 ps = setup_purse (pc, 1010 purse_pub); 1011 if (NULL == ps) 1012 { 1013 if (0 > pc->qs) 1014 { 1015 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 1016 return GNUNET_SYSERR; 1017 } 1018 qs = report_row_inconsistency ("purse-decision", 1019 rowid, 1020 "purse not found"); 1021 if (0 > qs) 1022 { 1023 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1024 pc->qs = qs; 1025 return GNUNET_SYSERR; 1026 } 1027 return GNUNET_OK; 1028 } 1029 qs = get_purse_fee (ps->creation_date, 1030 &purse_fee); 1031 if (0 > qs) 1032 { 1033 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1034 pc->qs = qs; 1035 return GNUNET_SYSERR; 1036 } 1037 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1038 return GNUNET_OK; /* already reported */ 1039 if (0 > 1040 TALER_amount_subtract (&balance_without_purse_fee, 1041 &ps->balance, 1042 &purse_fee)) 1043 { 1044 qs = report_row_inconsistency ("purse-request", 1045 rowid, 1046 "purse fee higher than balance"); 1047 if (0 > qs) 1048 { 1049 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1050 pc->qs = qs; 1051 return GNUNET_SYSERR; 1052 } 1053 GNUNET_assert (GNUNET_OK == 1054 TALER_amount_set_zero (TALER_ARL_currency, 1055 &balance_without_purse_fee)); 1056 } 1057 1058 if (refunded) 1059 { 1060 if (-1 != TALER_amount_cmp (&balance_without_purse_fee, 1061 &ps->total_value)) 1062 { 1063 qs = report_amount_arithmetic_inconsistency ("purse-decision: refund", 1064 rowid, 1065 &balance_without_purse_fee, 1066 &ps->total_value, 1067 0); 1068 if (0 > qs) 1069 { 1070 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1071 pc->qs = qs; 1072 return GNUNET_SYSERR; 1073 } 1074 } 1075 if ( (internal_checks) && 1076 (! ps->purse_refunded) ) 1077 { 1078 qs = report_row_inconsistency ( 1079 "purse-decision", 1080 rowid, 1081 "purse not marked as refunded (internal check)"); 1082 if (qs < 0) 1083 { 1084 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1085 pc->qs = qs; 1086 return GNUNET_SYSERR; 1087 } 1088 } 1089 } 1090 else 1091 { 1092 if (-1 == TALER_amount_cmp (&balance_without_purse_fee, 1093 &ps->total_value)) 1094 { 1095 qs = report_amount_arithmetic_inconsistency ("purse-decision: merge", 1096 rowid, 1097 &ps->total_value, 1098 &balance_without_purse_fee, 1099 0); 1100 if (0 > qs) 1101 { 1102 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1103 pc->qs = qs; 1104 return GNUNET_SYSERR; 1105 } 1106 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1107 purse_total_balance_insufficient_loss), 1108 &TALER_ARL_USE_AB ( 1109 purse_total_balance_insufficient_loss), 1110 &ps->total_value); 1111 } 1112 } 1113 1114 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1115 "Deleting purse with decision `%s'\n", 1116 TALER_B2S (&ps->purse_pub)); 1117 qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb, 1118 purse_pub); 1119 if (qs < 0) 1120 { 1121 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1122 pc->qs = qs; 1123 return GNUNET_SYSERR; 1124 } 1125 GNUNET_CRYPTO_hash (purse_pub, 1126 sizeof (*purse_pub), 1127 &key); 1128 GNUNET_assert (GNUNET_YES == 1129 GNUNET_CONTAINER_multihashmap_remove (pc->purses, 1130 &key, 1131 ps)); 1132 GNUNET_free (ps); 1133 return GNUNET_OK; 1134 } 1135 1136 1137 /** 1138 * Function called on explicitly deleted purses. 1139 * 1140 * @param cls closure 1141 * @param deletion_serial_id row ID with the deletion data 1142 * @param purse_pub public key of the purse 1143 * @param purse_sig signature affirming deletion of the purse 1144 * @return #GNUNET_OK to continue to iterate 1145 */ 1146 static enum GNUNET_GenericReturnValue 1147 handle_purse_deletion ( 1148 void *cls, 1149 uint64_t deletion_serial_id, 1150 const struct TALER_PurseContractPublicKeyP *purse_pub, 1151 const struct TALER_PurseContractSignatureP *purse_sig) 1152 { 1153 struct PurseContext *pc = cls; 1154 struct PurseSummary *ps; 1155 1156 ps = setup_purse (pc, 1157 purse_pub); 1158 if (NULL == ps) 1159 { 1160 GNUNET_break (0); 1161 return GNUNET_SYSERR; 1162 } 1163 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1164 "Handling purse `%s' deletion\n", 1165 TALER_B2S (purse_pub)); 1166 if (GNUNET_OK != 1167 TALER_wallet_purse_delete_verify (purse_pub, 1168 purse_sig)) 1169 { 1170 enum GNUNET_DB_QueryStatus qs; 1171 1172 qs = report_row_inconsistency ( 1173 "purse-delete", 1174 deletion_serial_id, 1175 "purse deletion signature invalid"); 1176 if (qs < 0) 1177 { 1178 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1179 pc->qs = qs; 1180 return GNUNET_SYSERR; 1181 } 1182 } 1183 else 1184 { 1185 if ( (internal_checks) && 1186 (! ps->purse_deleted) ) 1187 { 1188 enum GNUNET_DB_QueryStatus qs; 1189 1190 qs = report_row_inconsistency ( 1191 "purse-delete", 1192 deletion_serial_id, 1193 "purse not marked as deleted (internal check)"); 1194 if (qs < 0) 1195 { 1196 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1197 pc->qs = qs; 1198 return GNUNET_SYSERR; 1199 } 1200 } 1201 } 1202 return GNUNET_OK; 1203 } 1204 1205 1206 /** 1207 * Function called on expired purses. 1208 * 1209 * @param cls closure 1210 * @param purse_pub public key of the purse 1211 * @param balance amount of money in the purse 1212 * @param expiration_date when did the purse expire? 1213 * @return #GNUNET_OK to continue to iterate 1214 */ 1215 static enum GNUNET_GenericReturnValue 1216 handle_purse_expired ( 1217 void *cls, 1218 const struct TALER_PurseContractPublicKeyP *purse_pub, 1219 const struct TALER_Amount *balance, 1220 struct GNUNET_TIME_Timestamp expiration_date) 1221 { 1222 struct PurseContext *pc = cls; 1223 enum GNUNET_DB_QueryStatus qs; 1224 struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = { 1225 .amount = *balance, 1226 .expiration_date = expiration_date.abs_time, 1227 .purse_pub = purse_pub->eddsa_pub 1228 }; 1229 1230 qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies ( 1231 TALER_ARL_adb, 1232 &pnci); 1233 if (qs < 0) 1234 { 1235 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1236 pc->qs = qs; 1237 return GNUNET_SYSERR; 1238 } 1239 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1240 "Handling purse expiration `%s'\n", 1241 TALER_B2S (purse_pub)); 1242 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions), 1243 &TALER_ARL_USE_AB (purse_total_delayed_decisions), 1244 balance); 1245 return GNUNET_OK; 1246 } 1247 1248 1249 /** 1250 * Check that the purse summary matches what the exchange database 1251 * thinks about the purse, and update our own state of the purse. 1252 * 1253 * Remove all purses that we are happy with from the DB. 1254 * 1255 * @param cls our `struct PurseContext` 1256 * @param key hash of the purse public key 1257 * @param value a `struct PurseSummary` 1258 * @return #GNUNET_OK to process more entries 1259 */ 1260 static enum GNUNET_GenericReturnValue 1261 verify_purse_balance (void *cls, 1262 const struct GNUNET_HashCode *key, 1263 void *value) 1264 { 1265 struct PurseContext *pc = cls; 1266 struct PurseSummary *ps = value; 1267 enum GNUNET_DB_QueryStatus qs; 1268 1269 if (internal_checks) 1270 { 1271 struct TALER_Amount pf; 1272 struct TALER_Amount balance_without_purse_fee; 1273 1274 /* subtract purse fee from ps->balance to get actual balance we expect, as 1275 we track the balance including purse fee, while the exchange subtracts 1276 the purse fee early on. */ 1277 qs = get_purse_fee (ps->creation_date, 1278 &pf); 1279 if (qs < 0) 1280 { 1281 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1282 pc->qs = qs; 1283 return GNUNET_SYSERR; 1284 } 1285 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1286 return GNUNET_OK; /* error already reported */ 1287 if (0 > 1288 TALER_amount_subtract (&balance_without_purse_fee, 1289 &ps->balance, 1290 &pf)) 1291 { 1292 qs = report_row_inconsistency ("purse", 1293 0, 1294 "purse fee higher than balance"); 1295 if (qs < 0) 1296 { 1297 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1298 pc->qs = qs; 1299 return GNUNET_SYSERR; 1300 } 1301 GNUNET_assert (GNUNET_OK == 1302 TALER_amount_set_zero (TALER_ARL_currency, 1303 &balance_without_purse_fee)); 1304 } 1305 1306 if (0 != TALER_amount_cmp (&ps->exchange_balance, 1307 &balance_without_purse_fee)) 1308 { 1309 qs = report_amount_arithmetic_inconsistency ("purse-balance", 1310 0, 1311 &ps->exchange_balance, 1312 &balance_without_purse_fee, 1313 0); 1314 if (qs < 0) 1315 { 1316 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1317 pc->qs = qs; 1318 return GNUNET_SYSERR; 1319 } 1320 } 1321 } 1322 1323 if (ps->had_pi) 1324 { 1325 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1326 "Updating purse `%s'\n", 1327 TALER_B2S (&ps->purse_pub)); 1328 qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb, 1329 &ps->purse_pub, 1330 &ps->balance); 1331 } 1332 else 1333 { 1334 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1335 "Inserting purse `%s'\n", 1336 TALER_B2S (&ps->purse_pub)); 1337 qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb, 1338 &ps->purse_pub, 1339 &ps->balance, 1340 ps->expiration_date); 1341 ps->had_pi = true; 1342 } 1343 if (qs < 0) 1344 { 1345 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1346 pc->qs = qs; 1347 return GNUNET_SYSERR; 1348 } 1349 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 1350 return GNUNET_OK; 1351 } 1352 1353 1354 /** 1355 * Clear memory from the purses hash map. 1356 * 1357 * @param cls our `struct PurseContext` 1358 * @param key hash of the purse public key 1359 * @param value a `struct PurseSummary` 1360 * @return #GNUNET_OK to process more entries 1361 */ 1362 static enum GNUNET_GenericReturnValue 1363 release_purse_balance (void *cls, 1364 const struct GNUNET_HashCode *key, 1365 void *value) 1366 { 1367 struct PurseContext *pc = cls; 1368 struct PurseSummary *ps = value; 1369 1370 GNUNET_assert (GNUNET_YES == 1371 GNUNET_CONTAINER_multihashmap_remove (pc->purses, 1372 key, 1373 ps)); 1374 GNUNET_free (ps); 1375 return GNUNET_OK; 1376 } 1377 1378 1379 /** 1380 * Analyze purses for being well-formed. 1381 * 1382 * @param cls NULL 1383 * @return transaction status code 1384 */ 1385 static enum GNUNET_DB_QueryStatus 1386 analyze_purses (void *cls) 1387 { 1388 struct PurseContext pc; 1389 enum GNUNET_DB_QueryStatus qs; 1390 bool had_pp; 1391 bool had_bal; 1392 1393 (void) cls; 1394 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1395 "Analyzing purses\n"); 1396 qs = TALER_AUDITORDB_get_auditor_progress ( 1397 TALER_ARL_adb, 1398 TALER_ARL_GET_PP (purse_account_merge_serial_id), 1399 TALER_ARL_GET_PP (purse_decision_serial_id), 1400 TALER_ARL_GET_PP (purse_deletion_serial_id), 1401 TALER_ARL_GET_PP (purse_deposits_serial_id), 1402 TALER_ARL_GET_PP (purse_merges_serial_id), 1403 TALER_ARL_GET_PP (purse_request_serial_id), 1404 TALER_ARL_GET_PP (purse_open_counter), 1405 NULL); 1406 if (0 > qs) 1407 { 1408 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1409 return qs; 1410 } 1411 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 1412 if (had_pp) 1413 { 1414 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1415 "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n", 1416 (unsigned long long) TALER_ARL_USE_PP ( 1417 purse_open_counter), 1418 (unsigned long long) TALER_ARL_USE_PP ( 1419 purse_request_serial_id), 1420 (unsigned long long) TALER_ARL_USE_PP ( 1421 purse_decision_serial_id), 1422 (unsigned long long) TALER_ARL_USE_PP ( 1423 purse_deletion_serial_id), 1424 (unsigned long long) TALER_ARL_USE_PP ( 1425 purse_merges_serial_id), 1426 (unsigned long long) TALER_ARL_USE_PP ( 1427 purse_deposits_serial_id), 1428 (unsigned long long) TALER_ARL_USE_PP ( 1429 purse_account_merge_serial_id)); 1430 } 1431 else 1432 { 1433 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1434 "First analysis using this auditor, starting audit from scratch\n"); 1435 } 1436 pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1437 qs = TALER_AUDITORDB_get_balance ( 1438 TALER_ARL_adb, 1439 TALER_ARL_GET_AB (purse_global_balance), 1440 TALER_ARL_GET_AB (purse_total_balance_insufficient_loss), 1441 TALER_ARL_GET_AB (purse_total_delayed_decisions), 1442 TALER_ARL_GET_AB (purse_total_balance_purse_not_closed), 1443 TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus), 1444 TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus), 1445 TALER_ARL_GET_AB (purse_total_bad_sig_loss), 1446 NULL); 1447 if (qs < 0) 1448 { 1449 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1450 return qs; 1451 } 1452 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 1453 pc.purses = GNUNET_CONTAINER_multihashmap_create (512, 1454 GNUNET_NO); 1455 1456 qs = TALER_TALER_EXCHANGEDB_select_purse_requests_above_serial_id ( 1457 TALER_ARL_edb, 1458 TALER_ARL_USE_PP (purse_request_serial_id), 1459 &handle_purse_requested, 1460 &pc); 1461 if (qs < 0) 1462 { 1463 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1464 return qs; 1465 } 1466 if (pc.qs < 0) 1467 { 1468 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1469 return pc.qs; 1470 } 1471 qs = TALER_TALER_TALER_EXCHANGEDB_select_purse_merges_above_serial_id ( 1472 TALER_ARL_edb, 1473 TALER_ARL_USE_PP (purse_merges_serial_id), 1474 &handle_purse_merged, 1475 &pc); 1476 if (qs < 0) 1477 { 1478 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1479 return qs; 1480 } 1481 if (pc.qs < 0) 1482 { 1483 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1484 return pc.qs; 1485 } 1486 1487 qs = TALER_TALER_EXCHANGEDB_select_purse_deposits_above_serial_id ( 1488 TALER_ARL_edb, 1489 TALER_ARL_USE_PP (purse_deposits_serial_id), 1490 &handle_purse_deposits, 1491 &pc); 1492 if (qs < 0) 1493 { 1494 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1495 return qs; 1496 } 1497 if (pc.qs < 0) 1498 { 1499 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1500 return pc.qs; 1501 } 1502 1503 /* Charge purse fee! */ 1504 qs = TALER_EXCHANGEDB_select_account_merges_above_serial_id ( 1505 TALER_ARL_edb, 1506 TALER_ARL_USE_PP (purse_account_merge_serial_id), 1507 &handle_account_merged, 1508 &pc); 1509 if (qs < 0) 1510 { 1511 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1512 return qs; 1513 } 1514 if (pc.qs < 0) 1515 { 1516 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1517 return pc.qs; 1518 } 1519 1520 qs = TALER_EXCHANGEDB_select_all_purse_decisions_above_serial_id ( 1521 TALER_ARL_edb, 1522 TALER_ARL_USE_PP (purse_decision_serial_id), 1523 &handle_purse_decision, 1524 &pc); 1525 if (qs < 0) 1526 { 1527 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1528 return qs; 1529 } 1530 if (pc.qs < 0) 1531 { 1532 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1533 return pc.qs; 1534 } 1535 1536 qs = TALER_EXCHANGEDB_select_all_purse_deletions_above_serial_id ( 1537 TALER_ARL_edb, 1538 TALER_ARL_USE_PP (purse_deletion_serial_id), 1539 &handle_purse_deletion, 1540 &pc); 1541 if (qs < 0) 1542 { 1543 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1544 return qs; 1545 } 1546 if (pc.qs < 0) 1547 { 1548 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1549 return pc.qs; 1550 } 1551 1552 qs = TALER_AUDITORDB_select_purse_expired ( 1553 TALER_ARL_adb, 1554 &handle_purse_expired, 1555 &pc); 1556 if (qs < 0) 1557 { 1558 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1559 return qs; 1560 } 1561 if (pc.qs < 0) 1562 { 1563 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1564 return pc.qs; 1565 } 1566 1567 GNUNET_CONTAINER_multihashmap_iterate (pc.purses, 1568 &verify_purse_balance, 1569 &pc); 1570 GNUNET_CONTAINER_multihashmap_iterate (pc.purses, 1571 &release_purse_balance, 1572 &pc); 1573 GNUNET_break (0 == 1574 GNUNET_CONTAINER_multihashmap_size (pc.purses)); 1575 GNUNET_CONTAINER_multihashmap_destroy (pc.purses); 1576 if (pc.qs < 0) 1577 { 1578 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1579 return pc.qs; 1580 } 1581 if (had_bal) 1582 qs = TALER_AUDITORDB_update_balance ( 1583 TALER_ARL_adb, 1584 TALER_ARL_SET_AB (purse_global_balance), 1585 TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), 1586 TALER_ARL_SET_AB (purse_total_delayed_decisions), 1587 TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), 1588 TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), 1589 TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), 1590 TALER_ARL_SET_AB (purse_total_bad_sig_loss), 1591 NULL); 1592 else 1593 qs = TALER_AUDITORDB_insert_balance ( 1594 TALER_ARL_adb, 1595 TALER_ARL_SET_AB (purse_global_balance), 1596 TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), 1597 TALER_ARL_SET_AB (purse_total_delayed_decisions), 1598 TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), 1599 TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), 1600 TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), 1601 TALER_ARL_SET_AB (purse_total_bad_sig_loss), 1602 NULL); 1603 if (0 > qs) 1604 { 1605 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1606 "Failed to update auditor DB, not recording progress\n"); 1607 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1608 return qs; 1609 } 1610 if (had_pp) 1611 qs = TALER_AUDITORDB_update_auditor_progress ( 1612 TALER_ARL_adb, 1613 TALER_ARL_SET_PP (purse_account_merge_serial_id), 1614 TALER_ARL_SET_PP (purse_decision_serial_id), 1615 TALER_ARL_SET_PP (purse_deletion_serial_id), 1616 TALER_ARL_SET_PP (purse_deposits_serial_id), 1617 TALER_ARL_SET_PP (purse_merges_serial_id), 1618 TALER_ARL_SET_PP (purse_request_serial_id), 1619 TALER_ARL_SET_PP (purse_open_counter), 1620 NULL); 1621 else 1622 qs = TALER_AUDITORDB_insert_auditor_progress ( 1623 TALER_ARL_adb, 1624 TALER_ARL_SET_PP (purse_account_merge_serial_id), 1625 TALER_ARL_SET_PP (purse_decision_serial_id), 1626 TALER_ARL_SET_PP (purse_deletion_serial_id), 1627 TALER_ARL_SET_PP (purse_deposits_serial_id), 1628 TALER_ARL_SET_PP (purse_merges_serial_id), 1629 TALER_ARL_SET_PP (purse_request_serial_id), 1630 TALER_ARL_SET_PP (purse_open_counter), 1631 NULL); 1632 if (0 > qs) 1633 { 1634 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1635 "Failed to update auditor DB, not recording progress\n"); 1636 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1637 return qs; 1638 } 1639 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1640 "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n", 1641 (unsigned long long) TALER_ARL_USE_PP ( 1642 purse_request_serial_id), 1643 (unsigned long long) TALER_ARL_USE_PP ( 1644 purse_decision_serial_id), 1645 (unsigned long long) TALER_ARL_USE_PP ( 1646 purse_deletion_serial_id), 1647 (unsigned long long) TALER_ARL_USE_PP ( 1648 purse_merges_serial_id), 1649 (unsigned long long) TALER_ARL_USE_PP ( 1650 purse_deposits_serial_id), 1651 (unsigned long long) TALER_ARL_USE_PP ( 1652 purse_account_merge_serial_id)); 1653 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1654 } 1655 1656 1657 /** 1658 * Function called on events received from Postgres. 1659 * 1660 * @param cls closure, NULL 1661 * @param extra additional event data provided 1662 * @param extra_size number of bytes in @a extra 1663 */ 1664 static void 1665 db_notify (void *cls, 1666 const void *extra, 1667 size_t extra_size) 1668 { 1669 (void) cls; 1670 (void) extra; 1671 (void) extra_size; 1672 1673 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1674 "Received notification to wake purses\n"); 1675 if (GNUNET_OK != 1676 TALER_ARL_setup_sessions_and_run (&analyze_purses, 1677 NULL)) 1678 { 1679 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1680 "Audit failed\n"); 1681 GNUNET_SCHEDULER_shutdown (); 1682 global_ret = EXIT_FAILURE; 1683 return; 1684 } 1685 } 1686 1687 1688 /** 1689 * Function called on shutdown. 1690 */ 1691 static void 1692 do_shutdown (void *cls) 1693 { 1694 (void) cls; 1695 1696 if (NULL != eh) 1697 { 1698 TALER_AUDITORDB_event_listen_cancel (eh); 1699 eh = NULL; 1700 } 1701 TALER_ARL_done (); 1702 } 1703 1704 1705 /** 1706 * Main function that will be run. 1707 * 1708 * @param cls closure 1709 * @param args remaining command-line arguments 1710 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1711 * @param c configuration 1712 */ 1713 static void 1714 run (void *cls, 1715 char *const *args, 1716 const char *cfgfile, 1717 const struct GNUNET_CONFIGURATION_Handle *c) 1718 { 1719 (void) cls; 1720 (void) args; 1721 (void) cfgfile; 1722 1723 cfg = c; 1724 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1725 NULL); 1726 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1727 "Launching purses auditor\n"); 1728 if (GNUNET_OK != 1729 TALER_ARL_init (c)) 1730 { 1731 global_ret = EXIT_FAILURE; 1732 return; 1733 } 1734 if (test_mode != 1) 1735 { 1736 struct GNUNET_DB_EventHeaderP es = { 1737 .size = htons (sizeof (es)), 1738 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES) 1739 }; 1740 1741 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1742 "Running helper indefinitely\n"); 1743 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 1744 &es, 1745 GNUNET_TIME_UNIT_FOREVER_REL, 1746 &db_notify, 1747 NULL); 1748 } 1749 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1750 "Starting audit\n"); 1751 if (GNUNET_OK != 1752 TALER_ARL_setup_sessions_and_run (&analyze_purses, 1753 NULL)) 1754 { 1755 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1756 "Audit failed\n"); 1757 GNUNET_SCHEDULER_shutdown (); 1758 global_ret = EXIT_FAILURE; 1759 return; 1760 } 1761 } 1762 1763 1764 /** 1765 * The main function to check the database's handling of purses. 1766 * 1767 * @param argc number of arguments from the command line 1768 * @param argv command line arguments 1769 * @return 0 ok, 1 on error 1770 */ 1771 int 1772 main (int argc, 1773 char *const *argv) 1774 { 1775 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1776 GNUNET_GETOPT_option_flag ('i', 1777 "internal", 1778 "perform checks only applicable for exchange-internal audits", 1779 &internal_checks), 1780 GNUNET_GETOPT_option_flag ('t', 1781 "test", 1782 "run in test mode and exit when idle", 1783 &test_mode), 1784 GNUNET_GETOPT_option_timetravel ('T', 1785 "timetravel"), 1786 GNUNET_GETOPT_OPTION_END 1787 }; 1788 enum GNUNET_GenericReturnValue ret; 1789 1790 ret = GNUNET_PROGRAM_run ( 1791 TALER_AUDITOR_project_data (), 1792 argc, 1793 argv, 1794 "taler-helper-auditor-purses", 1795 gettext_noop ("Audit Taler exchange purse handling"), 1796 options, 1797 &run, 1798 NULL); 1799 if (GNUNET_SYSERR == ret) 1800 return EXIT_INVALIDARGUMENT; 1801 if (GNUNET_NO == ret) 1802 return EXIT_SUCCESS; 1803 return global_ret; 1804 } 1805 1806 1807 /* end of taler-helper-auditor-purses.c */