taler-helper-auditor-purses.c (56392B)
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 return ps; 506 } 507 508 509 /** 510 * Function called on purse requests. 511 * 512 * @param cls closure 513 * @param rowid which row in the database was the request stored in 514 * @param purse_pub public key of the purse 515 * @param merge_pub public key representing the merge capability 516 * @param purse_creation when was the purse created 517 * @param purse_expiration when would an unmerged purse expire 518 * @param h_contract_terms contract associated with the purse 519 * @param age_limit the age limit for deposits into the purse 520 * @param target_amount amount to be put into the purse 521 * @param purse_sig signature of the purse over the initialization data 522 * @return #GNUNET_OK to continue to iterate 523 */ 524 static enum GNUNET_GenericReturnValue 525 handle_purse_requested ( 526 void *cls, 527 uint64_t rowid, 528 const struct TALER_PurseContractPublicKeyP *purse_pub, 529 const struct TALER_PurseMergePublicKeyP *merge_pub, 530 struct GNUNET_TIME_Timestamp purse_creation, 531 struct GNUNET_TIME_Timestamp purse_expiration, 532 const struct TALER_PrivateContractHashP *h_contract_terms, 533 uint32_t age_limit, 534 const struct TALER_Amount *target_amount, 535 const struct TALER_PurseContractSignatureP *purse_sig) 536 { 537 struct PurseContext *pc = cls; 538 struct PurseSummary *ps; 539 struct GNUNET_HashCode key; 540 541 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 542 "Handling purse request `%s'\n", 543 TALER_B2S (purse_pub)); 544 TALER_ARL_USE_PP (purse_request_serial_id) = rowid; 545 if (GNUNET_OK != 546 TALER_wallet_purse_create_verify (purse_expiration, 547 h_contract_terms, 548 merge_pub, 549 age_limit, 550 target_amount, 551 purse_pub, 552 purse_sig)) 553 { 554 struct TALER_AUDITORDB_BadSigLosses bsl = { 555 .problem_row_id = rowid, 556 .operation = (char *) "purse-request", 557 .loss = *target_amount, 558 .operation_specific_pub = purse_pub->eddsa_pub 559 }; 560 enum GNUNET_DB_QueryStatus qs; 561 562 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 563 TALER_ARL_adb, 564 &bsl); 565 if (qs < 0) 566 { 567 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 568 pc->qs = qs; 569 return GNUNET_SYSERR; 570 } 571 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 572 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 573 target_amount); 574 } 575 GNUNET_CRYPTO_hash (purse_pub, 576 sizeof (*purse_pub), 577 &key); 578 ps = GNUNET_new (struct PurseSummary); 579 ps->purse_pub = *purse_pub; 580 GNUNET_assert (GNUNET_OK == 581 TALER_amount_set_zero (TALER_ARL_currency, 582 &ps->balance)); 583 ps->creation_date = purse_creation; 584 ps->expiration_date = purse_expiration; 585 ps->total_value = *target_amount; 586 ps->h_contract_terms = *h_contract_terms; 587 { 588 enum GNUNET_DB_QueryStatus qs; 589 590 qs = load_auditor_purse_summary (ps); 591 if (0 > qs) 592 { 593 GNUNET_free (ps); 594 pc->qs = qs; 595 return GNUNET_SYSERR; 596 } 597 } 598 GNUNET_assert (GNUNET_OK == 599 GNUNET_CONTAINER_multihashmap_put (pc->purses, 600 &key, 601 ps, 602 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); 603 return GNUNET_OK; 604 } 605 606 607 /** 608 * Function called with details about purse deposits that have been made, with 609 * the goal of auditing the deposit's execution. 610 * 611 * @param cls closure 612 * @param rowid unique serial ID for the deposit in our DB 613 * @param deposit deposit details 614 * @param reserve_pub which reserve is the purse merged into, NULL if unknown 615 * @param flags purse flags 616 * @param auditor_balance purse balance (according to the 617 * auditor during auditing) 618 * @param purse_total target amount the purse should reach 619 * @param denom_pub denomination public key of @a coin_pub 620 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 621 */ 622 static enum GNUNET_GenericReturnValue 623 handle_purse_deposits ( 624 void *cls, 625 uint64_t rowid, 626 const struct TALER_EXCHANGEDB_PurseDeposit *deposit, 627 const struct TALER_ReservePublicKeyP *reserve_pub, 628 enum TALER_WalletAccountMergeFlags flags, 629 const struct TALER_Amount *auditor_balance, 630 const struct TALER_Amount *purse_total, 631 const struct TALER_DenominationPublicKey *denom_pub) 632 { 633 struct PurseContext *pc = cls; 634 struct TALER_Amount amount_minus_fee; 635 const char *base_url 636 = (NULL == deposit->exchange_base_url) 637 ? TALER_ARL_exchange_url 638 : deposit->exchange_base_url; 639 struct TALER_DenominationHashP h_denom_pub; 640 641 /* should be monotonically increasing */ 642 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 643 "Handling purse deposit `%s'\n", 644 TALER_B2S (&deposit->purse_pub)); 645 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_deposits_serial_id)); 646 TALER_ARL_USE_PP (purse_deposits_serial_id) = rowid + 1; 647 648 { 649 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue; 650 enum GNUNET_DB_QueryStatus qs; 651 652 qs = TALER_ARL_get_denomination_info (denom_pub, 653 &issue, 654 &h_denom_pub); 655 if (0 > qs) 656 { 657 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 658 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 659 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 660 "Hard database error trying to get denomination %s from database!\n", 661 TALER_B2S (denom_pub)); 662 pc->qs = qs; 663 return GNUNET_SYSERR; 664 } 665 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 666 { 667 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 668 "Failed to find denomination key for purse deposit `%s' in record %llu\n", 669 TALER_B2S (&deposit->purse_pub), 670 (unsigned long long) rowid); 671 qs = report_row_inconsistency ("purse-deposit", 672 rowid, 673 "denomination key not found"); 674 if (0 > qs) 675 { 676 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 677 pc->qs = qs; 678 return GNUNET_SYSERR; 679 } 680 return GNUNET_OK; 681 } 682 TALER_ARL_amount_subtract (&amount_minus_fee, 683 &deposit->amount, 684 &issue->fees.deposit); 685 } 686 687 if (GNUNET_OK != 688 TALER_wallet_purse_deposit_verify (base_url, 689 &deposit->purse_pub, 690 &deposit->amount, 691 &h_denom_pub, 692 &deposit->h_age_commitment, 693 &deposit->coin_pub, 694 &deposit->coin_sig)) 695 { 696 struct TALER_AUDITORDB_BadSigLosses bsl = { 697 .problem_row_id = rowid, 698 .operation = (char *) "purse-deposit", 699 .loss = deposit->amount, 700 .operation_specific_pub = deposit->coin_pub.eddsa_pub 701 }; 702 enum GNUNET_DB_QueryStatus qs; 703 704 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 705 "Failed to verify purse deposit signature on `%s' in record %llu\n", 706 TALER_B2S (&deposit->purse_pub), 707 (unsigned long long) rowid); 708 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 709 TALER_ARL_adb, 710 &bsl); 711 if (qs < 0) 712 { 713 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 714 pc->qs = qs; 715 return GNUNET_SYSERR; 716 } 717 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 718 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 719 &deposit->amount); 720 return GNUNET_OK; 721 } 722 723 { 724 struct PurseSummary *ps; 725 726 ps = setup_purse (pc, 727 &deposit->purse_pub); 728 if (NULL == ps) 729 { 730 enum GNUNET_DB_QueryStatus qs; 731 732 if (0 > pc->qs) 733 { 734 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 735 return GNUNET_SYSERR; 736 } 737 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 738 qs = report_row_inconsistency ("purse_deposit", 739 rowid, 740 "purse not found"); 741 if (0 > qs) 742 { 743 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 744 pc->qs = qs; 745 return GNUNET_SYSERR; 746 } 747 return GNUNET_OK; 748 } 749 TALER_ARL_amount_add (&ps->balance, 750 &ps->balance, 751 &amount_minus_fee); 752 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance), 753 &TALER_ARL_USE_AB (purse_global_balance), 754 &amount_minus_fee); 755 } 756 return GNUNET_OK; 757 } 758 759 760 /** 761 * Function called with details about purse merges that have been made, with 762 * the goal of auditing the purse merge execution. 763 * 764 * @param cls closure 765 * @param rowid unique serial ID for the deposit in our DB 766 * @param partner_base_url where is the reserve, NULL for this exchange 767 * @param amount total amount expected in the purse 768 * @param balance current balance in the purse 769 * @param flags purse flags 770 * @param merge_pub merge capability key 771 * @param reserve_pub reserve the merge affects 772 * @param merge_sig signature affirming the merge 773 * @param purse_pub purse key 774 * @param merge_timestamp when did the merge happen 775 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 776 */ 777 static enum GNUNET_GenericReturnValue 778 handle_purse_merged ( 779 void *cls, 780 uint64_t rowid, 781 const char *partner_base_url, 782 const struct TALER_Amount *amount, 783 const struct TALER_Amount *balance, 784 enum TALER_WalletAccountMergeFlags flags, 785 const struct TALER_PurseMergePublicKeyP *merge_pub, 786 const struct TALER_ReservePublicKeyP *reserve_pub, 787 const struct TALER_PurseMergeSignatureP *merge_sig, 788 const struct TALER_PurseContractPublicKeyP *purse_pub, 789 struct GNUNET_TIME_Timestamp merge_timestamp) 790 { 791 struct PurseContext *pc = cls; 792 struct PurseSummary *ps; 793 enum GNUNET_DB_QueryStatus qs; 794 795 /* should be monotonically increasing */ 796 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 797 "Handling purse merged `%s'\n", 798 TALER_B2S (purse_pub)); 799 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_merges_serial_id)); 800 TALER_ARL_USE_PP (purse_merges_serial_id) = rowid + 1; 801 802 { 803 struct TALER_NormalizedPayto reserve_url; 804 805 reserve_url 806 = TALER_reserve_make_payto (NULL == partner_base_url 807 ? TALER_ARL_exchange_url 808 : partner_base_url, 809 reserve_pub); 810 if (GNUNET_OK != 811 TALER_wallet_purse_merge_verify (reserve_url, 812 merge_timestamp, 813 purse_pub, 814 merge_pub, 815 merge_sig)) 816 { 817 struct TALER_AUDITORDB_BadSigLosses bsl = { 818 .problem_row_id = rowid, 819 .operation = (char *) "merge-purse", 820 .loss = *amount, 821 .operation_specific_pub = merge_pub->eddsa_pub 822 }; 823 824 GNUNET_free (reserve_url.normalized_payto); 825 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 826 TALER_ARL_adb, 827 &bsl); 828 if (qs < 0) 829 { 830 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 831 pc->qs = qs; 832 return GNUNET_SYSERR; 833 } 834 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 835 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 836 amount); 837 return GNUNET_OK; 838 } 839 GNUNET_free (reserve_url.normalized_payto); 840 } 841 842 ps = setup_purse (pc, 843 purse_pub); 844 if (NULL == ps) 845 { 846 if (0 < pc->qs) 847 { 848 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 849 return GNUNET_SYSERR; 850 } 851 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 852 qs = report_row_inconsistency ("purse-merge", 853 rowid, 854 "purse not found"); 855 if (qs < 0) 856 { 857 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 858 pc->qs = qs; 859 return GNUNET_SYSERR; 860 } 861 return GNUNET_OK; 862 } 863 GNUNET_break (0 == 864 GNUNET_TIME_timestamp_cmp (merge_timestamp, 865 ==, 866 ps->merge_timestamp)); 867 TALER_ARL_amount_add (&ps->balance, 868 &ps->balance, 869 amount); 870 return GNUNET_OK; 871 } 872 873 874 /** 875 * Function called with details about account merge requests that have been 876 * made, with the goal of auditing the account merge execution. 877 * 878 * @param cls closure 879 * @param rowid unique serial ID for the deposit in our DB 880 * @param reserve_pub reserve affected by the merge 881 * @param purse_pub purse being merged 882 * @param h_contract_terms hash over contract of the purse 883 * @param purse_expiration when would the purse expire 884 * @param amount total amount in the purse 885 * @param min_age minimum age of all coins deposited into the purse 886 * @param flags how was the purse created 887 * @param purse_fee if a purse fee was paid, how high is it 888 * @param merge_timestamp when was the merge approved 889 * @param reserve_sig signature by reserve approving the merge 890 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 891 */ 892 static enum GNUNET_GenericReturnValue 893 handle_account_merged ( 894 void *cls, 895 uint64_t rowid, 896 const struct TALER_ReservePublicKeyP *reserve_pub, 897 const struct TALER_PurseContractPublicKeyP *purse_pub, 898 const struct TALER_PrivateContractHashP *h_contract_terms, 899 struct GNUNET_TIME_Timestamp purse_expiration, 900 const struct TALER_Amount *amount, 901 uint32_t min_age, 902 enum TALER_WalletAccountMergeFlags flags, 903 const struct TALER_Amount *purse_fee, 904 struct GNUNET_TIME_Timestamp merge_timestamp, 905 const struct TALER_ReserveSignatureP *reserve_sig) 906 { 907 struct PurseContext *pc = cls; 908 struct PurseSummary *ps; 909 enum GNUNET_DB_QueryStatus qs; 910 911 /* should be monotonically increasing */ 912 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 913 "Handling account merge on purse `%s'\n", 914 TALER_B2S (purse_pub)); 915 GNUNET_assert (rowid >= TALER_ARL_USE_PP (purse_account_merge_serial_id)); 916 TALER_ARL_USE_PP (purse_account_merge_serial_id) = rowid + 1; 917 if (GNUNET_OK != 918 TALER_wallet_account_merge_verify (merge_timestamp, 919 purse_pub, 920 purse_expiration, 921 h_contract_terms, 922 amount, 923 purse_fee, 924 min_age, 925 flags, 926 reserve_pub, 927 reserve_sig)) 928 { 929 struct TALER_AUDITORDB_BadSigLosses bsl = { 930 .problem_row_id = rowid, 931 .operation = (char *) "account-merge", 932 .loss = *purse_fee, 933 .operation_specific_pub = reserve_pub->eddsa_pub 934 }; 935 936 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 937 TALER_ARL_adb, 938 &bsl); 939 if (qs < 0) 940 { 941 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 942 pc->qs = qs; 943 return GNUNET_SYSERR; 944 } 945 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_bad_sig_loss), 946 &TALER_ARL_USE_AB (purse_total_bad_sig_loss), 947 purse_fee); 948 return GNUNET_OK; 949 } 950 ps = setup_purse (pc, 951 purse_pub); 952 if (NULL == ps) 953 { 954 if (0 > pc->qs) 955 { 956 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 957 return GNUNET_SYSERR; 958 } 959 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs); 960 qs = report_row_inconsistency ("account-merge", 961 rowid, 962 "purse not found"); 963 if (0 > qs) 964 { 965 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 966 pc->qs = qs; 967 return GNUNET_SYSERR; 968 } 969 return GNUNET_OK; 970 } 971 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_global_balance), 972 &TALER_ARL_USE_AB (purse_global_balance), 973 purse_fee); 974 TALER_ARL_amount_add (&ps->balance, 975 &ps->balance, 976 purse_fee); 977 return GNUNET_OK; 978 } 979 980 981 /** 982 * Function called with details about purse decisions that have been made. 983 * 984 * @param cls closure 985 * @param rowid unique serial ID for the deposit in our DB 986 * @param purse_pub which purse was the decision made on 987 * @param refunded true if decision was to refund 988 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 989 */ 990 static enum GNUNET_GenericReturnValue 991 handle_purse_decision ( 992 void *cls, 993 uint64_t rowid, 994 const struct TALER_PurseContractPublicKeyP *purse_pub, 995 bool refunded) 996 { 997 struct PurseContext *pc = cls; 998 struct PurseSummary *ps; 999 struct GNUNET_HashCode key; 1000 enum GNUNET_DB_QueryStatus qs; 1001 struct TALER_Amount purse_fee; 1002 struct TALER_Amount balance_without_purse_fee; 1003 1004 TALER_ARL_USE_PP (purse_decision_serial_id) = rowid; 1005 ps = setup_purse (pc, 1006 purse_pub); 1007 if (NULL == ps) 1008 { 1009 if (0 > pc->qs) 1010 { 1011 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc->qs); 1012 return GNUNET_SYSERR; 1013 } 1014 qs = report_row_inconsistency ("purse-decision", 1015 rowid, 1016 "purse not found"); 1017 if (0 > qs) 1018 { 1019 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1020 pc->qs = qs; 1021 return GNUNET_SYSERR; 1022 } 1023 return GNUNET_OK; 1024 } 1025 qs = get_purse_fee (ps->creation_date, 1026 &purse_fee); 1027 if (0 > qs) 1028 { 1029 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1030 pc->qs = qs; 1031 return GNUNET_SYSERR; 1032 } 1033 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1034 return GNUNET_OK; /* already reported */ 1035 if (0 > 1036 TALER_amount_subtract (&balance_without_purse_fee, 1037 &ps->balance, 1038 &purse_fee)) 1039 { 1040 qs = report_row_inconsistency ("purse-request", 1041 rowid, 1042 "purse fee higher than balance"); 1043 if (0 > qs) 1044 { 1045 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1046 pc->qs = qs; 1047 return GNUNET_SYSERR; 1048 } 1049 GNUNET_assert (GNUNET_OK == 1050 TALER_amount_set_zero (TALER_ARL_currency, 1051 &balance_without_purse_fee)); 1052 } 1053 1054 if (refunded) 1055 { 1056 if (-1 != TALER_amount_cmp (&balance_without_purse_fee, 1057 &ps->total_value)) 1058 { 1059 qs = report_amount_arithmetic_inconsistency ("purse-decision: refund", 1060 rowid, 1061 &balance_without_purse_fee, 1062 &ps->total_value, 1063 0); 1064 if (0 > qs) 1065 { 1066 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1067 pc->qs = qs; 1068 return GNUNET_SYSERR; 1069 } 1070 } 1071 if ( (internal_checks) && 1072 (! ps->purse_refunded) ) 1073 { 1074 qs = report_row_inconsistency ( 1075 "purse-decision", 1076 rowid, 1077 "purse not marked as refunded (internal check)"); 1078 if (qs < 0) 1079 { 1080 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1081 pc->qs = qs; 1082 return GNUNET_SYSERR; 1083 } 1084 } 1085 } 1086 else 1087 { 1088 if (-1 == TALER_amount_cmp (&balance_without_purse_fee, 1089 &ps->total_value)) 1090 { 1091 qs = report_amount_arithmetic_inconsistency ("purse-decision: merge", 1092 rowid, 1093 &ps->total_value, 1094 &balance_without_purse_fee, 1095 0); 1096 if (0 > qs) 1097 { 1098 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1099 pc->qs = qs; 1100 return GNUNET_SYSERR; 1101 } 1102 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1103 purse_total_balance_insufficient_loss), 1104 &TALER_ARL_USE_AB ( 1105 purse_total_balance_insufficient_loss), 1106 &ps->total_value); 1107 } 1108 } 1109 1110 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1111 "Deleting purse with decision `%s'\n", 1112 TALER_B2S (&ps->purse_pub)); 1113 qs = TALER_AUDITORDB_delete_purse_info (TALER_ARL_adb, 1114 purse_pub); 1115 if (qs < 0) 1116 { 1117 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1118 pc->qs = qs; 1119 return GNUNET_SYSERR; 1120 } 1121 GNUNET_CRYPTO_hash (purse_pub, 1122 sizeof (*purse_pub), 1123 &key); 1124 GNUNET_assert (GNUNET_YES == 1125 GNUNET_CONTAINER_multihashmap_remove (pc->purses, 1126 &key, 1127 ps)); 1128 GNUNET_free (ps); 1129 return GNUNET_OK; 1130 } 1131 1132 1133 /** 1134 * Function called on explicitly deleted purses. 1135 * 1136 * @param cls closure 1137 * @param deletion_serial_id row ID with the deletion data 1138 * @param purse_pub public key of the purse 1139 * @param purse_sig signature affirming deletion of the purse 1140 * @return #GNUNET_OK to continue to iterate 1141 */ 1142 static enum GNUNET_GenericReturnValue 1143 handle_purse_deletion ( 1144 void *cls, 1145 uint64_t deletion_serial_id, 1146 const struct TALER_PurseContractPublicKeyP *purse_pub, 1147 const struct TALER_PurseContractSignatureP *purse_sig) 1148 { 1149 struct PurseContext *pc = cls; 1150 struct PurseSummary *ps; 1151 1152 ps = setup_purse (pc, 1153 purse_pub); 1154 if (NULL == ps) 1155 { 1156 GNUNET_break (0); 1157 return GNUNET_SYSERR; 1158 } 1159 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1160 "Handling purse `%s' deletion\n", 1161 TALER_B2S (purse_pub)); 1162 if (GNUNET_OK != 1163 TALER_wallet_purse_delete_verify (purse_pub, 1164 purse_sig)) 1165 { 1166 enum GNUNET_DB_QueryStatus qs; 1167 1168 qs = report_row_inconsistency ( 1169 "purse-delete", 1170 deletion_serial_id, 1171 "purse deletion signature invalid"); 1172 if (qs < 0) 1173 { 1174 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1175 pc->qs = qs; 1176 return GNUNET_SYSERR; 1177 } 1178 } 1179 else 1180 { 1181 if ( (internal_checks) && 1182 (! ps->purse_deleted) ) 1183 { 1184 enum GNUNET_DB_QueryStatus qs; 1185 1186 qs = report_row_inconsistency ( 1187 "purse-delete", 1188 deletion_serial_id, 1189 "purse not marked as deleted (internal check)"); 1190 if (qs < 0) 1191 { 1192 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1193 pc->qs = qs; 1194 return GNUNET_SYSERR; 1195 } 1196 } 1197 } 1198 return GNUNET_OK; 1199 } 1200 1201 1202 /** 1203 * Function called on expired purses. 1204 * 1205 * @param cls closure 1206 * @param purse_pub public key of the purse 1207 * @param balance amount of money in the purse 1208 * @param expiration_date when did the purse expire? 1209 * @return #GNUNET_OK to continue to iterate 1210 */ 1211 static enum GNUNET_GenericReturnValue 1212 handle_purse_expired ( 1213 void *cls, 1214 const struct TALER_PurseContractPublicKeyP *purse_pub, 1215 const struct TALER_Amount *balance, 1216 struct GNUNET_TIME_Timestamp expiration_date) 1217 { 1218 struct PurseContext *pc = cls; 1219 enum GNUNET_DB_QueryStatus qs; 1220 struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnci = { 1221 .amount = *balance, 1222 .expiration_date = expiration_date.abs_time, 1223 .purse_pub = purse_pub->eddsa_pub 1224 }; 1225 1226 qs = TALER_AUDITORDB_insert_purse_not_closed_inconsistencies ( 1227 TALER_ARL_adb, 1228 &pnci); 1229 if (qs < 0) 1230 { 1231 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1232 pc->qs = qs; 1233 return GNUNET_SYSERR; 1234 } 1235 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1236 "Handling purse expiration `%s'\n", 1237 TALER_B2S (purse_pub)); 1238 TALER_ARL_amount_add (&TALER_ARL_USE_AB (purse_total_delayed_decisions), 1239 &TALER_ARL_USE_AB (purse_total_delayed_decisions), 1240 balance); 1241 return GNUNET_OK; 1242 } 1243 1244 1245 /** 1246 * Check that the purse summary matches what the exchange database 1247 * thinks about the purse, and update our own state of the purse. 1248 * 1249 * Remove all purses that we are happy with from the DB. 1250 * 1251 * @param cls our `struct PurseContext` 1252 * @param key hash of the purse public key 1253 * @param value a `struct PurseSummary` 1254 * @return #GNUNET_OK to process more entries 1255 */ 1256 static enum GNUNET_GenericReturnValue 1257 verify_purse_balance (void *cls, 1258 const struct GNUNET_HashCode *key, 1259 void *value) 1260 { 1261 struct PurseContext *pc = cls; 1262 struct PurseSummary *ps = value; 1263 enum GNUNET_DB_QueryStatus qs; 1264 1265 if (internal_checks) 1266 { 1267 struct TALER_Amount pf; 1268 struct TALER_Amount balance_without_purse_fee; 1269 1270 /* subtract purse fee from ps->balance to get actual balance we expect, as 1271 we track the balance including purse fee, while the exchange subtracts 1272 the purse fee early on. */ 1273 qs = get_purse_fee (ps->creation_date, 1274 &pf); 1275 if (qs < 0) 1276 { 1277 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1278 pc->qs = qs; 1279 return GNUNET_SYSERR; 1280 } 1281 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1282 return GNUNET_OK; /* error already reported */ 1283 if (0 > 1284 TALER_amount_subtract (&balance_without_purse_fee, 1285 &ps->balance, 1286 &pf)) 1287 { 1288 qs = report_row_inconsistency ("purse", 1289 0, 1290 "purse fee higher than balance"); 1291 if (qs < 0) 1292 { 1293 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1294 pc->qs = qs; 1295 return GNUNET_SYSERR; 1296 } 1297 GNUNET_assert (GNUNET_OK == 1298 TALER_amount_set_zero (TALER_ARL_currency, 1299 &balance_without_purse_fee)); 1300 } 1301 1302 if (0 != TALER_amount_cmp (&ps->exchange_balance, 1303 &balance_without_purse_fee)) 1304 { 1305 qs = report_amount_arithmetic_inconsistency ("purse-balance", 1306 0, 1307 &ps->exchange_balance, 1308 &balance_without_purse_fee, 1309 0); 1310 if (qs < 0) 1311 { 1312 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1313 pc->qs = qs; 1314 return GNUNET_SYSERR; 1315 } 1316 } 1317 } 1318 1319 if (ps->had_pi) 1320 { 1321 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1322 "Updating purse `%s'\n", 1323 TALER_B2S (&ps->purse_pub)); 1324 qs = TALER_AUDITORDB_update_purse_info (TALER_ARL_adb, 1325 &ps->purse_pub, 1326 &ps->balance); 1327 } 1328 else 1329 { 1330 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1331 "Inserting purse `%s'\n", 1332 TALER_B2S (&ps->purse_pub)); 1333 qs = TALER_AUDITORDB_insert_purse_info (TALER_ARL_adb, 1334 &ps->purse_pub, 1335 &ps->balance, 1336 ps->expiration_date); 1337 ps->had_pi = true; 1338 } 1339 if (qs < 0) 1340 { 1341 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1342 pc->qs = qs; 1343 return GNUNET_SYSERR; 1344 } 1345 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 1346 return GNUNET_OK; 1347 } 1348 1349 1350 /** 1351 * Clear memory from the purses hash map. 1352 * 1353 * @param cls our `struct PurseContext` 1354 * @param key hash of the purse public key 1355 * @param value a `struct PurseSummary` 1356 * @return #GNUNET_OK to process more entries 1357 */ 1358 static enum GNUNET_GenericReturnValue 1359 release_purse_balance (void *cls, 1360 const struct GNUNET_HashCode *key, 1361 void *value) 1362 { 1363 struct PurseContext *pc = cls; 1364 struct PurseSummary *ps = value; 1365 1366 GNUNET_assert (GNUNET_YES == 1367 GNUNET_CONTAINER_multihashmap_remove (pc->purses, 1368 key, 1369 ps)); 1370 GNUNET_free (ps); 1371 return GNUNET_OK; 1372 } 1373 1374 1375 /** 1376 * Analyze purses for being well-formed. 1377 * 1378 * @param cls NULL 1379 * @return transaction status code 1380 */ 1381 static enum GNUNET_DB_QueryStatus 1382 analyze_purses (void *cls) 1383 { 1384 struct PurseContext pc; 1385 enum GNUNET_DB_QueryStatus qs; 1386 bool had_pp; 1387 bool had_bal; 1388 1389 (void) cls; 1390 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1391 "Analyzing purses\n"); 1392 qs = TALER_AUDITORDB_get_auditor_progress ( 1393 TALER_ARL_adb, 1394 TALER_ARL_GET_PP (purse_account_merge_serial_id), 1395 TALER_ARL_GET_PP (purse_decision_serial_id), 1396 TALER_ARL_GET_PP (purse_deletion_serial_id), 1397 TALER_ARL_GET_PP (purse_deposits_serial_id), 1398 TALER_ARL_GET_PP (purse_merges_serial_id), 1399 TALER_ARL_GET_PP (purse_request_serial_id), 1400 TALER_ARL_GET_PP (purse_open_counter), 1401 NULL); 1402 if (0 > qs) 1403 { 1404 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1405 return qs; 1406 } 1407 had_pp = (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); 1408 if (had_pp) 1409 { 1410 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1411 "Resuming purse audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu\n", 1412 (unsigned long long) TALER_ARL_USE_PP ( 1413 purse_open_counter), 1414 (unsigned long long) TALER_ARL_USE_PP ( 1415 purse_request_serial_id), 1416 (unsigned long long) TALER_ARL_USE_PP ( 1417 purse_decision_serial_id), 1418 (unsigned long long) TALER_ARL_USE_PP ( 1419 purse_deletion_serial_id), 1420 (unsigned long long) TALER_ARL_USE_PP ( 1421 purse_merges_serial_id), 1422 (unsigned long long) TALER_ARL_USE_PP ( 1423 purse_deposits_serial_id), 1424 (unsigned long long) TALER_ARL_USE_PP ( 1425 purse_account_merge_serial_id)); 1426 } 1427 else 1428 { 1429 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1430 "First analysis using this auditor, starting audit from scratch\n"); 1431 } 1432 pc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1433 qs = TALER_AUDITORDB_get_balance ( 1434 TALER_ARL_adb, 1435 TALER_ARL_GET_AB (purse_global_balance), 1436 TALER_ARL_GET_AB (purse_total_balance_insufficient_loss), 1437 TALER_ARL_GET_AB (purse_total_delayed_decisions), 1438 TALER_ARL_GET_AB (purse_total_balance_purse_not_closed), 1439 TALER_ARL_GET_AB (purse_total_arithmetic_delta_plus), 1440 TALER_ARL_GET_AB (purse_total_arithmetic_delta_minus), 1441 TALER_ARL_GET_AB (purse_total_bad_sig_loss), 1442 NULL); 1443 if (qs < 0) 1444 { 1445 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1446 return qs; 1447 } 1448 had_bal = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 1449 pc.purses = GNUNET_CONTAINER_multihashmap_create (512, 1450 GNUNET_NO); 1451 1452 qs = TALER_TALER_EXCHANGEDB_select_purse_requests_above_serial_id ( 1453 TALER_ARL_edb, 1454 TALER_ARL_USE_PP (purse_request_serial_id), 1455 &handle_purse_requested, 1456 &pc); 1457 if (qs < 0) 1458 { 1459 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1460 return qs; 1461 } 1462 if (pc.qs < 0) 1463 { 1464 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1465 return pc.qs; 1466 } 1467 qs = TALER_TALER_TALER_EXCHANGEDB_select_purse_merges_above_serial_id ( 1468 TALER_ARL_edb, 1469 TALER_ARL_USE_PP (purse_merges_serial_id), 1470 &handle_purse_merged, 1471 &pc); 1472 if (qs < 0) 1473 { 1474 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1475 return qs; 1476 } 1477 if (pc.qs < 0) 1478 { 1479 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1480 return pc.qs; 1481 } 1482 1483 qs = TALER_TALER_EXCHANGEDB_select_purse_deposits_above_serial_id ( 1484 TALER_ARL_edb, 1485 TALER_ARL_USE_PP (purse_deposits_serial_id), 1486 &handle_purse_deposits, 1487 &pc); 1488 if (qs < 0) 1489 { 1490 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1491 return qs; 1492 } 1493 if (pc.qs < 0) 1494 { 1495 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1496 return pc.qs; 1497 } 1498 1499 /* Charge purse fee! */ 1500 qs = TALER_EXCHANGEDB_select_account_merges_above_serial_id ( 1501 TALER_ARL_edb, 1502 TALER_ARL_USE_PP (purse_account_merge_serial_id), 1503 &handle_account_merged, 1504 &pc); 1505 if (qs < 0) 1506 { 1507 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1508 return qs; 1509 } 1510 if (pc.qs < 0) 1511 { 1512 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1513 return pc.qs; 1514 } 1515 1516 qs = TALER_EXCHANGEDB_select_all_purse_decisions_above_serial_id ( 1517 TALER_ARL_edb, 1518 TALER_ARL_USE_PP (purse_decision_serial_id), 1519 &handle_purse_decision, 1520 &pc); 1521 if (qs < 0) 1522 { 1523 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1524 return qs; 1525 } 1526 if (pc.qs < 0) 1527 { 1528 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1529 return pc.qs; 1530 } 1531 1532 qs = TALER_EXCHANGEDB_select_all_purse_deletions_above_serial_id ( 1533 TALER_ARL_edb, 1534 TALER_ARL_USE_PP (purse_deletion_serial_id), 1535 &handle_purse_deletion, 1536 &pc); 1537 if (qs < 0) 1538 { 1539 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1540 return qs; 1541 } 1542 if (pc.qs < 0) 1543 { 1544 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1545 return pc.qs; 1546 } 1547 1548 qs = TALER_AUDITORDB_select_purse_expired ( 1549 TALER_ARL_adb, 1550 &handle_purse_expired, 1551 &pc); 1552 if (qs < 0) 1553 { 1554 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1555 return qs; 1556 } 1557 if (pc.qs < 0) 1558 { 1559 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1560 return pc.qs; 1561 } 1562 1563 GNUNET_CONTAINER_multihashmap_iterate (pc.purses, 1564 &verify_purse_balance, 1565 &pc); 1566 GNUNET_CONTAINER_multihashmap_iterate (pc.purses, 1567 &release_purse_balance, 1568 &pc); 1569 GNUNET_break (0 == 1570 GNUNET_CONTAINER_multihashmap_size (pc.purses)); 1571 GNUNET_CONTAINER_multihashmap_destroy (pc.purses); 1572 if (pc.qs < 0) 1573 { 1574 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == pc.qs); 1575 return pc.qs; 1576 } 1577 if (had_bal) 1578 qs = TALER_AUDITORDB_update_balance ( 1579 TALER_ARL_adb, 1580 TALER_ARL_SET_AB (purse_global_balance), 1581 TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), 1582 TALER_ARL_SET_AB (purse_total_delayed_decisions), 1583 TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), 1584 TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), 1585 TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), 1586 TALER_ARL_SET_AB (purse_total_bad_sig_loss), 1587 NULL); 1588 else 1589 qs = TALER_AUDITORDB_insert_balance ( 1590 TALER_ARL_adb, 1591 TALER_ARL_SET_AB (purse_global_balance), 1592 TALER_ARL_SET_AB (purse_total_balance_insufficient_loss), 1593 TALER_ARL_SET_AB (purse_total_delayed_decisions), 1594 TALER_ARL_SET_AB (purse_total_balance_purse_not_closed), 1595 TALER_ARL_SET_AB (purse_total_arithmetic_delta_plus), 1596 TALER_ARL_SET_AB (purse_total_arithmetic_delta_minus), 1597 TALER_ARL_SET_AB (purse_total_bad_sig_loss), 1598 NULL); 1599 if (0 > qs) 1600 { 1601 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1602 "Failed to update auditor DB, not recording progress\n"); 1603 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1604 return qs; 1605 } 1606 if (had_pp) 1607 qs = TALER_AUDITORDB_update_auditor_progress ( 1608 TALER_ARL_adb, 1609 TALER_ARL_SET_PP (purse_account_merge_serial_id), 1610 TALER_ARL_SET_PP (purse_decision_serial_id), 1611 TALER_ARL_SET_PP (purse_deletion_serial_id), 1612 TALER_ARL_SET_PP (purse_deposits_serial_id), 1613 TALER_ARL_SET_PP (purse_merges_serial_id), 1614 TALER_ARL_SET_PP (purse_request_serial_id), 1615 TALER_ARL_SET_PP (purse_open_counter), 1616 NULL); 1617 else 1618 qs = TALER_AUDITORDB_insert_auditor_progress ( 1619 TALER_ARL_adb, 1620 TALER_ARL_SET_PP (purse_account_merge_serial_id), 1621 TALER_ARL_SET_PP (purse_decision_serial_id), 1622 TALER_ARL_SET_PP (purse_deletion_serial_id), 1623 TALER_ARL_SET_PP (purse_deposits_serial_id), 1624 TALER_ARL_SET_PP (purse_merges_serial_id), 1625 TALER_ARL_SET_PP (purse_request_serial_id), 1626 TALER_ARL_SET_PP (purse_open_counter), 1627 NULL); 1628 if (0 > qs) 1629 { 1630 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1631 "Failed to update auditor DB, not recording progress\n"); 1632 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1633 return qs; 1634 } 1635 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1636 "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu/%llu\n", 1637 (unsigned long long) TALER_ARL_USE_PP ( 1638 purse_request_serial_id), 1639 (unsigned long long) TALER_ARL_USE_PP ( 1640 purse_decision_serial_id), 1641 (unsigned long long) TALER_ARL_USE_PP ( 1642 purse_deletion_serial_id), 1643 (unsigned long long) TALER_ARL_USE_PP ( 1644 purse_merges_serial_id), 1645 (unsigned long long) TALER_ARL_USE_PP ( 1646 purse_deposits_serial_id), 1647 (unsigned long long) TALER_ARL_USE_PP ( 1648 purse_account_merge_serial_id)); 1649 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1650 } 1651 1652 1653 /** 1654 * Function called on events received from Postgres. 1655 * 1656 * @param cls closure, NULL 1657 * @param extra additional event data provided 1658 * @param extra_size number of bytes in @a extra 1659 */ 1660 static void 1661 db_notify (void *cls, 1662 const void *extra, 1663 size_t extra_size) 1664 { 1665 (void) cls; 1666 (void) extra; 1667 (void) extra_size; 1668 1669 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1670 "Received notification to wake purses\n"); 1671 if (GNUNET_OK != 1672 TALER_ARL_setup_sessions_and_run (&analyze_purses, 1673 NULL)) 1674 { 1675 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1676 "Audit failed\n"); 1677 GNUNET_SCHEDULER_shutdown (); 1678 global_ret = EXIT_FAILURE; 1679 return; 1680 } 1681 } 1682 1683 1684 /** 1685 * Function called on shutdown. 1686 */ 1687 static void 1688 do_shutdown (void *cls) 1689 { 1690 (void) cls; 1691 1692 if (NULL != eh) 1693 { 1694 TALER_AUDITORDB_event_listen_cancel (eh); 1695 eh = NULL; 1696 } 1697 TALER_ARL_done (); 1698 } 1699 1700 1701 /** 1702 * Main function that will be run. 1703 * 1704 * @param cls closure 1705 * @param args remaining command-line arguments 1706 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1707 * @param c configuration 1708 */ 1709 static void 1710 run (void *cls, 1711 char *const *args, 1712 const char *cfgfile, 1713 const struct GNUNET_CONFIGURATION_Handle *c) 1714 { 1715 (void) cls; 1716 (void) args; 1717 (void) cfgfile; 1718 1719 cfg = c; 1720 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1721 NULL); 1722 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1723 "Launching purses auditor\n"); 1724 if (GNUNET_OK != 1725 TALER_ARL_init (c)) 1726 { 1727 global_ret = EXIT_FAILURE; 1728 return; 1729 } 1730 if (test_mode != 1) 1731 { 1732 struct GNUNET_DB_EventHeaderP es = { 1733 .size = htons (sizeof (es)), 1734 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_PURSES) 1735 }; 1736 1737 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1738 "Running helper indefinitely\n"); 1739 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 1740 &es, 1741 GNUNET_TIME_UNIT_FOREVER_REL, 1742 &db_notify, 1743 NULL); 1744 } 1745 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1746 "Starting audit\n"); 1747 if (GNUNET_OK != 1748 TALER_ARL_setup_sessions_and_run (&analyze_purses, 1749 NULL)) 1750 { 1751 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1752 "Audit failed\n"); 1753 GNUNET_SCHEDULER_shutdown (); 1754 global_ret = EXIT_FAILURE; 1755 return; 1756 } 1757 } 1758 1759 1760 /** 1761 * The main function to check the database's handling of purses. 1762 * 1763 * @param argc number of arguments from the command line 1764 * @param argv command line arguments 1765 * @return 0 ok, 1 on error 1766 */ 1767 int 1768 main (int argc, 1769 char *const *argv) 1770 { 1771 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1772 GNUNET_GETOPT_option_flag ('i', 1773 "internal", 1774 "perform checks only applicable for exchange-internal audits", 1775 &internal_checks), 1776 GNUNET_GETOPT_option_flag ('t', 1777 "test", 1778 "run in test mode and exit when idle", 1779 &test_mode), 1780 GNUNET_GETOPT_option_timetravel ('T', 1781 "timetravel"), 1782 GNUNET_GETOPT_OPTION_END 1783 }; 1784 enum GNUNET_GenericReturnValue ret; 1785 1786 ret = GNUNET_PROGRAM_run ( 1787 TALER_AUDITOR_project_data (), 1788 argc, 1789 argv, 1790 "taler-helper-auditor-purses", 1791 gettext_noop ("Audit Taler exchange purse handling"), 1792 options, 1793 &run, 1794 NULL); 1795 if (GNUNET_SYSERR == ret) 1796 return EXIT_INVALIDARGUMENT; 1797 if (GNUNET_NO == ret) 1798 return EXIT_SUCCESS; 1799 return global_ret; 1800 } 1801 1802 1803 /* end of taler-helper-auditor-purses.c */