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