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