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