taler-helper-auditor-reserves.c (81849B)
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-reserves.c 18 * @brief audits the reserves of an exchange database 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 /* RESERVE_IN's callback typedef also lives in exchangedb_lib.h (pulled in 23 transitively by report-lib.h below), so its closure override must be 24 established before any of those includes. */ 25 struct ReserveContext; 26 #define TALER_EXCHANGEDB_RESERVE_IN_RESULT_CLOSURE struct ReserveContext 27 #include "auditordb_lib.h" 28 #include "report-lib.h" 29 #include "taler/taler_dbevents.h" 30 #include "exchangedb_lib.h" 31 #include "exchange-database/get_reserve_close_request_info.h" 32 #include "auditor-database/delete_reserve_info.h" 33 #include "auditor-database/event_listen.h" 34 #include "auditor-database/get_auditor_progress.h" 35 #include "auditor-database/get_balance.h" 36 #include "auditor-database/get_reserve_info.h" 37 #include "auditor-database/insert_amount_arithmetic_inconsistency.h" 38 #include "auditor-database/insert_auditor_progress.h" 39 #include "auditor-database/insert_bad_sig_losses.h" 40 #include "auditor-database/insert_balance.h" 41 #include \ 42 "auditor-database/insert_denomination_key_validity_withdraw_inconsistency.h" 43 #include \ 44 "auditor-database/insert_reserve_balance_insufficient_inconsistency.h" 45 #include \ 46 "auditor-database/insert_reserve_balance_summary_wrong_inconsistency.h" 47 #include "auditor-database/insert_reserve_info.h" 48 #include "auditor-database/insert_reserve_not_closed_inconsistency.h" 49 #include "auditor-database/insert_row_inconsistency.h" 50 #include "auditor-database/update_reserve_info.h" 51 #include "exchange-database/get_denomination_revocation.h" 52 #include "exchange-database/get_wire_fee.h" 53 #include "exchange-database/get_reserve.h" 54 struct ReserveContext; 55 #define TALER_EXCHANGEDB_ACCOUNT_MERGE_RESULT_CLOSURE struct ReserveContext 56 #define TALER_EXCHANGEDB_PURSE_DECISION_RESULT_CLOSURE struct ReserveContext 57 #define TALER_EXCHANGEDB_RECOUP_RESULT_CLOSURE struct ReserveContext 58 #define TALER_EXCHANGEDB_RESERVE_CLOSED_RESULT_CLOSURE struct ReserveContext 59 #define TALER_EXCHANGEDB_RESERVE_OPEN_RESULT_CLOSURE struct ReserveContext 60 #define TALER_EXCHANGEDB_WITHDRAW_RESULT_CLOSURE struct ReserveContext 61 #include "exchange-database/iterate_account_merges_above_serial_id.h" 62 #include "exchange-database/iterate_purse_decisions_above_serial_id.h" 63 #include "exchange-database/iterate_recoups_above_serial_id.h" 64 #include "exchange-database/iterate_reserve_closed_above_serial_id.h" 65 #include "exchange-database/iterate_reserve_open_above_serial_id.h" 66 #include "exchange-database/iterate_reserves_in_above_serial_id.h" 67 #include "exchange-database/iterate_withdrawals_above_serial_id.h" 68 69 /** 70 * Use a 1 day grace period to deal with clocks not being perfectly synchronized. 71 */ 72 #define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS 73 74 /** 75 * Return value from main(). 76 */ 77 static int global_ret; 78 79 /** 80 * State of the last database transaction. 81 */ 82 static enum GNUNET_DB_QueryStatus global_qs; 83 84 /** 85 * Run in test mode. Exit when idle instead of 86 * going to sleep and waiting for more work. 87 */ 88 static int test_mode; 89 90 /** 91 * After how long should idle reserves be closed? 92 */ 93 static struct GNUNET_TIME_Relative idle_reserve_expiration_time; 94 95 /** 96 * Checkpointing our progress for reserves. 97 */ 98 static TALER_ARL_DEF_PP (reserves_reserve_in_serial_id); 99 static TALER_ARL_DEF_PP (reserves_withdraw_serial_id); 100 static TALER_ARL_DEF_PP (reserves_reserve_recoup_serial_id); 101 static TALER_ARL_DEF_PP (reserves_reserve_open_serial_id); 102 static TALER_ARL_DEF_PP (reserves_reserve_close_serial_id); 103 static TALER_ARL_DEF_PP (reserves_purse_decisions_serial_id); 104 static TALER_ARL_DEF_PP (reserves_account_merges_serial_id); 105 static TALER_ARL_DEF_PP (reserves_history_requests_serial_id); 106 107 /** 108 * Tracked global reserve balances. 109 */ 110 static TALER_ARL_DEF_AB (reserves_reserve_total_balance); 111 static TALER_ARL_DEF_AB (reserves_reserve_loss); 112 static TALER_ARL_DEF_AB (reserves_withdraw_fee_revenue); 113 static TALER_ARL_DEF_AB (reserves_close_fee_revenue); 114 static TALER_ARL_DEF_AB (reserves_purse_fee_revenue); 115 static TALER_ARL_DEF_AB (reserves_open_fee_revenue); 116 static TALER_ARL_DEF_AB (reserves_history_fee_revenue); 117 118 /** 119 * Total amount lost by operations for which signatures were invalid. 120 */ 121 static TALER_ARL_DEF_AB (reserves_total_bad_sig_loss); 122 123 /** 124 * Total amount affected by reserves not having been closed on time. 125 */ 126 static TALER_ARL_DEF_AB (total_balance_reserve_not_closed); 127 128 /** 129 * Total delta between expected and stored reserve balance summaries, 130 * for positive deltas. Used only when internal checks are 131 * enabled. 132 */ 133 static TALER_ARL_DEF_AB (total_balance_summary_delta_plus); 134 135 /** 136 * Total delta between expected and stored reserve balance summaries, 137 * for negative deltas. Used only when internal checks are 138 * enabled. 139 */ 140 static TALER_ARL_DEF_AB (total_balance_summary_delta_minus); 141 142 /** 143 * Profits the exchange made by bad amount calculations. 144 */ 145 static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_plus); 146 147 /** 148 * Losses the exchange made by bad amount calculations. 149 */ 150 static TALER_ARL_DEF_AB (reserves_total_arithmetic_delta_minus); 151 152 /** 153 * Should we run checks that only work for exchange-internal audits? 154 */ 155 static int internal_checks; 156 157 static struct GNUNET_DB_EventHandler *eh; 158 159 /** 160 * The auditors's configuration. 161 */ 162 static const struct GNUNET_CONFIGURATION_Handle *cfg; 163 164 /* ***************************** Report logic **************************** */ 165 166 167 /** 168 * Report a (serious) inconsistency in the exchange's database with 169 * respect to calculations involving amounts. 170 * 171 * @param operation what operation had the inconsistency 172 * @param rowid affected row, 0 if row is missing 173 * @param exchange amount calculated by exchange 174 * @param auditor amount calculated by auditor 175 * @param profitable 1 if @a exchange being larger than @a auditor is 176 * profitable for the exchange for this operation, 177 * -1 if @a exchange being smaller than @a auditor is 178 * profitable for the exchange, and 0 if it is unclear 179 */ 180 static void 181 report_amount_arithmetic_inconsistency ( 182 const char *operation, 183 uint64_t rowid, 184 const struct TALER_Amount *exchange, 185 const struct TALER_Amount *auditor, 186 int profitable) 187 { 188 struct TALER_Amount delta; 189 struct TALER_Amount *target; 190 enum GNUNET_DB_QueryStatus qs; 191 192 if (0 < TALER_amount_cmp (exchange, 193 auditor)) 194 { 195 /* exchange > auditor */ 196 TALER_ARL_amount_subtract (&delta, 197 exchange, 198 auditor); 199 } 200 else 201 { 202 /* exchange <= auditor */ 203 profitable = -profitable; 204 TALER_ARL_amount_subtract (&delta, 205 auditor, 206 exchange); 207 } 208 209 { 210 struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = { 211 .problem_row_id = rowid, 212 .profitable = profitable, 213 .operation = (char *) operation, 214 .exchange_amount = *exchange, 215 .auditor_amount = *auditor, 216 }; 217 218 qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency ( 219 TALER_ARL_adb, 220 &aai); 221 222 if (qs < 0) 223 { 224 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 225 global_qs = qs; 226 return; 227 } 228 } 229 230 if (0 != profitable) 231 { 232 target = (1 == profitable) 233 ? &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_plus) 234 : &TALER_ARL_USE_AB (reserves_total_arithmetic_delta_minus); 235 TALER_ARL_amount_add (target, 236 target, 237 &delta); 238 } 239 } 240 241 242 /** 243 * Report a (serious) inconsistency in the exchange's database. 244 * 245 * @param table affected table 246 * @param rowid affected row, 0 if row is missing 247 * @param diagnostic message explaining the problem 248 */ 249 static void 250 report_row_inconsistency (const char *table, 251 uint64_t rowid, 252 const char *diagnostic) 253 { 254 enum GNUNET_DB_QueryStatus qs; 255 struct TALER_AUDITORDB_RowInconsistency ri = { 256 .diagnostic = (char *) diagnostic, 257 .row_table = (char *) table, 258 .problem_row_id = rowid 259 }; 260 261 qs = TALER_AUDITORDB_insert_row_inconsistency ( 262 TALER_ARL_adb, 263 &ri); 264 265 if (qs < 0) 266 { 267 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 268 global_qs = qs; 269 return; 270 } 271 } 272 273 274 /* ***************************** Analyze reserves ************************ */ 275 /* This logic checks the reserves_in, withdraw and reserves-tables */ 276 277 /** 278 * Summary data we keep per reserve. 279 */ 280 struct ReserveSummary 281 { 282 /** 283 * Public key of the reserve. 284 * Always set when the struct is first initialized. 285 */ 286 struct TALER_ReservePublicKeyP reserve_pub; 287 288 /** 289 * Sum of all incoming transfers during this transaction. 290 * Updated only in #handle_reserve_in(). 291 */ 292 struct TALER_Amount total_in; 293 294 /** 295 * Sum of all outgoing transfers during this transaction (includes fees). 296 * Updated only in #handle_withdrawals(). 297 */ 298 struct TALER_Amount total_out; 299 300 /** 301 * Sum of balance and fees encountered during this transaction. 302 */ 303 struct TALER_AUDITORDB_ReserveFeeBalance curr_balance; 304 305 /** 306 * Previous balances of the reserve as remembered by the auditor. 307 * (updated based on @e total_in and @e total_out at the end). 308 */ 309 struct TALER_AUDITORDB_ReserveFeeBalance prev_balance; 310 311 /** 312 * Previous reserve expiration data, as remembered by the auditor. 313 * (updated on-the-fly in #handle_reserve_in()). 314 */ 315 struct GNUNET_TIME_Timestamp a_expiration_date; 316 317 /** 318 * Which account did originally put money into the reserve? 319 */ 320 struct TALER_FullPayto sender_account; 321 322 /** 323 * Did we have a previous reserve info? Used to decide between 324 * UPDATE and INSERT later. Initialized in 325 * #load_auditor_reserve_summary() together with the a-* values 326 * (if available). 327 */ 328 bool had_ri; 329 330 }; 331 332 333 /** 334 * Load the auditor's remembered state about the reserve into @a rs. 335 * The "total_in" and "total_out" amounts of @a rs must already be 336 * initialized (so we can determine the currency). 337 * 338 * @param[in,out] rs reserve summary to (fully) initialize 339 * @return transaction status code 340 */ 341 static enum GNUNET_DB_QueryStatus 342 load_auditor_reserve_summary (struct ReserveSummary *rs) 343 { 344 enum GNUNET_DB_QueryStatus qs; 345 uint64_t rowid; 346 347 qs = TALER_AUDITORDB_get_reserve_info (TALER_ARL_adb, 348 &rs->reserve_pub, 349 &rowid, 350 &rs->prev_balance, 351 &rs->a_expiration_date, 352 &rs->sender_account); 353 if (0 > qs) 354 { 355 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 356 return qs; 357 } 358 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 359 { 360 rs->had_ri = false; 361 GNUNET_assert (GNUNET_OK == 362 TALER_amount_set_zero (rs->total_in.currency, 363 &rs->prev_balance.reserve_balance)); 364 GNUNET_assert (GNUNET_OK == 365 TALER_amount_set_zero (rs->total_in.currency, 366 &rs->prev_balance.reserve_loss)); 367 GNUNET_assert (GNUNET_OK == 368 TALER_amount_set_zero (rs->total_in.currency, 369 &rs->prev_balance.withdraw_fee_balance 370 )); 371 GNUNET_assert (GNUNET_OK == 372 TALER_amount_set_zero (rs->total_in.currency, 373 &rs->prev_balance.close_fee_balance)); 374 GNUNET_assert (GNUNET_OK == 375 TALER_amount_set_zero (rs->total_in.currency, 376 &rs->prev_balance.purse_fee_balance)); 377 GNUNET_assert (GNUNET_OK == 378 TALER_amount_set_zero (rs->total_in.currency, 379 &rs->prev_balance.open_fee_balance)); 380 GNUNET_assert (GNUNET_OK == 381 TALER_amount_set_zero (rs->total_in.currency, 382 &rs->prev_balance.history_fee_balance) 383 ); 384 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 385 "Creating fresh reserve `%s'\n", 386 TALER_B2S (&rs->reserve_pub)); 387 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 388 } 389 rs->had_ri = true; 390 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 391 "Auditor remembers reserve `%s' has balance %s\n", 392 TALER_B2S (&rs->reserve_pub), 393 TALER_amount2s (&rs->prev_balance.reserve_balance)); 394 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 395 } 396 397 398 /** 399 * Closure to the various callbacks we make while checking a reserve. 400 */ 401 struct ReserveContext 402 { 403 /** 404 * Map from hash of reserve's public key to a `struct ReserveSummary`. 405 */ 406 struct GNUNET_CONTAINER_MultiHashMap *reserves; 407 408 /** 409 * Map from hash of denomination's public key to a 410 * static string "revoked" for keys that have been revoked, 411 * or "master signature invalid" in case the revocation is 412 * there but bogus. 413 */ 414 struct GNUNET_CONTAINER_MultiHashMap *revoked; 415 416 /** 417 * Transaction status code, set to error codes if applicable. 418 */ 419 enum GNUNET_DB_QueryStatus qs; 420 421 }; 422 423 424 /** 425 * Create a new reserve for @a reserve_pub in @a rc. 426 * 427 * @param[in,out] rc context to update 428 * @param reserve_pub key for which to create a reserve 429 * @return NULL on error 430 */ 431 static struct ReserveSummary * 432 setup_reserve (struct ReserveContext *rc, 433 const struct TALER_ReservePublicKeyP *reserve_pub) 434 { 435 struct ReserveSummary *rs; 436 struct GNUNET_HashCode key; 437 enum GNUNET_DB_QueryStatus qs; 438 439 GNUNET_CRYPTO_hash (reserve_pub, 440 sizeof (*reserve_pub), 441 &key); 442 rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, 443 &key); 444 if (NULL != rs) 445 return rs; 446 rs = GNUNET_new (struct ReserveSummary); 447 rs->reserve_pub = *reserve_pub; 448 GNUNET_assert (GNUNET_OK == 449 TALER_amount_set_zero (TALER_ARL_currency, 450 &rs->total_in)); 451 GNUNET_assert (GNUNET_OK == 452 TALER_amount_set_zero (TALER_ARL_currency, 453 &rs->total_out)); 454 GNUNET_assert (GNUNET_OK == 455 TALER_amount_set_zero (TALER_ARL_currency, 456 &rs->curr_balance.reserve_balance)); 457 GNUNET_assert (GNUNET_OK == 458 TALER_amount_set_zero (TALER_ARL_currency, 459 &rs->curr_balance.reserve_loss)); 460 GNUNET_assert (GNUNET_OK == 461 TALER_amount_set_zero (TALER_ARL_currency, 462 &rs->curr_balance.withdraw_fee_balance)) 463 ; 464 GNUNET_assert (GNUNET_OK == 465 TALER_amount_set_zero (TALER_ARL_currency, 466 &rs->curr_balance.close_fee_balance)); 467 GNUNET_assert (GNUNET_OK == 468 TALER_amount_set_zero (TALER_ARL_currency, 469 &rs->curr_balance.purse_fee_balance)); 470 GNUNET_assert (GNUNET_OK == 471 TALER_amount_set_zero (TALER_ARL_currency, 472 &rs->curr_balance.open_fee_balance)); 473 GNUNET_assert (GNUNET_OK == 474 TALER_amount_set_zero (TALER_ARL_currency, 475 &rs->curr_balance.history_fee_balance)); 476 if (0 > (qs = load_auditor_reserve_summary (rs))) 477 { 478 GNUNET_free (rs); 479 rc->qs = qs; 480 return NULL; 481 } 482 GNUNET_assert (GNUNET_OK == 483 GNUNET_CONTAINER_multihashmap_put (rc->reserves, 484 &key, 485 rs, 486 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY) 487 ); 488 return rs; 489 } 490 491 492 /** 493 * Function called with details about incoming wire transfers. 494 * 495 * @param rc our `struct ReserveContext` 496 * @param rowid unique serial ID for the refresh session in our DB 497 * @param reserve_pub public key of the reserve (also the WTID) 498 * @param credit amount that was received 499 * @param sender_account_details information about the sender's bank account 500 * @param wire_reference unique reference identifying the wire transfer 501 * @param execution_date when did we receive the funds 502 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 503 */ 504 static enum GNUNET_GenericReturnValue 505 handle_reserve_in ( 506 struct ReserveContext *rc, 507 uint64_t rowid, 508 const struct TALER_ReservePublicKeyP *reserve_pub, 509 const struct TALER_Amount *credit, 510 const struct TALER_FullPayto sender_account_details, 511 uint64_t wire_reference, 512 struct GNUNET_TIME_Timestamp execution_date) 513 { 514 struct ReserveSummary *rs; 515 struct GNUNET_TIME_Timestamp expiry; 516 517 (void) wire_reference; 518 /* should be monotonically increasing */ 519 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_in_serial_id)); 520 TALER_ARL_USE_PP (reserves_reserve_in_serial_id) = rowid + 1; 521 rs = setup_reserve (rc, 522 reserve_pub); 523 if (NULL == rs) 524 { 525 GNUNET_break (0); 526 return GNUNET_SYSERR; 527 } 528 if (NULL == rs->sender_account.full_payto) 529 rs->sender_account.full_payto 530 = GNUNET_strdup (sender_account_details.full_payto); 531 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 532 "Additional incoming wire transfer for reserve `%s' of %s\n", 533 TALER_B2S (reserve_pub), 534 TALER_amount2s (credit)); 535 expiry = GNUNET_TIME_absolute_to_timestamp ( 536 GNUNET_TIME_absolute_add (execution_date.abs_time, 537 idle_reserve_expiration_time)); 538 rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date, 539 expiry); 540 TALER_ARL_amount_add (&rs->total_in, 541 &rs->total_in, 542 credit); 543 return GNUNET_OK; 544 } 545 546 547 /** 548 * Function called with details about withdraw operations. Verifies 549 * the signature and updates the reserve's balance. 550 * 551 * @param rc our `struct ReserveContext` 552 * @param rowid unique serial ID for the refresh session in our DB 553 * @param num_denom_serials number of elements in @e denom_serials array 554 * @param denom_serials array with length @e num_denom_serials of serial ID's of denominations in our DB 555 * @param selected_h hash over the gamma-selected planchets 556 * @param h_planchets running hash over all hashes of blinded planchets in the original withdraw request 557 * @param blinding_seed the blinding seed for CS denominations that was provided during withdraw; might be NULL 558 * @param age_proof_required true if the withdraw request required an age proof. 559 * @param max_age if @e age_proof_required is true, the maximum age that was set on the coins. 560 * @param noreveal_index if @e age_proof_required is true, the index that was returned by the exchange for the reveal phase. 561 * @param reserve_pub public key of the reserve 562 * @param reserve_sig signature over the withdraw operation 563 * @param execution_date when did the wallet withdraw the coin 564 * @param amount_with_fee amount that was withdrawn 565 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 566 */ 567 static enum GNUNET_GenericReturnValue 568 handle_withdrawals ( 569 struct ReserveContext *rc, 570 uint64_t rowid, 571 size_t num_denom_serials, 572 const uint64_t *denom_serials, 573 const struct TALER_HashBlindedPlanchetsP *selected_h, 574 const struct TALER_HashBlindedPlanchetsP *h_planchets, 575 const struct TALER_BlindingMasterSeedP *blinding_seed, 576 bool age_proof_required, 577 uint8_t max_age, 578 uint8_t noreveal_index, 579 const struct TALER_ReservePublicKeyP *reserve_pub, 580 const struct TALER_ReserveSignatureP *reserve_sig, 581 struct GNUNET_TIME_Timestamp execution_date, 582 const struct TALER_Amount *amount_with_fee) 583 { 584 struct ReserveSummary *rs; 585 struct TALER_Amount auditor_amount; 586 struct TALER_Amount auditor_fee; 587 struct TALER_Amount auditor_amount_with_fee; 588 struct TALER_AgeMask age_mask = { 0 }; 589 bool have_age_mask = false; 590 bool missing_denom = false; 591 enum GNUNET_DB_QueryStatus qs; 592 593 /* should be monotonically increasing */ 594 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 595 "Analyzing withdrawal row %llu\n", 596 (unsigned long long) rowid); 597 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_withdraw_serial_id)); 598 TALER_ARL_USE_PP (reserves_withdraw_serial_id) = rowid + 1; 599 600 GNUNET_assert (GNUNET_OK == 601 TALER_amount_set_zero (TALER_ARL_currency, 602 &auditor_amount)); 603 GNUNET_assert (GNUNET_OK == 604 TALER_amount_set_zero (TALER_ARL_currency, 605 &auditor_fee)); 606 GNUNET_assert (GNUNET_OK == 607 TALER_amount_set_zero (TALER_ARL_currency, 608 &auditor_amount_with_fee)); 609 rs = setup_reserve (rc, 610 reserve_pub); 611 if (NULL == rs) 612 { 613 GNUNET_break (0); 614 return GNUNET_SYSERR; 615 } 616 for (size_t i = 0; i < num_denom_serials; i++) 617 { 618 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue; 619 620 /* lookup denomination pub data (make sure denom_pub is valid, establish fees); 621 initializes wsrd.h_denomination_pub! */ 622 qs = TALER_ARL_get_denomination_info_by_serial (denom_serials[i], 623 &issue); 624 if (0 > qs) 625 { 626 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 627 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 628 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 629 "Hard database error trying to get denomination by serial %llu (%s) from database!\n", 630 (unsigned long long) denom_serials[i], 631 GNUNET_h2s (&h_planchets->hash)); 632 rc->qs = qs; 633 return GNUNET_SYSERR; 634 } 635 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 636 { 637 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 638 "Denomination #%llu not found\n", 639 (unsigned long long) denom_serials[i]); 640 report_row_inconsistency ("withdraw", 641 rowid, 642 "denomination key not found"); 643 if (global_qs < 0) 644 return GNUNET_SYSERR; 645 /* We cannot account for this planchet, but the other planchets 646 of the batch must still be processed. */ 647 missing_denom = true; 648 continue; 649 } 650 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 651 "Analyzing withdrawn denomination #%llu (%s)\n", 652 (unsigned long long) denom_serials[i], 653 TALER_amount2s (&issue->value)); 654 if (! have_age_mask) 655 { 656 age_mask = issue->age_mask; 657 have_age_mask = true; 658 } 659 else if (age_mask.bits != issue->age_mask.bits) 660 { 661 /* All denominations of one batch must share the age mask, as the 662 reserve signature is made over a single age mask. */ 663 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 664 "Denominations of withdraw row %llu disagree on the age mask\n", 665 (unsigned long long) rowid); 666 report_row_inconsistency ("withdraw", 667 rowid, 668 "denominations of the batch disagree on the age mask"); 669 if (global_qs < 0) 670 return GNUNET_SYSERR; 671 } 672 673 /* check that execution date is within withdraw range for denom_pub */ 674 GNUNET_log ( 675 GNUNET_ERROR_TYPE_DEBUG, 676 "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n", 677 (unsigned long long) issue->start.abs_time.abs_value_us, 678 (unsigned long long) issue->expire_withdraw.abs_time.abs_value_us, 679 (unsigned long long) execution_date.abs_time.abs_value_us); 680 if (GNUNET_TIME_timestamp_cmp (issue->start, 681 >, 682 execution_date) || 683 GNUNET_TIME_timestamp_cmp (issue->expire_withdraw, 684 <, 685 execution_date)) 686 { 687 struct TALER_AUDITORDB_DenominationKeyValidityWithdrawInconsistency 688 dkvwi = { 689 .problem_row_id = rowid, 690 .execution_date = execution_date.abs_time, 691 .denompub_h = issue->denom_hash, 692 .reserve_pub = *reserve_pub 693 }; 694 695 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 696 "Withdraw outside of denomination #%llu validity period detected\n", 697 (unsigned long long) denom_serials[i]); 698 qs = 699 TALER_AUDITORDB_insert_denomination_key_validity_withdraw_inconsistency 700 ( 701 TALER_ARL_adb, 702 &dkvwi); 703 704 if (qs < 0) 705 { 706 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 707 rc->qs = qs; 708 return GNUNET_SYSERR; 709 } 710 } 711 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 712 "Adding withdraw fee of denomination (%s)\n", 713 TALER_amount2s (&issue->fees.withdraw)); 714 TALER_ARL_amount_add (&auditor_amount, 715 &auditor_amount, 716 &issue->value); 717 TALER_ARL_amount_add (&auditor_fee, 718 &auditor_fee, 719 &issue->fees.withdraw); 720 { 721 struct TALER_Amount issue_amount_with_fee; 722 723 TALER_ARL_amount_add (&issue_amount_with_fee, 724 &issue->value, 725 &issue->fees.withdraw); 726 TALER_ARL_amount_add (&auditor_amount_with_fee, 727 &auditor_amount_with_fee, 728 &issue_amount_with_fee); 729 } 730 TALER_ARL_amount_add (&rs->curr_balance.withdraw_fee_balance, 731 &rs->curr_balance.withdraw_fee_balance, 732 &issue->fees.withdraw); 733 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_withdraw_fee_revenue), 734 &TALER_ARL_USE_AB (reserves_withdraw_fee_revenue), 735 &issue->fees.withdraw); 736 } /* end for all coins in batch */ 737 738 if (missing_denom) 739 { 740 /* We do not know the value of the entire batch, so we can check 741 neither the reserve signature nor the total; still debit the 742 reserve by the amount the exchange claims. The row inconsistency 743 reported above states that we could not verify it. */ 744 TALER_ARL_amount_add (&rs->total_out, 745 &rs->total_out, 746 amount_with_fee); 747 return GNUNET_OK; 748 } 749 if (age_proof_required && 750 (! have_age_mask)) 751 { 752 /* Without a denomination we do not know the age mask the reserve 753 signature was made over. */ 754 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 755 "Age-restricted withdraw row %llu has no denominations\n", 756 (unsigned long long) rowid); 757 report_row_inconsistency ("withdraw", 758 rowid, 759 "no denomination given for age-restricted withdraw"); 760 if (global_qs < 0) 761 return GNUNET_SYSERR; 762 return GNUNET_OK; 763 } 764 765 /* check reserve_sig (first: setup remaining members of wsrd) */ 766 if (GNUNET_OK != 767 TALER_wallet_withdraw_verify ( 768 &auditor_amount, 769 &auditor_fee, 770 h_planchets, 771 blinding_seed, 772 age_proof_required 773 ? &age_mask 774 : NULL, 775 age_proof_required 776 ? max_age 777 : 0, 778 reserve_pub, 779 reserve_sig)) 780 { 781 struct TALER_AUDITORDB_BadSigLosses bsl = { 782 .problem_row_id = rowid, 783 .operation = (char *) "withdraw", 784 .loss = *amount_with_fee, 785 .operation_specific_pub = reserve_pub->eddsa_pub 786 }; 787 788 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 789 "Withdraw signature invalid (row #%llu)\n", 790 (unsigned long long) rowid); 791 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 792 TALER_ARL_adb, 793 &bsl); 794 if (qs < 0) 795 { 796 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 797 rc->qs = qs; 798 return GNUNET_SYSERR; 799 } 800 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 801 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 802 amount_with_fee); 803 return GNUNET_OK; /* exit function here, we cannot add this to the legitimate withdrawals */ 804 } 805 806 if (0 != 807 TALER_amount_cmp (&auditor_amount_with_fee, 808 amount_with_fee)) 809 { 810 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 811 "Withdraw fee inconsistent (row #%llu)\n", 812 (unsigned long long) rowid); 813 report_row_inconsistency ("withdraw", 814 rowid, 815 "amount with fee from exchange does not match denomination value plus fee"); 816 if (global_qs < 0) 817 { 818 GNUNET_break (0); 819 return GNUNET_SYSERR; 820 } 821 } 822 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 823 "Reserve `%s' reduced by %s from withdraw\n", 824 TALER_B2S (reserve_pub), 825 TALER_amount2s (&auditor_amount_with_fee)); 826 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 827 "Increasing withdraw profits by fee %s\n", 828 TALER_amount2s (&auditor_fee)); 829 TALER_ARL_amount_add (&rs->total_out, 830 &rs->total_out, 831 &auditor_amount_with_fee); 832 return GNUNET_OK; 833 } 834 835 836 /** 837 * Function called with details about withdraw operations. Verifies 838 * the signature and updates the reserve's balance. 839 * 840 * @param rc our `struct ReserveContext` 841 * @param rowid unique serial ID for the refresh session in our DB 842 * @param timestamp when did we receive the recoup request 843 * @param amount how much should be added back to the reserve 844 * @param reserve_pub public key of the reserve 845 * @param coin public information about the coin, denomination signature is 846 * already verified in #check_recoup() 847 * @param denom_pub public key of the denomionation of @a coin 848 * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP 849 * @param coin_blind blinding factor used to blind the coin 850 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 851 */ 852 static enum GNUNET_GenericReturnValue 853 handle_recoup_by_reserve ( 854 struct ReserveContext *rc, 855 uint64_t rowid, 856 struct GNUNET_TIME_Timestamp timestamp, 857 const struct TALER_Amount *amount, 858 const struct TALER_ReservePublicKeyP *reserve_pub, 859 const struct TALER_CoinPublicInfo *coin, 860 const struct TALER_DenominationPublicKey *denom_pub, 861 const struct TALER_CoinSpendSignatureP *coin_sig, 862 const union GNUNET_CRYPTO_BlindingSecretP *coin_blind) 863 { 864 struct ReserveSummary *rs; 865 struct GNUNET_TIME_Timestamp expiry; 866 struct TALER_MasterSignatureP msig; 867 uint64_t rev_rowid; 868 enum GNUNET_DB_QueryStatus qs; 869 const char *rev; 870 871 (void) denom_pub; 872 /* should be monotonically increasing */ 873 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id)); 874 TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id) = rowid + 1; 875 /* We know that denom_pub matches denom_pub_hash because this 876 is how the SQL statement joined the tables. */ 877 if (GNUNET_OK != 878 TALER_wallet_recoup_verify (&coin->denom_pub_hash, 879 coin_blind, 880 &coin->coin_pub, 881 coin_sig)) 882 { 883 struct TALER_AUDITORDB_BadSigLosses bslr = { 884 .problem_row_id = rowid, 885 .operation = (char *) "recoup", 886 .loss = *amount, 887 .operation_specific_pub = coin->coin_pub.eddsa_pub 888 }; 889 890 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 891 TALER_ARL_adb, 892 &bslr); 893 894 if (qs < 0) 895 { 896 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 897 rc->qs = qs; 898 return GNUNET_SYSERR; 899 } 900 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 901 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 902 amount); 903 /* We continue here intentionally to credit the reserve anyway, 904 we already accounted for the loss, so we need to continue to 905 avoid getting another error when the reserve is drained which 906 would double-count the damage. */ 907 } 908 909 /* check that the coin was eligible for recoup!*/ 910 rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked, 911 &coin->denom_pub_hash.hash); 912 if (NULL == rev) 913 { 914 qs = TALER_EXCHANGEDB_get_denomination_revocation (TALER_ARL_edb, 915 &coin->denom_pub_hash, 916 &msig, 917 &rev_rowid); 918 if (0 > qs) 919 { 920 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 921 rc->qs = qs; 922 return GNUNET_SYSERR; 923 } 924 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 925 { 926 report_row_inconsistency ("recoup", 927 rowid, 928 "denomination key not in revocation set"); 929 if (global_qs < 0) 930 return GNUNET_SYSERR; 931 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss), 932 &TALER_ARL_USE_AB (reserves_reserve_loss), 933 amount); 934 } 935 else 936 { 937 if (GNUNET_OK != 938 TALER_exchange_offline_denomination_revoke_verify ( 939 &coin->denom_pub_hash, 940 &TALER_ARL_master_pub, 941 &msig)) 942 { 943 rev = "master signature invalid"; 944 } 945 else 946 { 947 rev = "revoked"; 948 } 949 GNUNET_assert ( 950 GNUNET_OK == 951 GNUNET_CONTAINER_multihashmap_put ( 952 rc->revoked, 953 &coin->denom_pub_hash.hash, 954 (void *) rev, 955 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); 956 } 957 } 958 else 959 { 960 rev_rowid = 0; /* reported elsewhere */ 961 } 962 if ((NULL != rev) && 963 (0 == strcmp (rev, 964 "master signature invalid"))) 965 { 966 struct TALER_AUDITORDB_BadSigLosses bslrm = { 967 .problem_row_id = rev_rowid, 968 .operation = (char *) "recoup-master", 969 .loss = *amount, 970 .operation_specific_pub = TALER_ARL_master_pub.eddsa_pub 971 }; 972 973 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 974 TALER_ARL_adb, 975 &bslrm); 976 977 if (qs < 0) 978 { 979 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 980 rc->qs = qs; 981 return GNUNET_SYSERR; 982 } 983 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 984 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 985 amount); 986 } 987 988 rs = setup_reserve (rc, 989 reserve_pub); 990 if (NULL == rs) 991 { 992 GNUNET_break (0); 993 return GNUNET_SYSERR; 994 } 995 TALER_ARL_amount_add (&rs->total_in, 996 &rs->total_in, 997 amount); 998 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 999 "Additional /recoup value to for reserve `%s' of %s\n", 1000 TALER_B2S (reserve_pub), 1001 TALER_amount2s (amount)); 1002 expiry = GNUNET_TIME_absolute_to_timestamp ( 1003 GNUNET_TIME_absolute_add (timestamp.abs_time, 1004 idle_reserve_expiration_time)); 1005 rs->a_expiration_date = GNUNET_TIME_timestamp_max (rs->a_expiration_date, 1006 expiry); 1007 return GNUNET_OK; 1008 } 1009 1010 1011 /** 1012 * Obtain the closing fee for a transfer at @a time for target 1013 * @a receiver_account. 1014 * 1015 * @param receiver_account payto:// URI of the target account 1016 * @param atime when was the transfer made 1017 * @param[out] fee set to the closing fee 1018 * @return #GNUNET_OK on success 1019 */ 1020 static enum GNUNET_GenericReturnValue 1021 get_closing_fee (const struct TALER_FullPayto receiver_account, 1022 struct GNUNET_TIME_Timestamp atime, 1023 struct TALER_Amount *fee) 1024 { 1025 struct TALER_MasterSignatureP master_sig; 1026 struct GNUNET_TIME_Timestamp start_date; 1027 struct GNUNET_TIME_Timestamp end_date; 1028 struct TALER_WireFeeSet fees; 1029 char *method; 1030 /* Only set by TALER_EXCHANGEDB_get_wire_fee() if a fee was found; 1031 0 means 'no such row' in the diagnostic below. */ 1032 uint64_t rowid = 0; 1033 1034 method = TALER_payto_get_method (receiver_account.full_payto); 1035 if (NULL == method) 1036 { 1037 report_row_inconsistency ("closing-fee", 1038 0, 1039 "specified wire address lacks method"); 1040 return GNUNET_SYSERR; 1041 } 1042 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1043 "Method is `%s'\n", 1044 method); 1045 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1046 TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb, 1047 method, 1048 atime, 1049 &rowid, 1050 &start_date, 1051 &end_date, 1052 &fees, 1053 &master_sig)) 1054 { 1055 char *diag; 1056 1057 GNUNET_asprintf (&diag, 1058 "closing fee for `%s' unavailable at %s\n", 1059 method, 1060 GNUNET_TIME_timestamp2s (atime)); 1061 report_row_inconsistency ("closing-fee", 1062 rowid, 1063 diag); 1064 GNUNET_free (diag); 1065 GNUNET_free (method); 1066 return GNUNET_SYSERR; 1067 } 1068 *fee = fees.closing; 1069 GNUNET_free (method); 1070 return GNUNET_OK; 1071 } 1072 1073 1074 /** 1075 * Function called about reserve opening operations. 1076 * 1077 * @param rc closure 1078 * @param rowid row identifier used to uniquely identify the reserve closing operation 1079 * @param reserve_payment how much to pay from the 1080 * reserve's own balance for opening the reserve 1081 * @param request_timestamp when was the request created 1082 * @param reserve_expiration desired expiration time for the reserve 1083 * @param purse_limit minimum number of purses the client 1084 * wants to have concurrently open for this reserve 1085 * @param reserve_pub public key of the reserve 1086 * @param reserve_sig signature affirming the operation 1087 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 1088 */ 1089 static enum GNUNET_GenericReturnValue 1090 handle_reserve_open ( 1091 struct ReserveContext *rc, 1092 uint64_t rowid, 1093 const struct TALER_Amount *reserve_payment, 1094 struct GNUNET_TIME_Timestamp request_timestamp, 1095 struct GNUNET_TIME_Timestamp reserve_expiration, 1096 uint32_t purse_limit, 1097 const struct TALER_ReservePublicKeyP *reserve_pub, 1098 const struct TALER_ReserveSignatureP *reserve_sig) 1099 { 1100 struct ReserveSummary *rs; 1101 enum GNUNET_DB_QueryStatus qs; 1102 1103 /* should be monotonically increasing */ 1104 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_open_serial_id)); 1105 TALER_ARL_USE_PP (reserves_reserve_open_serial_id) = rowid + 1; 1106 1107 rs = setup_reserve (rc, 1108 reserve_pub); 1109 if (NULL == rs) 1110 { 1111 GNUNET_break (0); 1112 return GNUNET_SYSERR; 1113 } 1114 if (GNUNET_OK != 1115 TALER_wallet_reserve_open_verify (reserve_payment, 1116 request_timestamp, 1117 reserve_expiration, 1118 purse_limit, 1119 reserve_pub, 1120 reserve_sig)) 1121 { 1122 struct TALER_AUDITORDB_BadSigLosses bsl = { 1123 .problem_row_id = rowid, 1124 .operation = (char *) "reserve-open", 1125 .loss = *reserve_payment, 1126 .operation_specific_pub = reserve_pub->eddsa_pub 1127 }; 1128 1129 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 1130 TALER_ARL_adb, 1131 &bsl); 1132 1133 if (qs < 0) 1134 { 1135 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1136 rc->qs = qs; 1137 return GNUNET_SYSERR; 1138 } 1139 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1140 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1141 reserve_payment); 1142 return GNUNET_OK; 1143 } 1144 TALER_ARL_amount_add (&rs->curr_balance.open_fee_balance, 1145 &rs->curr_balance.open_fee_balance, 1146 reserve_payment); 1147 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_open_fee_revenue), 1148 &TALER_ARL_USE_AB (reserves_open_fee_revenue), 1149 reserve_payment); 1150 TALER_ARL_amount_add (&rs->total_out, 1151 &rs->total_out, 1152 reserve_payment); 1153 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1154 "Additional open operation for reserve `%s' of %s\n", 1155 TALER_B2S (reserve_pub), 1156 TALER_amount2s (reserve_payment)); 1157 return GNUNET_OK; 1158 } 1159 1160 1161 /** 1162 * Function called about reserve closing operations 1163 * the aggregator triggered. 1164 * 1165 * @param rc closure 1166 * @param rowid row identifier used to uniquely identify the reserve closing operation 1167 * @param execution_date when did we execute the close operation 1168 * @param amount_with_fee how much did we debit the reserve 1169 * @param closing_fee how much did we charge for closing the reserve 1170 * @param reserve_pub public key of the reserve 1171 * @param receiver_account where did we send the funds 1172 * @param transfer_details details about the wire transfer 1173 * @param close_request_row which close request triggered the operation? 1174 * 0 if it was a timeout 1175 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 1176 */ 1177 static enum GNUNET_GenericReturnValue 1178 handle_reserve_closed ( 1179 struct ReserveContext *rc, 1180 uint64_t rowid, 1181 struct GNUNET_TIME_Timestamp execution_date, 1182 const struct TALER_Amount *amount_with_fee, 1183 const struct TALER_Amount *closing_fee, 1184 const struct TALER_ReservePublicKeyP *reserve_pub, 1185 const struct TALER_FullPayto receiver_account, 1186 const struct TALER_WireTransferIdentifierRawP *transfer_details, 1187 uint64_t close_request_row) 1188 { 1189 struct ReserveSummary *rs; 1190 1191 (void) transfer_details; 1192 /* should be monotonically increasing */ 1193 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_reserve_close_serial_id)); 1194 TALER_ARL_USE_PP (reserves_reserve_close_serial_id) = rowid + 1; 1195 1196 rs = setup_reserve (rc, 1197 reserve_pub); 1198 if (NULL == rs) 1199 { 1200 GNUNET_break (0); 1201 return GNUNET_SYSERR; 1202 } 1203 { 1204 struct TALER_Amount expected_fee; 1205 1206 /* verify closing_fee is correct! */ 1207 if (GNUNET_OK != 1208 get_closing_fee (receiver_account, 1209 execution_date, 1210 &expected_fee)) 1211 { 1212 GNUNET_break (0); 1213 } 1214 else 1215 { 1216 /* taler-exchange-closer never charges more than the remaining 1217 balance: if the closing fee is not below it, the entire balance 1218 is booked as the fee and nothing is wired. */ 1219 if (0 > TALER_amount_cmp (amount_with_fee, 1220 &expected_fee)) 1221 expected_fee = *amount_with_fee; 1222 if (0 != TALER_amount_cmp (&expected_fee, 1223 closing_fee)) 1224 { 1225 report_amount_arithmetic_inconsistency ( 1226 "closing aggregation fee", 1227 rowid, 1228 closing_fee, 1229 &expected_fee, 1230 1); 1231 if (global_qs < 0) 1232 return GNUNET_SYSERR; 1233 } 1234 } 1235 } 1236 1237 TALER_ARL_amount_add (&rs->curr_balance.close_fee_balance, 1238 &rs->curr_balance.close_fee_balance, 1239 closing_fee); 1240 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_close_fee_revenue), 1241 &TALER_ARL_USE_AB (reserves_close_fee_revenue), 1242 closing_fee); 1243 TALER_ARL_amount_add (&rs->total_out, 1244 &rs->total_out, 1245 amount_with_fee); 1246 if (0 != close_request_row) 1247 { 1248 struct TALER_ReserveSignatureP reserve_sig; 1249 struct GNUNET_TIME_Timestamp request_timestamp; 1250 struct TALER_Amount close_balance; 1251 struct TALER_Amount close_fee; 1252 struct TALER_FullPayto payto_uri = { NULL }; 1253 enum GNUNET_DB_QueryStatus qs; 1254 1255 qs = TALER_EXCHANGEDB_get_reserve_close_request_info ( 1256 TALER_ARL_edb, 1257 reserve_pub, 1258 close_request_row, 1259 &reserve_sig, 1260 &request_timestamp, 1261 &close_balance, 1262 &close_fee, 1263 &payto_uri); 1264 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 1265 { 1266 report_row_inconsistency ("reserves_close", 1267 rowid, 1268 "reserve close request unknown"); 1269 if (global_qs < 0) 1270 return GNUNET_SYSERR; 1271 } 1272 else 1273 { 1274 struct TALER_FullPaytoHashP h_payto; 1275 1276 TALER_full_payto_hash (payto_uri, 1277 &h_payto); 1278 if (GNUNET_OK != 1279 TALER_wallet_reserve_close_verify ( 1280 request_timestamp, 1281 &h_payto, 1282 reserve_pub, 1283 &reserve_sig)) 1284 { 1285 struct TALER_AUDITORDB_BadSigLosses bsl = { 1286 .problem_row_id = close_request_row, 1287 .operation = (char *) "close-request", 1288 .loss = *amount_with_fee, 1289 .operation_specific_pub = reserve_pub->eddsa_pub 1290 }; 1291 1292 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 1293 TALER_ARL_adb, 1294 &bsl); 1295 1296 if (qs < 0) 1297 { 1298 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1299 rc->qs = qs; 1300 GNUNET_free (payto_uri.full_payto); 1301 return GNUNET_SYSERR; 1302 } 1303 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1304 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1305 amount_with_fee); 1306 } 1307 } 1308 if ( (NULL == payto_uri.full_payto) && 1309 (NULL == rs->sender_account.full_payto) ) 1310 { 1311 GNUNET_break (! rs->had_ri); 1312 report_row_inconsistency ("reserves_close", 1313 rowid, 1314 "target account not verified, auditor does not know reserve"); 1315 if (global_qs < 0) 1316 return GNUNET_SYSERR; 1317 } 1318 if (NULL == payto_uri.full_payto) 1319 { 1320 if ((NULL == rs->sender_account.full_payto) || 1321 (0 != TALER_full_payto_cmp (rs->sender_account, 1322 receiver_account))) 1323 { 1324 report_row_inconsistency ("reserves_close", 1325 rowid, 1326 "target account does not match origin account"); 1327 if (global_qs < 0) 1328 return GNUNET_SYSERR; 1329 } 1330 } 1331 else 1332 { 1333 if (0 != TALER_full_payto_cmp (payto_uri, 1334 receiver_account)) 1335 { 1336 report_row_inconsistency ("reserves_close", 1337 rowid, 1338 "target account does not match origin account"); 1339 if (global_qs < 0) 1340 { 1341 GNUNET_free (payto_uri.full_payto); 1342 return GNUNET_SYSERR; 1343 } 1344 } 1345 } 1346 GNUNET_free (payto_uri.full_payto); 1347 } 1348 else 1349 { 1350 if (NULL == rs->sender_account.full_payto) 1351 { 1352 GNUNET_break (! rs->had_ri); 1353 report_row_inconsistency ("reserves_close", 1354 rowid, 1355 "target account not verified, auditor does not know reserve"); 1356 if (global_qs < 0) 1357 return GNUNET_SYSERR; 1358 } 1359 else if (0 != TALER_full_payto_cmp (rs->sender_account, 1360 receiver_account)) 1361 { 1362 report_row_inconsistency ("reserves_close", 1363 rowid, 1364 "target account does not match origin account"); 1365 if (global_qs < 0) 1366 return GNUNET_SYSERR; 1367 } 1368 } 1369 1370 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1371 "Additional closing operation for reserve `%s' of %s\n", 1372 TALER_B2S (reserve_pub), 1373 TALER_amount2s (amount_with_fee)); 1374 return GNUNET_OK; 1375 } 1376 1377 1378 /** 1379 * Function called with details about account merge requests that have been 1380 * made, with the goal of accounting for the merge fee paid by the reserve (if 1381 * applicable). 1382 * 1383 * @param rc closure 1384 * @param rowid unique serial ID for the deposit in our DB 1385 * @param reserve_pub reserve affected by the merge 1386 * @param purse_pub purse being merged 1387 * @param h_contract_terms hash over contract of the purse 1388 * @param purse_expiration when would the purse expire 1389 * @param amount total amount in the purse 1390 * @param min_age minimum age of all coins deposited into the purse 1391 * @param flags how was the purse created 1392 * @param purse_fee if a purse fee was paid, how high is it 1393 * @param merge_timestamp when was the merge approved 1394 * @param reserve_sig signature by reserve approving the merge 1395 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 1396 */ 1397 static enum GNUNET_GenericReturnValue 1398 handle_account_merged ( 1399 struct ReserveContext *rc, 1400 uint64_t rowid, 1401 const struct TALER_ReservePublicKeyP *reserve_pub, 1402 const struct TALER_PurseContractPublicKeyP *purse_pub, 1403 const struct TALER_PrivateContractHashP *h_contract_terms, 1404 struct GNUNET_TIME_Timestamp purse_expiration, 1405 const struct TALER_Amount *amount, 1406 uint32_t min_age, 1407 enum TALER_WalletAccountMergeFlags flags, 1408 const struct TALER_Amount *purse_fee, 1409 struct GNUNET_TIME_Timestamp merge_timestamp, 1410 const struct TALER_ReserveSignatureP *reserve_sig) 1411 { 1412 struct ReserveSummary *rs; 1413 enum GNUNET_DB_QueryStatus qs; 1414 1415 /* should be monotonically increasing */ 1416 GNUNET_assert (rowid >= TALER_ARL_USE_PP (reserves_account_merges_serial_id)); 1417 TALER_ARL_USE_PP (reserves_account_merges_serial_id) = rowid + 1; 1418 if (GNUNET_OK != 1419 TALER_wallet_account_merge_verify (merge_timestamp, 1420 purse_pub, 1421 purse_expiration, 1422 h_contract_terms, 1423 amount, 1424 purse_fee, 1425 min_age, 1426 flags, 1427 reserve_pub, 1428 reserve_sig)) 1429 { 1430 struct TALER_AUDITORDB_BadSigLosses bsl = { 1431 .problem_row_id = rowid, 1432 .operation = (char *) "account-merge", 1433 .loss = *purse_fee, 1434 .operation_specific_pub = reserve_pub->eddsa_pub 1435 }; 1436 1437 qs = TALER_AUDITORDB_insert_bad_sig_losses ( 1438 TALER_ARL_adb, 1439 &bsl); 1440 if (qs < 0) 1441 { 1442 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1443 rc->qs = qs; 1444 return GNUNET_SYSERR; 1445 } 1446 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1447 &TALER_ARL_USE_AB (reserves_total_bad_sig_loss), 1448 purse_fee); 1449 return GNUNET_OK; 1450 } 1451 if ((flags & TALER_WAMF_MERGE_MODE_MASK) != 1452 TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE) 1453 return GNUNET_OK; /* no impact on reserve balance */ 1454 rs = setup_reserve (rc, 1455 reserve_pub); 1456 if (NULL == rs) 1457 { 1458 GNUNET_break (0); 1459 return GNUNET_SYSERR; 1460 } 1461 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_purse_fee_revenue), 1462 &TALER_ARL_USE_AB (reserves_purse_fee_revenue), 1463 purse_fee); 1464 TALER_ARL_amount_add (&rs->curr_balance.purse_fee_balance, 1465 &rs->curr_balance.purse_fee_balance, 1466 purse_fee); 1467 TALER_ARL_amount_add (&rs->total_out, 1468 &rs->total_out, 1469 purse_fee); 1470 return GNUNET_OK; 1471 } 1472 1473 1474 /** 1475 * Function called with details about a purse that was merged into an account. 1476 * Only updates the reserve balance, the actual verifications are done in the 1477 * purse helper. 1478 * 1479 * @param rc closure 1480 * @param rowid unique serial ID for the refund in our DB 1481 * @param purse_pub public key of the purse 1482 * @param reserve_pub which reserve is the purse credited to 1483 * @param purse_value what is the target value of the purse 1484 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 1485 */ 1486 static enum GNUNET_GenericReturnValue 1487 purse_decision_cb (struct ReserveContext *rc, 1488 uint64_t rowid, 1489 const struct TALER_PurseContractPublicKeyP *purse_pub, 1490 const struct TALER_ReservePublicKeyP *reserve_pub, 1491 const struct TALER_Amount *purse_value) 1492 { 1493 struct ReserveSummary *rs; 1494 1495 GNUNET_assert (rowid >= TALER_ARL_USE_PP ( 1496 reserves_purse_decisions_serial_id)); /* should be monotonically increasing */ 1497 TALER_ARL_USE_PP (reserves_purse_decisions_serial_id) = rowid + 1; 1498 rs = setup_reserve (rc, 1499 reserve_pub); 1500 if (NULL == rs) 1501 { 1502 GNUNET_break (0); 1503 return GNUNET_SYSERR; 1504 } 1505 TALER_ARL_amount_add (&rs->total_in, 1506 &rs->total_in, 1507 purse_value); 1508 return GNUNET_OK; 1509 } 1510 1511 1512 /** 1513 * Check that the reserve summary matches what the exchange database 1514 * thinks about the reserve, and update our own state of the reserve. 1515 * 1516 * Remove all reserves that we are happy with from the DB. 1517 * 1518 * @param cls our `struct ReserveContext` 1519 * @param key hash of the reserve public key 1520 * @param value a `struct ReserveSummary` 1521 * @return #GNUNET_OK to process more entries 1522 */ 1523 static enum GNUNET_GenericReturnValue 1524 verify_reserve_balance (void *cls, 1525 const struct GNUNET_HashCode *key, 1526 void *value) 1527 { 1528 struct ReserveContext *rc = cls; 1529 struct ReserveSummary *rs = value; 1530 struct TALER_Amount mbalance; 1531 struct TALER_Amount nbalance; 1532 enum GNUNET_DB_QueryStatus qs; 1533 enum GNUNET_GenericReturnValue ret; 1534 1535 ret = GNUNET_OK; 1536 /* Check our reserve summary balance calculation shows that 1537 the reserve balance is acceptable (i.e. non-negative) */ 1538 TALER_ARL_amount_add (&mbalance, 1539 &rs->total_in, 1540 &rs->prev_balance.reserve_balance); 1541 if (TALER_ARL_SR_INVALID_NEGATIVE == 1542 TALER_ARL_amount_subtract_neg (&nbalance, 1543 &mbalance, 1544 &rs->total_out)) 1545 { 1546 struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiil = { 1547 .reserve_pub = rs->reserve_pub.eddsa_pub, 1548 .inconsistency_gain = false 1549 }; 1550 1551 TALER_ARL_amount_subtract (&rbiil.inconsistency_amount, 1552 &rs->total_out, 1553 &mbalance); 1554 /* Note: 'curr_balance' is folded into 'prev_balance' at the end of 1555 this function, so we must accumulate into 'curr_balance' here 1556 (which starts at zero), not into 'prev_balance'. */ 1557 TALER_ARL_amount_add (&rs->curr_balance.reserve_loss, 1558 &rs->curr_balance.reserve_loss, 1559 &rbiil.inconsistency_amount); 1560 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss), 1561 &TALER_ARL_USE_AB (reserves_reserve_loss), 1562 &rbiil.inconsistency_amount); 1563 qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency ( 1564 TALER_ARL_adb, 1565 &rbiil); 1566 1567 if (qs < 0) 1568 { 1569 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1570 rc->qs = qs; 1571 return GNUNET_SYSERR; 1572 } 1573 /* Continue with a reserve balance of zero */ 1574 GNUNET_assert (GNUNET_OK == 1575 TALER_amount_set_zero (TALER_ARL_currency, 1576 &rs->curr_balance.reserve_balance)); 1577 nbalance = rs->curr_balance.reserve_balance; 1578 } 1579 else 1580 { 1581 /* Update remaining reserve balance! */ 1582 rs->curr_balance.reserve_balance = nbalance; 1583 } 1584 1585 if (internal_checks) 1586 { 1587 /* Now check OUR balance calculation vs. the one the exchange has 1588 in its database. This can only be done when we are doing an 1589 internal audit, as otherwise the balance of the 'reserves' table 1590 is not replicated at the auditor. */ 1591 struct TALER_EXCHANGEDB_Reserve reserve = { 1592 .pub = rs->reserve_pub 1593 }; 1594 1595 qs = TALER_EXCHANGEDB_get_reserve (TALER_ARL_edb, 1596 &reserve); 1597 if (0 > qs) 1598 { 1599 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1600 rc->qs = qs; 1601 return GNUNET_SYSERR; 1602 } 1603 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1604 { 1605 /* If the exchange doesn't have this reserve in the summary, it 1606 is like the exchange 'lost' that amount from its records, 1607 making an illegitimate gain over the amount it dropped. 1608 We don't add the amount to some total simply because it is 1609 not an actualized gain and could be trivially corrected by 1610 restoring the summary. */ 1611 struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig = { 1612 .reserve_pub = rs->reserve_pub.eddsa_pub, 1613 .inconsistency_amount = nbalance, 1614 .inconsistency_gain = true 1615 }; 1616 1617 qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency ( 1618 TALER_ARL_adb, 1619 &rbiig); 1620 1621 if (qs < 0) 1622 { 1623 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1624 rc->qs = qs; 1625 return GNUNET_SYSERR; 1626 } 1627 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1628 { 1629 GNUNET_break (0); 1630 qs = GNUNET_DB_STATUS_HARD_ERROR; 1631 } 1632 rc->qs = qs; 1633 } 1634 else 1635 { 1636 /* Check that exchange's balance matches our expected balance for the reserve */ 1637 if (0 != TALER_amount_cmp (&rs->curr_balance.reserve_balance, 1638 &reserve.balance)) 1639 { 1640 struct TALER_Amount delta; 1641 bool gain; 1642 1643 if (0 < TALER_amount_cmp (&rs->curr_balance.reserve_balance, 1644 &reserve.balance)) 1645 { 1646 /* balance > reserve.balance: the exchange claims to owe 1647 less than we computed, so it made an illegitimate gain */ 1648 gain = true; 1649 TALER_ARL_amount_subtract (&delta, 1650 &rs->curr_balance.reserve_balance, 1651 &reserve.balance); 1652 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1653 total_balance_summary_delta_plus), 1654 &TALER_ARL_USE_AB ( 1655 total_balance_summary_delta_plus), 1656 &delta); 1657 } 1658 else 1659 { 1660 /* balance < reserve.balance */ 1661 gain = false; 1662 TALER_ARL_amount_subtract (&delta, 1663 &reserve.balance, 1664 &rs->curr_balance.reserve_balance); 1665 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1666 total_balance_summary_delta_minus), 1667 &TALER_ARL_USE_AB ( 1668 total_balance_summary_delta_minus), 1669 &delta); 1670 } 1671 1672 { 1673 struct TALER_AUDITORDB_ReserveBalanceInsufficientInconsistency rbiig = 1674 { 1675 .reserve_pub = rs->reserve_pub.eddsa_pub, 1676 .inconsistency_amount = delta, 1677 .inconsistency_gain = gain 1678 }; 1679 1680 qs = TALER_AUDITORDB_insert_reserve_balance_insufficient_inconsistency 1681 ( 1682 TALER_ARL_adb, 1683 &rbiig); 1684 } 1685 if (qs < 0) 1686 { 1687 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1688 rc->qs = qs; 1689 return GNUNET_SYSERR; 1690 } 1691 1692 { 1693 struct TALER_AUDITORDB_ReserveBalanceSummaryWrongInconsistency rbswi = 1694 { 1695 .exchange_amount = reserve.balance, 1696 .auditor_amount = rs->curr_balance.reserve_balance, 1697 .reserve_pub = rs->reserve_pub 1698 }; 1699 1700 qs = 1701 TALER_AUDITORDB_insert_reserve_balance_summary_wrong_inconsistency 1702 ( 1703 TALER_ARL_adb, 1704 &rbswi); 1705 } 1706 if (qs < 0) 1707 { 1708 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1709 rc->qs = qs; 1710 return GNUNET_SYSERR; 1711 } 1712 } 1713 } 1714 } /* end of 'if (internal_checks)' */ 1715 1716 /* Check that reserve is being closed if it is past its expiration date 1717 (and the closing fee would not exceed the remaining balance) */ 1718 if (GNUNET_TIME_relative_cmp (CLOSING_GRACE_PERIOD, 1719 <, 1720 GNUNET_TIME_absolute_get_duration ( 1721 rs->a_expiration_date.abs_time))) 1722 { 1723 /* Reserve is expired */ 1724 struct TALER_Amount cfee; 1725 1726 if ( (NULL != rs->sender_account.full_payto) && 1727 (GNUNET_OK == 1728 get_closing_fee (rs->sender_account, 1729 rs->a_expiration_date, 1730 &cfee)) ) 1731 { 1732 /* We got the closing fee */ 1733 if (1 == TALER_amount_cmp (&nbalance, 1734 &cfee)) 1735 { 1736 struct TALER_AUDITORDB_ReserveNotClosedInconsistency rnci = { 1737 .reserve_pub = rs->reserve_pub, 1738 .expiration_time = rs->a_expiration_date.abs_time, 1739 .balance = nbalance, 1740 .diagnostic = rs->sender_account.full_payto 1741 }; 1742 1743 /* remaining balance (according to us) exceeds closing fee */ 1744 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1745 total_balance_reserve_not_closed), 1746 &TALER_ARL_USE_AB ( 1747 total_balance_reserve_not_closed), 1748 &rnci.balance); 1749 qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency ( 1750 TALER_ARL_adb, 1751 &rnci); 1752 if (qs < 0) 1753 { 1754 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1755 rc->qs = qs; 1756 return GNUNET_SYSERR; 1757 } 1758 } 1759 } 1760 else 1761 { 1762 /* We failed to determine the closing fee, complain! */ 1763 struct TALER_AUDITORDB_ReserveNotClosedInconsistency rncid = { 1764 .reserve_pub = rs->reserve_pub, 1765 .balance = nbalance, 1766 .expiration_time = rs->a_expiration_date.abs_time, 1767 .diagnostic = (char *) "could not determine closing fee" 1768 }; 1769 1770 /* Even if we don't know the closing fee, update the 1771 total_balance_reserve_not_closed */ 1772 TALER_ARL_amount_add (&TALER_ARL_USE_AB ( 1773 total_balance_reserve_not_closed), 1774 &TALER_ARL_USE_AB ( 1775 total_balance_reserve_not_closed), 1776 &nbalance); 1777 qs = TALER_AUDITORDB_insert_reserve_not_closed_inconsistency ( 1778 TALER_ARL_adb, 1779 &rncid); 1780 if (qs < 0) 1781 { 1782 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1783 rc->qs = qs; 1784 return GNUNET_SYSERR; 1785 } 1786 } 1787 } 1788 /* We already computed the 'new' balance in 'curr_balance' 1789 to include the previous balance, so this one is just 1790 an assignment, not adding up! */ 1791 rs->prev_balance.reserve_balance = rs->curr_balance.reserve_balance; 1792 1793 /* Add up new totals to previous totals */ 1794 TALER_ARL_amount_add (&rs->prev_balance.reserve_loss, 1795 &rs->prev_balance.reserve_loss, 1796 &rs->curr_balance.reserve_loss); 1797 TALER_ARL_amount_add (&rs->prev_balance.withdraw_fee_balance, 1798 &rs->prev_balance.withdraw_fee_balance, 1799 &rs->curr_balance.withdraw_fee_balance); 1800 TALER_ARL_amount_add (&rs->prev_balance.close_fee_balance, 1801 &rs->prev_balance.close_fee_balance, 1802 &rs->curr_balance.close_fee_balance); 1803 TALER_ARL_amount_add (&rs->prev_balance.purse_fee_balance, 1804 &rs->prev_balance.purse_fee_balance, 1805 &rs->curr_balance.purse_fee_balance); 1806 TALER_ARL_amount_add (&rs->prev_balance.open_fee_balance, 1807 &rs->prev_balance.open_fee_balance, 1808 &rs->curr_balance.open_fee_balance); 1809 TALER_ARL_amount_add (&rs->prev_balance.history_fee_balance, 1810 &rs->prev_balance.history_fee_balance, 1811 &rs->curr_balance.history_fee_balance); 1812 /* Update global balance: add incoming first, then try 1813 to subtract outgoing... */ 1814 TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_total_balance), 1815 &TALER_ARL_USE_AB (reserves_reserve_total_balance), 1816 &rs->total_in); 1817 { 1818 struct TALER_Amount r; 1819 1820 if (TALER_ARL_SR_INVALID_NEGATIVE == 1821 TALER_ARL_amount_subtract_neg (&r, 1822 &TALER_ARL_USE_AB ( 1823 reserves_reserve_total_balance), 1824 &rs->total_out)) 1825 { 1826 /* We could not reduce our total balance, i.e. exchange allowed IN TOTAL (!) 1827 to be withdrawn more than it was IN TOTAL ever given (exchange balance 1828 went negative!). Woopsie. Calculate how badly it went and log. */ 1829 report_amount_arithmetic_inconsistency ("global escrow balance", 1830 0, 1831 &TALER_ARL_USE_AB ( 1832 reserves_reserve_total_balance), /* what we had */ 1833 &rs->total_out, /* what we needed */ 1834 0 /* specific profit/loss does not apply to the total summary */ 1835 ); 1836 if (global_qs < 0) 1837 return GNUNET_SYSERR; 1838 /* We unexpectedly went negative, so a sane value to continue from 1839 would be zero. */ 1840 GNUNET_assert (GNUNET_OK == 1841 TALER_amount_set_zero (TALER_ARL_currency, 1842 &TALER_ARL_USE_AB ( 1843 reserves_reserve_total_balance))); 1844 } 1845 else 1846 { 1847 TALER_ARL_USE_AB (reserves_reserve_total_balance) = r; 1848 } 1849 } 1850 if (TALER_amount_is_zero (&rs->prev_balance.reserve_balance)) 1851 { 1852 /* balance is zero, drop reserve details (and then do not update/insert) */ 1853 if (rs->had_ri) 1854 { 1855 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1856 "Final balance of reserve `%s' is zero, dropping it\n", 1857 TALER_B2S (&rs->reserve_pub)); 1858 qs = TALER_AUDITORDB_delete_reserve_info (TALER_ARL_adb, 1859 &rs->reserve_pub); 1860 if (0 >= qs) 1861 { 1862 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1863 ret = GNUNET_SYSERR; 1864 rc->qs = qs; 1865 } 1866 } 1867 else 1868 { 1869 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1870 "Final balance of reserve `%s' is zero, no need to remember it\n", 1871 TALER_B2S (&rs->reserve_pub)); 1872 } 1873 } 1874 else 1875 { 1876 /* balance is non-zero, persist for future audits */ 1877 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1878 "Remembering final balance of reserve `%s' as %s\n", 1879 TALER_B2S (&rs->reserve_pub), 1880 TALER_amount2s (&rs->prev_balance.reserve_balance)); 1881 if (rs->had_ri) 1882 qs = TALER_AUDITORDB_update_reserve_info (TALER_ARL_adb, 1883 &rs->reserve_pub, 1884 &rs->prev_balance, 1885 rs->a_expiration_date); 1886 else 1887 qs = TALER_AUDITORDB_insert_reserve_info (TALER_ARL_adb, 1888 &rs->reserve_pub, 1889 &rs->prev_balance, 1890 rs->a_expiration_date, 1891 rs->sender_account); 1892 if (0 >= qs) 1893 { 1894 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1895 ret = GNUNET_SYSERR; 1896 rc->qs = qs; 1897 } 1898 } 1899 /* now we can discard the cached entry */ 1900 GNUNET_assert (GNUNET_YES == 1901 GNUNET_CONTAINER_multihashmap_remove (rc->reserves, 1902 key, 1903 rs)); 1904 GNUNET_free (rs->sender_account.full_payto); 1905 GNUNET_free (rs); 1906 return ret; 1907 } 1908 1909 1910 #define CHECK_DB() do { \ 1911 if (qs < 0) { \ 1912 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); \ 1913 goto cleanup; \ 1914 } \ 1915 if (global_qs < 0) { \ 1916 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == global_qs); \ 1917 qs = global_qs; \ 1918 goto cleanup; \ 1919 } \ 1920 if (rc.qs < 0) { \ 1921 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == rc.qs); \ 1922 qs = rc.qs; \ 1923 goto cleanup; \ 1924 } \ 1925 } while (0) 1926 1927 1928 /** 1929 * Analyze reserves for being well-formed. 1930 * 1931 * @param cls NULL 1932 * @return transaction status code 1933 */ 1934 static enum GNUNET_DB_QueryStatus 1935 analyze_reserves (void *cls) 1936 { 1937 struct ReserveContext rc = { 1938 .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT 1939 }; 1940 enum GNUNET_DB_QueryStatus qs; 1941 1942 (void) cls; 1943 global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 1944 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1945 "Analyzing reserves\n"); 1946 qs = TALER_AUDITORDB_get_auditor_progress ( 1947 TALER_ARL_adb, 1948 TALER_ARL_GET_PP (reserves_reserve_in_serial_id), 1949 TALER_ARL_GET_PP (reserves_withdraw_serial_id), 1950 TALER_ARL_GET_PP (reserves_reserve_recoup_serial_id), 1951 TALER_ARL_GET_PP (reserves_reserve_open_serial_id), 1952 TALER_ARL_GET_PP (reserves_reserve_close_serial_id), 1953 TALER_ARL_GET_PP (reserves_purse_decisions_serial_id), 1954 TALER_ARL_GET_PP (reserves_account_merges_serial_id), 1955 TALER_ARL_GET_PP (reserves_history_requests_serial_id), 1956 NULL); 1957 if (0 > qs) 1958 { 1959 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 1960 return qs; 1961 } 1962 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1963 { 1964 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1965 "First analysis using this auditor, starting audit from scratch\n"); 1966 } 1967 else 1968 { 1969 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1970 "Resuming reserve audit at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n", 1971 (unsigned long long) TALER_ARL_USE_PP ( 1972 reserves_reserve_in_serial_id), 1973 (unsigned long long) TALER_ARL_USE_PP ( 1974 reserves_withdraw_serial_id), 1975 (unsigned long long) TALER_ARL_USE_PP ( 1976 reserves_reserve_recoup_serial_id), 1977 (unsigned long long) TALER_ARL_USE_PP ( 1978 reserves_reserve_open_serial_id), 1979 (unsigned long long) TALER_ARL_USE_PP ( 1980 reserves_reserve_close_serial_id), 1981 (unsigned long long) TALER_ARL_USE_PP ( 1982 reserves_purse_decisions_serial_id), 1983 (unsigned long long) TALER_ARL_USE_PP ( 1984 reserves_account_merges_serial_id), 1985 (unsigned long long) TALER_ARL_USE_PP ( 1986 reserves_history_requests_serial_id)); 1987 } 1988 qs = TALER_AUDITORDB_get_balance ( 1989 TALER_ARL_adb, 1990 TALER_ARL_GET_AB (reserves_reserve_total_balance), 1991 TALER_ARL_GET_AB (reserves_reserve_loss), 1992 TALER_ARL_GET_AB (reserves_withdraw_fee_revenue), 1993 TALER_ARL_GET_AB (reserves_close_fee_revenue), 1994 TALER_ARL_GET_AB (reserves_purse_fee_revenue), 1995 TALER_ARL_GET_AB (reserves_open_fee_revenue), 1996 TALER_ARL_GET_AB (reserves_history_fee_revenue), 1997 TALER_ARL_GET_AB (reserves_total_bad_sig_loss), 1998 TALER_ARL_GET_AB (total_balance_reserve_not_closed), 1999 TALER_ARL_GET_AB (reserves_total_arithmetic_delta_plus), 2000 TALER_ARL_GET_AB (reserves_total_arithmetic_delta_minus), 2001 TALER_ARL_GET_AB (total_balance_summary_delta_plus), 2002 TALER_ARL_GET_AB (total_balance_summary_delta_minus), 2003 NULL); 2004 if (qs < 0) 2005 { 2006 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 2007 return qs; 2008 } 2009 rc.reserves = GNUNET_CONTAINER_multihashmap_create (512, 2010 GNUNET_NO); 2011 rc.revoked = GNUNET_CONTAINER_multihashmap_create (4, 2012 GNUNET_NO); 2013 2014 qs = TALER_EXCHANGEDB_iterate_reserves_in_above_serial_id ( 2015 TALER_ARL_edb, 2016 TALER_ARL_USE_PP (reserves_reserve_in_serial_id), 2017 &handle_reserve_in, 2018 &rc); 2019 CHECK_DB (); 2020 qs = TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id ( 2021 TALER_ARL_edb, 2022 TALER_ARL_USE_PP (reserves_withdraw_serial_id), 2023 &handle_withdrawals, 2024 &rc); 2025 CHECK_DB (); 2026 qs = TALER_EXCHANGEDB_iterate_recoups_above_serial_id ( 2027 TALER_ARL_edb, 2028 TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id), 2029 &handle_recoup_by_reserve, 2030 &rc); 2031 CHECK_DB (); 2032 qs = TALER_EXCHANGEDB_iterate_reserve_open_above_serial_id ( 2033 TALER_ARL_edb, 2034 TALER_ARL_USE_PP (reserves_reserve_open_serial_id), 2035 &handle_reserve_open, 2036 &rc); 2037 CHECK_DB (); 2038 qs = TALER_EXCHANGEDB_iterate_reserve_closed_above_serial_id ( 2039 TALER_ARL_edb, 2040 TALER_ARL_USE_PP (reserves_reserve_close_serial_id), 2041 &handle_reserve_closed, 2042 &rc); 2043 CHECK_DB (); 2044 /* process purse_decisions (to credit reserve) */ 2045 qs = TALER_EXCHANGEDB_iterate_purse_decisions_above_serial_id ( 2046 TALER_ARL_edb, 2047 TALER_ARL_USE_PP (reserves_purse_decisions_serial_id), 2048 false, /* only go for merged purses! */ 2049 &purse_decision_cb, 2050 &rc); 2051 CHECK_DB (); 2052 /* Charge purse fee! */ 2053 2054 qs = TALER_EXCHANGEDB_iterate_account_merges_above_serial_id ( 2055 TALER_ARL_edb, 2056 TALER_ARL_USE_PP (reserves_account_merges_serial_id), 2057 &handle_account_merged, 2058 &rc); 2059 CHECK_DB (); 2060 GNUNET_CONTAINER_multihashmap_iterate (rc.reserves, 2061 &verify_reserve_balance, 2062 &rc); 2063 CHECK_DB (); 2064 GNUNET_break (0 == 2065 GNUNET_CONTAINER_multihashmap_size (rc.reserves)); 2066 2067 qs = TALER_AUDITORDB_insert_balance ( 2068 TALER_ARL_adb, 2069 TALER_ARL_SET_AB (reserves_reserve_total_balance), 2070 TALER_ARL_SET_AB (reserves_reserve_loss), 2071 TALER_ARL_SET_AB (reserves_withdraw_fee_revenue), 2072 TALER_ARL_SET_AB (reserves_close_fee_revenue), 2073 TALER_ARL_SET_AB (reserves_purse_fee_revenue), 2074 TALER_ARL_SET_AB (reserves_open_fee_revenue), 2075 TALER_ARL_SET_AB (reserves_history_fee_revenue), 2076 TALER_ARL_SET_AB (reserves_total_bad_sig_loss), 2077 TALER_ARL_SET_AB (total_balance_reserve_not_closed), 2078 TALER_ARL_SET_AB (reserves_total_arithmetic_delta_plus), 2079 TALER_ARL_SET_AB (reserves_total_arithmetic_delta_minus), 2080 TALER_ARL_SET_AB (total_balance_summary_delta_plus), 2081 TALER_ARL_SET_AB (total_balance_summary_delta_minus), 2082 NULL); 2083 if (0 > qs) 2084 { 2085 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 2086 goto cleanup; 2087 } 2088 2089 qs = TALER_AUDITORDB_insert_auditor_progress ( 2090 TALER_ARL_adb, 2091 TALER_ARL_SET_PP (reserves_reserve_in_serial_id), 2092 TALER_ARL_SET_PP (reserves_withdraw_serial_id), 2093 TALER_ARL_SET_PP (reserves_reserve_recoup_serial_id), 2094 TALER_ARL_SET_PP (reserves_reserve_open_serial_id), 2095 TALER_ARL_SET_PP (reserves_reserve_close_serial_id), 2096 TALER_ARL_SET_PP (reserves_purse_decisions_serial_id), 2097 TALER_ARL_SET_PP (reserves_account_merges_serial_id), 2098 TALER_ARL_SET_PP (reserves_history_requests_serial_id), 2099 NULL); 2100 if (0 > qs) 2101 { 2102 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2103 "Failed to update auditor DB, not recording progress\n"); 2104 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 2105 goto cleanup; 2106 } 2107 2108 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2109 "Concluded reserve audit step at %llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu\n", 2110 (unsigned long long) TALER_ARL_USE_PP ( 2111 reserves_reserve_in_serial_id), 2112 (unsigned long long) TALER_ARL_USE_PP ( 2113 reserves_withdraw_serial_id), 2114 (unsigned long long) TALER_ARL_USE_PP ( 2115 reserves_reserve_recoup_serial_id), 2116 (unsigned long long) TALER_ARL_USE_PP ( 2117 reserves_reserve_open_serial_id), 2118 (unsigned long long) TALER_ARL_USE_PP ( 2119 reserves_reserve_close_serial_id), 2120 (unsigned long long) TALER_ARL_USE_PP ( 2121 reserves_purse_decisions_serial_id), 2122 (unsigned long long) TALER_ARL_USE_PP ( 2123 reserves_account_merges_serial_id), 2124 (unsigned long long) TALER_ARL_USE_PP ( 2125 reserves_history_requests_serial_id)); 2126 qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 2127 cleanup: 2128 GNUNET_CONTAINER_multihashmap_destroy (rc.reserves); 2129 GNUNET_CONTAINER_multihashmap_destroy (rc.revoked); 2130 return qs; 2131 } 2132 2133 2134 #undef CHECK_DB 2135 2136 2137 /** 2138 * Function called on events received from Postgres. 2139 * 2140 * @param cls closure, NULL 2141 * @param extra additional event data provided 2142 * @param extra_size number of bytes in @a extra 2143 */ 2144 static void 2145 db_notify (void *cls, 2146 const void *extra, 2147 size_t extra_size) 2148 { 2149 (void) cls; 2150 (void) extra; 2151 (void) extra_size; 2152 2153 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2154 "Received notification to wake reserves helper\n"); 2155 if (GNUNET_OK != 2156 TALER_ARL_setup_sessions_and_run (&analyze_reserves, 2157 NULL)) 2158 { 2159 GNUNET_SCHEDULER_shutdown (); 2160 global_ret = EXIT_FAILURE; 2161 return; 2162 } 2163 } 2164 2165 2166 /** 2167 * Function called on shutdown. 2168 */ 2169 static void 2170 do_shutdown (void *cls) 2171 { 2172 (void) cls; 2173 if (NULL != eh) 2174 { 2175 TALER_AUDITORDB_event_listen_cancel (eh); 2176 eh = NULL; 2177 } 2178 TALER_ARL_done (); 2179 } 2180 2181 2182 /** 2183 * Main function that will be run. 2184 * 2185 * @param cls closure 2186 * @param args remaining command-line arguments 2187 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 2188 * @param c configuration 2189 */ 2190 static void 2191 run (void *cls, 2192 char *const *args, 2193 const char *cfgfile, 2194 const struct GNUNET_CONFIGURATION_Handle *c) 2195 { 2196 (void) cls; 2197 (void) args; 2198 (void) cfgfile; 2199 2200 cfg = c; 2201 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 2202 NULL); 2203 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2204 "Launching reserves auditor\n"); 2205 if (EXIT_SUCCESS != 2206 (global_ret = TALER_ARL_init (c))) 2207 { 2208 return; 2209 } 2210 if (GNUNET_OK != 2211 GNUNET_CONFIGURATION_get_value_time (TALER_ARL_cfg, 2212 "exchangedb", 2213 "IDLE_RESERVE_EXPIRATION_TIME", 2214 &idle_reserve_expiration_time)) 2215 { 2216 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 2217 "exchangedb", 2218 "IDLE_RESERVE_EXPIRATION_TIME"); 2219 GNUNET_SCHEDULER_shutdown (); 2220 global_ret = EXIT_FAILURE; 2221 return; 2222 } 2223 if (test_mode != 1) 2224 { 2225 struct GNUNET_DB_EventHeaderP es = { 2226 .size = htons (sizeof (es)), 2227 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_RESERVES) 2228 }; 2229 2230 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2231 "Running helper indefinitely\n"); 2232 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 2233 &es, 2234 GNUNET_TIME_UNIT_FOREVER_REL, 2235 &db_notify, 2236 NULL); 2237 } 2238 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2239 "Starting audit\n"); 2240 if (GNUNET_OK != 2241 TALER_ARL_setup_sessions_and_run (&analyze_reserves, 2242 NULL)) 2243 { 2244 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2245 "Audit failed\n"); 2246 GNUNET_SCHEDULER_shutdown (); 2247 global_ret = EXIT_FAILURE; 2248 return; 2249 } 2250 } 2251 2252 2253 /** 2254 * The main function to check the database's handling of reserves. 2255 * 2256 * @param argc number of arguments from the command line 2257 * @param argv command line arguments 2258 * @return 0 ok, 1 on error 2259 */ 2260 int 2261 main (int argc, 2262 char *const *argv) 2263 { 2264 const struct GNUNET_GETOPT_CommandLineOption options[] = { 2265 GNUNET_GETOPT_option_flag ('i', 2266 "internal", 2267 "perform checks only applicable for exchange-internal audits", 2268 &internal_checks), 2269 GNUNET_GETOPT_option_flag ('t', 2270 "test", 2271 "run in test mode and exit when idle", 2272 &test_mode), 2273 GNUNET_GETOPT_option_timetravel ('T', 2274 "timetravel"), 2275 GNUNET_GETOPT_OPTION_END 2276 }; 2277 enum GNUNET_GenericReturnValue ret; 2278 2279 ret = GNUNET_PROGRAM_run ( 2280 TALER_AUDITOR_project_data (), 2281 argc, 2282 argv, 2283 "taler-helper-auditor-reserves", 2284 gettext_noop ("Audit Taler exchange reserve handling"), 2285 options, 2286 &run, 2287 NULL); 2288 if (GNUNET_SYSERR == ret) 2289 return EXIT_NOTCONFIGURED; 2290 if (GNUNET_NO == ret) 2291 return EXIT_SUCCESS; 2292 return global_ret; 2293 } 2294 2295 2296 /* end of taler-helper-auditor-reserves.c */