get_reserve_history.c (31061B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file get_reserve_history.c 18 * @brief Obtain (parts of) the history of a reserve. 19 * @author Christian Grothoff 20 */ 21 #include "taler/taler_error_codes.h" 22 #include "taler/taler_pq_lib.h" 23 #include "exchange-database/get_reserve_history.h" 24 #include "exchange-database/start_read_committed.h" 25 #include "exchange-database/commit.h" 26 #include "exchange-database/rollback.h" 27 #include "helper.h" 28 29 /** 30 * How often do we re-try when encountering DB serialization issues? 31 * (We are read-only, so can only happen due to concurrent insert, 32 * which should be very rare.) 33 */ 34 #define RETRIES 3 35 36 37 /** 38 * Closure for callbacks invoked via #TALER_EXCHANGEDB_get_reserve_history(). 39 */ 40 struct ReserveHistoryContext 41 { 42 43 /** 44 * Which reserve are we building the history for? 45 */ 46 const struct TALER_ReservePublicKeyP *reserve_pub; 47 48 /** 49 * Where we build the history. 50 */ 51 struct TALER_EXCHANGEDB_ReserveHistory *rh; 52 53 /** 54 * Tail of @e rh list. 55 */ 56 struct TALER_EXCHANGEDB_ReserveHistory *rh_tail; 57 58 /** 59 * Plugin context. 60 */ 61 struct TALER_EXCHANGEDB_PostgresContext *pg; 62 63 /** 64 * Current reserve_history_serial_id being processed, 65 * set before each sub-table callback. 66 */ 67 uint64_t current_history_offset; 68 69 /** 70 * Set to true on serious internal errors during 71 * the callbacks. 72 */ 73 bool failed; 74 }; 75 76 77 /** 78 * Append and return a fresh element to the reserve 79 * history kept in @a rhc. 80 * 81 * @param rhc where the history is kept 82 * @return the fresh element that was added 83 */ 84 static struct TALER_EXCHANGEDB_ReserveHistory * 85 append_rh (struct ReserveHistoryContext *rhc) 86 { 87 struct TALER_EXCHANGEDB_ReserveHistory *tail; 88 89 tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory); 90 tail->history_offset = rhc->current_history_offset; 91 if (NULL != rhc->rh_tail) 92 { 93 rhc->rh_tail->next = tail; 94 rhc->rh_tail = tail; 95 } 96 else 97 { 98 rhc->rh_tail = tail; 99 rhc->rh = tail; 100 } 101 return tail; 102 } 103 104 105 /** 106 * Add bank transfers to result set for #TALER_EXCHANGEDB_get_reserve_history. 107 * 108 * @param cls a `struct ReserveHistoryContext *` 109 * @param result SQL result 110 * @param num_results number of rows in @a result 111 */ 112 static void 113 add_bank_to_exchange (void *cls, 114 PGresult *result, 115 unsigned int num_results) 116 { 117 struct ReserveHistoryContext *rhc = cls; 118 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 119 120 while (0 < num_results) 121 { 122 struct TALER_EXCHANGEDB_BankTransfer *bt; 123 struct TALER_EXCHANGEDB_ReserveHistory *tail; 124 125 bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer); 126 { 127 struct GNUNET_PQ_ResultSpec rs[] = { 128 GNUNET_PQ_result_spec_uint64 ("wire_reference", 129 &bt->wire_reference), 130 TALER_PQ_RESULT_SPEC_AMOUNT ("credit", 131 &bt->amount), 132 GNUNET_PQ_result_spec_timestamp ("execution_date", 133 &bt->execution_date), 134 GNUNET_PQ_result_spec_string ("sender_account_details", 135 &bt->sender_account_details.full_payto), 136 GNUNET_PQ_result_spec_end 137 }; 138 139 if (GNUNET_OK != 140 GNUNET_PQ_extract_result (result, 141 rs, 142 --num_results)) 143 { 144 GNUNET_break (0); 145 GNUNET_free (bt); 146 rhc->failed = true; 147 return; 148 } 149 } 150 bt->reserve_pub = *rhc->reserve_pub; 151 tail = append_rh (rhc); 152 tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; 153 tail->details.bank = bt; 154 } /* end of 'while (0 < rows)' */ 155 } 156 157 158 /** 159 * Add coin withdrawals to result set for #TALER_EXCHANGEDB_get_reserve_history. 160 * 161 * @param cls a `struct ReserveHistoryContext *` 162 * @param result SQL result 163 * @param num_results number of rows in @a result 164 */ 165 static void 166 add_withdraw (void *cls, 167 PGresult *result, 168 unsigned int num_results) 169 { 170 struct ReserveHistoryContext *rhc = cls; 171 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 172 173 while (0 < num_results) 174 { 175 struct TALER_EXCHANGEDB_Withdraw *wd; 176 struct TALER_EXCHANGEDB_ReserveHistory *tail; 177 178 wd = GNUNET_new (struct TALER_EXCHANGEDB_Withdraw); 179 { 180 bool no_noreveal_index; 181 bool no_max_age; 182 bool no_selected_h; 183 size_t num_denom_hs; 184 size_t num_denom_serials; 185 uint64_t *my_denom_serials = NULL; 186 struct TALER_DenominationHashP *my_denom_pub_hashes = NULL; 187 struct GNUNET_PQ_ResultSpec rs[] = { 188 GNUNET_PQ_result_spec_auto_from_type ("planchets_h", 189 &wd->planchets_h), 190 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 191 &wd->reserve_sig), 192 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 193 &wd->amount_with_fee), 194 GNUNET_PQ_result_spec_allow_null ( 195 GNUNET_PQ_result_spec_uint16 ("max_age", 196 &wd->max_age), 197 &no_max_age), 198 GNUNET_PQ_result_spec_allow_null ( 199 GNUNET_PQ_result_spec_uint16 ("noreveal_index", 200 &wd->noreveal_index), 201 &no_noreveal_index), 202 GNUNET_PQ_result_spec_allow_null ( 203 GNUNET_PQ_result_spec_auto_from_type ("blinding_seed", 204 &wd->blinding_seed), 205 &wd->no_blinding_seed), 206 GNUNET_PQ_result_spec_allow_null ( 207 GNUNET_PQ_result_spec_auto_from_type ("selected_h", 208 &wd->selected_h), 209 &no_selected_h), 210 TALER_PQ_result_spec_array_denom_hash (pg->conn, 211 "denom_pub_hashes", 212 &num_denom_hs, 213 &my_denom_pub_hashes), 214 GNUNET_PQ_result_spec_array_uint64 (pg->conn, 215 "denom_serials", 216 &num_denom_serials, 217 &my_denom_serials), 218 GNUNET_PQ_result_spec_end 219 }; 220 221 if (GNUNET_OK != 222 GNUNET_PQ_extract_result (result, 223 rs, 224 --num_results)) 225 { 226 GNUNET_break (0); 227 GNUNET_free (wd); 228 rhc->failed = true; 229 GNUNET_PQ_cleanup_result (rs); 230 return; 231 } 232 233 if (num_denom_hs != num_denom_serials) 234 { 235 GNUNET_break (0); 236 GNUNET_free (wd); 237 rhc->failed = true; 238 GNUNET_PQ_cleanup_result (rs); 239 return; 240 } 241 242 if ((no_noreveal_index != no_max_age) || 243 (no_noreveal_index != no_selected_h)) 244 { 245 GNUNET_break (0); 246 GNUNET_free (wd); 247 rhc->failed = true; 248 GNUNET_PQ_cleanup_result (rs); 249 return; 250 } 251 wd->age_proof_required = ! no_max_age; 252 wd->num_coins = num_denom_serials; 253 wd->reserve_pub = *rhc->reserve_pub; 254 wd->denom_serials = my_denom_serials; 255 wd->denom_pub_hashes = my_denom_pub_hashes; 256 /* prevent cleanup from destroying our actual result */ 257 my_denom_serials = NULL; 258 my_denom_pub_hashes = NULL; 259 GNUNET_PQ_cleanup_result (rs); 260 } 261 262 tail = append_rh (rhc); 263 tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COINS; 264 tail->details.withdraw = wd; 265 } 266 } 267 268 269 /** 270 * Add recoups to result set for #TALER_EXCHANGEDB_get_reserve_history. 271 * 272 * @param cls a `struct ReserveHistoryContext *` 273 * @param result SQL result 274 * @param num_results number of rows in @a result 275 */ 276 static void 277 add_recoup (void *cls, 278 PGresult *result, 279 unsigned int num_results) 280 { 281 struct ReserveHistoryContext *rhc = cls; 282 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 283 284 while (0 < num_results) 285 { 286 struct TALER_EXCHANGEDB_Recoup *recoup; 287 struct TALER_EXCHANGEDB_ReserveHistory *tail; 288 289 recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup); 290 { 291 struct GNUNET_PQ_ResultSpec rs[] = { 292 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 293 &recoup->value), 294 GNUNET_PQ_result_spec_auto_from_type ("coin_pub", 295 &recoup->coin.coin_pub), 296 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 297 &recoup->coin_blind), 298 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 299 &recoup->coin_sig), 300 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 301 &recoup->timestamp), 302 GNUNET_PQ_result_spec_auto_from_type ("planchets_h", 303 &recoup->planchets_h), 304 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 305 &recoup->coin.denom_pub_hash), 306 TALER_PQ_result_spec_denom_sig ( 307 "denom_sig", 308 &recoup->coin.denom_sig), 309 GNUNET_PQ_result_spec_end 310 }; 311 312 if (GNUNET_OK != 313 GNUNET_PQ_extract_result (result, 314 rs, 315 --num_results)) 316 { 317 GNUNET_break (0); 318 GNUNET_free (recoup); 319 rhc->failed = true; 320 return; 321 } 322 } 323 recoup->reserve_pub = *rhc->reserve_pub; 324 tail = append_rh (rhc); 325 tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; 326 tail->details.recoup = recoup; 327 } /* end of 'while (0 < rows)' */ 328 } 329 330 331 /** 332 * Add exchange-to-bank transfers to result set for 333 * #TALER_EXCHANGEDB_get_reserve_history. 334 * 335 * @param cls a `struct ReserveHistoryContext *` 336 * @param result SQL result 337 * @param num_results number of rows in @a result 338 */ 339 static void 340 add_exchange_to_bank (void *cls, 341 PGresult *result, 342 unsigned int num_results) 343 { 344 struct ReserveHistoryContext *rhc = cls; 345 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 346 347 while (0 < num_results) 348 { 349 struct TALER_EXCHANGEDB_ClosingTransfer *closing; 350 struct TALER_EXCHANGEDB_ReserveHistory *tail; 351 352 closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer); 353 { 354 struct GNUNET_PQ_ResultSpec rs[] = { 355 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 356 &closing->amount), 357 TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", 358 &closing->closing_fee), 359 GNUNET_PQ_result_spec_timestamp ("execution_date", 360 &closing->execution_date), 361 GNUNET_PQ_result_spec_string ("receiver_account", 362 &closing->receiver_account_details. 363 full_payto), 364 GNUNET_PQ_result_spec_auto_from_type ("wtid", 365 &closing->wtid), 366 GNUNET_PQ_result_spec_end 367 }; 368 369 if (GNUNET_OK != 370 GNUNET_PQ_extract_result (result, 371 rs, 372 --num_results)) 373 { 374 GNUNET_break (0); 375 GNUNET_free (closing); 376 rhc->failed = true; 377 return; 378 } 379 } 380 closing->reserve_pub = *rhc->reserve_pub; 381 tail = append_rh (rhc); 382 tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; 383 tail->details.closing = closing; 384 } /* end of 'while (0 < rows)' */ 385 } 386 387 388 /** 389 * Add purse merge transfers to result set for 390 * #TALER_EXCHANGEDB_get_reserve_history. 391 * 392 * @param cls a `struct ReserveHistoryContext *` 393 * @param result SQL result 394 * @param num_results number of rows in @a result 395 */ 396 static void 397 add_p2p_merge (void *cls, 398 PGresult *result, 399 unsigned int num_results) 400 { 401 struct ReserveHistoryContext *rhc = cls; 402 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 403 404 while (0 < num_results) 405 { 406 struct TALER_EXCHANGEDB_PurseMerge *merge; 407 struct TALER_EXCHANGEDB_ReserveHistory *tail; 408 409 merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge); 410 { 411 uint32_t flags32; 412 struct TALER_Amount balance; 413 struct GNUNET_PQ_ResultSpec rs[] = { 414 TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee", 415 &merge->purse_fee), 416 TALER_PQ_RESULT_SPEC_AMOUNT ("balance", 417 &balance), 418 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 419 &merge->amount_with_fee), 420 GNUNET_PQ_result_spec_timestamp ("merge_timestamp", 421 &merge->merge_timestamp), 422 GNUNET_PQ_result_spec_timestamp ("purse_expiration", 423 &merge->purse_expiration), 424 GNUNET_PQ_result_spec_uint32 ("age_limit", 425 &merge->min_age), 426 GNUNET_PQ_result_spec_uint32 ("flags", 427 &flags32), 428 GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", 429 &merge->h_contract_terms), 430 GNUNET_PQ_result_spec_auto_from_type ("merge_pub", 431 &merge->merge_pub), 432 GNUNET_PQ_result_spec_auto_from_type ("purse_pub", 433 &merge->purse_pub), 434 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 435 &merge->reserve_sig), 436 GNUNET_PQ_result_spec_end 437 }; 438 439 if (GNUNET_OK != 440 GNUNET_PQ_extract_result (result, 441 rs, 442 --num_results)) 443 { 444 GNUNET_break (0); 445 GNUNET_free (merge); 446 rhc->failed = true; 447 return; 448 } 449 merge->flags = (enum TALER_WalletAccountMergeFlags) flags32; 450 if ( (! GNUNET_TIME_absolute_is_future ( 451 merge->merge_timestamp.abs_time)) && 452 (-1 != TALER_amount_cmp (&balance, 453 &merge->amount_with_fee)) ) 454 merge->merged = true; 455 } 456 merge->reserve_pub = *rhc->reserve_pub; 457 tail = append_rh (rhc); 458 tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE; 459 tail->details.merge = merge; 460 } 461 } 462 463 464 /** 465 * Add paid for history requests to result set for 466 * #TALER_EXCHANGEDB_get_reserve_history. 467 * 468 * @param cls a `struct ReserveHistoryContext *` 469 * @param result SQL result 470 * @param num_results number of rows in @a result 471 */ 472 static void 473 add_open_requests (void *cls, 474 PGresult *result, 475 unsigned int num_results) 476 { 477 struct ReserveHistoryContext *rhc = cls; 478 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 479 480 while (0 < num_results) 481 { 482 struct TALER_EXCHANGEDB_OpenRequest *orq; 483 struct TALER_EXCHANGEDB_ReserveHistory *tail; 484 485 orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest); 486 { 487 struct GNUNET_PQ_ResultSpec rs[] = { 488 TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee", 489 &orq->open_fee), 490 GNUNET_PQ_result_spec_timestamp ("request_timestamp", 491 &orq->request_timestamp), 492 GNUNET_PQ_result_spec_timestamp ("expiration_date", 493 &orq->reserve_expiration), 494 GNUNET_PQ_result_spec_uint32 ("requested_purse_limit", 495 &orq->purse_limit), 496 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 497 &orq->reserve_sig), 498 GNUNET_PQ_result_spec_end 499 }; 500 501 if (GNUNET_OK != 502 GNUNET_PQ_extract_result (result, 503 rs, 504 --num_results)) 505 { 506 GNUNET_break (0); 507 GNUNET_free (orq); 508 rhc->failed = true; 509 return; 510 } 511 } 512 orq->reserve_pub = *rhc->reserve_pub; 513 tail = append_rh (rhc); 514 tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST; 515 tail->details.open_request = orq; 516 } 517 } 518 519 520 /** 521 * Add paid for history requests to result set for 522 * #TALER_EXCHANGEDB_get_reserve_history. 523 * 524 * @param cls a `struct ReserveHistoryContext *` 525 * @param result SQL result 526 * @param num_results number of rows in @a result 527 */ 528 static void 529 add_close_requests (void *cls, 530 PGresult *result, 531 unsigned int num_results) 532 { 533 struct ReserveHistoryContext *rhc = cls; 534 535 while (0 < num_results) 536 { 537 struct TALER_EXCHANGEDB_CloseRequest *crq; 538 struct TALER_EXCHANGEDB_ReserveHistory *tail; 539 540 crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest); 541 { 542 struct TALER_FullPayto payto_uri; 543 struct GNUNET_PQ_ResultSpec rs[] = { 544 GNUNET_PQ_result_spec_timestamp ("close_timestamp", 545 &crq->request_timestamp), 546 GNUNET_PQ_result_spec_string ("payto_uri", 547 &payto_uri.full_payto), 548 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 549 &crq->reserve_sig), 550 GNUNET_PQ_result_spec_end 551 }; 552 553 if (GNUNET_OK != 554 GNUNET_PQ_extract_result (result, 555 rs, 556 --num_results)) 557 { 558 GNUNET_break (0); 559 GNUNET_free (crq); 560 rhc->failed = true; 561 return; 562 } 563 TALER_full_payto_hash (payto_uri, 564 &crq->target_account_h_payto); 565 GNUNET_free (payto_uri.full_payto); 566 } 567 crq->reserve_pub = *rhc->reserve_pub; 568 tail = append_rh (rhc); 569 tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST; 570 tail->details.close_request = crq; 571 } 572 } 573 574 575 /** 576 * Add reserve history entries found. 577 * 578 * @param cls a `struct ReserveHistoryContext *` 579 * @param result SQL result 580 * @param num_results number of rows in @a result 581 */ 582 static void 583 handle_history_entry (void *cls, 584 PGresult *result, 585 unsigned int num_results) 586 { 587 static const struct 588 { 589 /** 590 * Table with reserve history entry we are responsible for. 591 */ 592 const char *table; 593 /** 594 * Name of the prepared statement to run. 595 */ 596 const char *statement; 597 /** 598 * Function to use to process the results. 599 */ 600 GNUNET_PQ_PostgresResultHandler cb; 601 } work[] = { 602 /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */ 603 { "reserves_in", 604 "get_reserve_history_reserves_in_get_transactions", 605 add_bank_to_exchange }, 606 /** #TALER_EXCHANGEDB_RO_WITHDRAW_COINS */ 607 { "withdraw", 608 "get_reserve_history_withdraw_details", 609 &add_withdraw }, 610 /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */ 611 { "recoup", 612 "get_reserve_history_recoup_by_reserve", 613 &add_recoup }, 614 /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */ 615 { "reserves_close", 616 "get_reserve_history_close_by_reserve", 617 &add_exchange_to_bank }, 618 /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */ 619 { "purse_decision", 620 "get_reserve_history_merge_by_reserve", 621 &add_p2p_merge }, 622 /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */ 623 { "reserves_open_requests", 624 "get_reserve_history_open_request_by_reserve", 625 &add_open_requests }, 626 /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */ 627 { "close_requests", 628 "get_reserve_history_close_request_by_reserve", 629 &add_close_requests }, 630 /* List terminator */ 631 { NULL, NULL, NULL } 632 }; 633 struct ReserveHistoryContext *rhc = cls; 634 char *table_name; 635 uint64_t serial_id; 636 struct GNUNET_PQ_ResultSpec rs[] = { 637 GNUNET_PQ_result_spec_string ("table_name", 638 &table_name), 639 GNUNET_PQ_result_spec_uint64 ("serial_id", 640 &serial_id), 641 GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id", 642 &rhc->current_history_offset), 643 GNUNET_PQ_result_spec_end 644 }; 645 struct GNUNET_PQ_QueryParam params[] = { 646 GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub), 647 GNUNET_PQ_query_param_uint64 (&serial_id), 648 GNUNET_PQ_query_param_end 649 }; 650 651 while (0 < num_results--) 652 { 653 enum GNUNET_DB_QueryStatus qs; 654 bool found = false; 655 656 if (GNUNET_OK != 657 GNUNET_PQ_extract_result (result, 658 rs, 659 num_results)) 660 { 661 GNUNET_break (0); 662 rhc->failed = true; 663 return; 664 } 665 666 for (unsigned int i = 0; 667 NULL != work[i].cb; 668 i++) 669 { 670 if (0 != strcmp (table_name, 671 work[i].table)) 672 continue; 673 found = true; 674 qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn, 675 work[i].statement, 676 params, 677 work[i].cb, 678 rhc); 679 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 680 "Reserve %s had %d transactions at %llu in table %s\n", 681 TALER_B2S (rhc->reserve_pub), 682 (int) qs, 683 (unsigned long long) serial_id, 684 table_name); 685 if (0 >= qs) 686 rhc->failed = true; 687 break; 688 } 689 if (! found) 690 { 691 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 692 "Reserve history includes unsupported table `%s`\n", 693 table_name); 694 rhc->failed = true; 695 } 696 GNUNET_PQ_cleanup_result (rs); 697 if (rhc->failed) 698 break; 699 } 700 } 701 702 703 enum GNUNET_DB_QueryStatus 704 TALER_EXCHANGEDB_get_reserve_history ( 705 struct TALER_EXCHANGEDB_PostgresContext *pg, 706 const struct TALER_ReservePublicKeyP *reserve_pub, 707 uint64_t start_off, 708 uint64_t etag_in, 709 uint64_t *etag_out, 710 struct TALER_Amount *balance, 711 struct TALER_EXCHANGEDB_ReserveHistory **rhp) 712 { 713 struct ReserveHistoryContext rhc = { 714 .pg = pg, 715 .reserve_pub = reserve_pub 716 }; 717 struct GNUNET_PQ_QueryParam params[] = { 718 GNUNET_PQ_query_param_auto_from_type (reserve_pub), 719 GNUNET_PQ_query_param_end 720 }; 721 struct GNUNET_PQ_QueryParam lparams[] = { 722 GNUNET_PQ_query_param_auto_from_type (reserve_pub), 723 GNUNET_PQ_query_param_uint64 (&start_off), 724 GNUNET_PQ_query_param_end 725 }; 726 727 *rhp = NULL; 728 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 729 "Getting transactions for reserve %s\n", 730 TALER_B2S (reserve_pub)); 731 PREPARE (pg, 732 "get_reserve_history_etag", 733 "SELECT" 734 " his.reserve_history_serial_id" 735 ",r.current_balance" 736 " FROM reserve_history his" 737 " JOIN reserves r USING (reserve_pub)" 738 " WHERE his.reserve_pub=$1" 739 " ORDER BY reserve_history_serial_id DESC" 740 " LIMIT 1;"); 741 PREPARE (pg, 742 "get_reserve_history", 743 "SELECT" 744 " table_name" 745 ",serial_id" 746 ",reserve_history_serial_id" 747 " FROM reserve_history" 748 " WHERE reserve_pub=$1" 749 " AND reserve_history_serial_id > $2" 750 " ORDER BY reserve_history_serial_id DESC;"); 751 PREPARE (pg, 752 "get_reserve_history_reserves_in_get_transactions", 753 "SELECT" 754 " ri.wire_reference" 755 ",ri.credit" 756 ",ri.execution_date" 757 ",wt.payto_uri AS sender_account_details" 758 " FROM reserves_in ri" 759 " JOIN wire_targets wt" 760 " ON (wire_source_h_payto = wire_target_h_payto)" 761 " WHERE ri.reserve_pub=$1" 762 " AND ri.reserve_in_serial_id=$2;"); 763 PREPARE (pg, 764 "get_reserve_history_withdraw_details", 765 "SELECT" 766 " planchets_h" 767 ",amount_with_fee" 768 ",reserve_sig" 769 ",max_age" 770 ",noreveal_index" 771 ",selected_h" 772 ",blinding_seed" 773 ",denom_serials" 774 ",ARRAY(" 775 " SELECT denominations.denom_pub_hash FROM (" 776 " SELECT UNNEST(denom_serials) AS id," 777 " generate_subscripts(denom_serials, 1) AS nr" /* for order */ 778 " ) AS denoms" 779 " LEFT JOIN denominations ON denominations.denominations_serial=denoms.id" 780 ") AS denom_pub_hashes" 781 " FROM withdraw " 782 " WHERE withdraw_id=$2" 783 " AND reserve_pub=$1;"); 784 PREPARE (pg, 785 "get_reserve_history_recoup_by_reserve", 786 "SELECT" 787 " rec.coin_pub" 788 ",rec.coin_sig" 789 ",rec.coin_blind" 790 ",rec.amount" 791 ",rec.recoup_timestamp" 792 ",ro.planchets_h" 793 ",denom.denom_pub_hash" 794 ",kc.denom_sig" 795 " FROM recoup rec" 796 " JOIN withdraw ro" 797 " USING (withdraw_id)" 798 " JOIN reserves res" 799 " USING (reserve_pub)" 800 " JOIN known_coins kc" 801 " USING (coin_pub)" 802 " JOIN denominations denom" 803 " ON (denom.denominations_serial = kc.denominations_serial)" 804 " WHERE rec.recoup_uuid=$2" 805 " AND res.reserve_pub=$1;"); 806 PREPARE (pg, 807 "get_reserve_history_close_by_reserve", 808 "SELECT" 809 " rc.amount" 810 ",rc.closing_fee" 811 ",rc.execution_date" 812 ",wt.payto_uri AS receiver_account" 813 ",rc.wtid" 814 " FROM reserves_close rc" 815 " JOIN wire_targets wt" 816 " USING (wire_target_h_payto)" 817 " WHERE reserve_pub=$1" 818 " AND close_uuid=$2;"); 819 PREPARE (pg, 820 "get_reserve_history_merge_by_reserve", 821 "SELECT" 822 " pr.amount_with_fee" 823 ",pr.balance" 824 ",pr.purse_fee" 825 ",pr.h_contract_terms" 826 ",pr.merge_pub" 827 ",am.reserve_sig" 828 ",pm.purse_pub" 829 ",pm.merge_timestamp" 830 ",pr.purse_expiration" 831 ",pr.age_limit" 832 ",pr.flags" 833 " FROM purse_decision pdes" 834 " JOIN purse_requests pr" 835 " ON (pr.purse_pub = pdes.purse_pub)" 836 " JOIN purse_merges pm" 837 " ON (pm.purse_pub = pdes.purse_pub)" 838 " JOIN account_merges am" 839 " ON (am.purse_pub = pm.purse_pub AND" 840 " am.reserve_pub = pm.reserve_pub)" 841 " WHERE pdes.purse_decision_serial_id=$2" 842 " AND pm.reserve_pub=$1" 843 " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */ 844 " AND NOT pdes.refunded;"); 845 PREPARE (pg, 846 "get_reserve_history_open_request_by_reserve", 847 "SELECT" 848 " reserve_payment AS open_fee" 849 ",request_timestamp" 850 ",expiration_date" 851 ",requested_purse_limit" 852 ",reserve_sig" 853 " FROM reserves_open_requests" 854 " WHERE reserve_pub=$1" 855 " AND open_request_uuid=$2;"); 856 PREPARE (pg, 857 "get_reserve_history_close_request_by_reserve", 858 "SELECT" 859 " close_timestamp" 860 ",payto_uri" 861 ",reserve_sig" 862 " FROM close_requests" 863 " WHERE reserve_pub=$1" 864 " AND close_request_serial_id=$2;"); 865 866 for (unsigned int i = 0; i<RETRIES; i++) 867 { 868 enum GNUNET_DB_QueryStatus qs; 869 uint64_t end; 870 struct GNUNET_PQ_ResultSpec rs[] = { 871 GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id", 872 &end), 873 TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance", 874 balance), 875 GNUNET_PQ_result_spec_end 876 }; 877 878 if (GNUNET_OK != 879 TALER_EXCHANGEDB_start_read_committed (pg, 880 "get-reserve-transactions") 881 ) 882 { 883 GNUNET_break (0); 884 return GNUNET_DB_STATUS_HARD_ERROR; 885 } 886 /* First only check the last item, to see if 887 we even need to iterate */ 888 qs = GNUNET_PQ_eval_prepared_singleton_select ( 889 pg->conn, 890 "get_reserve_history_etag", 891 params, 892 rs); 893 switch (qs) 894 { 895 case GNUNET_DB_STATUS_HARD_ERROR: 896 TALER_EXCHANGEDB_rollback (pg); 897 return qs; 898 case GNUNET_DB_STATUS_SOFT_ERROR: 899 TALER_EXCHANGEDB_rollback (pg); 900 continue; 901 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 902 TALER_EXCHANGEDB_rollback (pg); 903 return qs; 904 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 905 *etag_out = end; 906 if (end == etag_in) 907 { 908 /* Nothing changed since the client last asked, so we are done -- 909 but the read-committed transaction opened above still has to be 910 closed. Leaving it open parks the connection in "idle in 911 transaction" until the next TALER_EXCHANGEDB_start(), whose 912 preflight then rolls it back and logs it as a bug. */ 913 TALER_EXCHANGEDB_rollback (pg); 914 return qs; 915 } 916 } 917 /* We indeed need to iterate over the history */ 918 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 919 "Current ETag for reserve %s is %llu\n", 920 TALER_B2S (reserve_pub), 921 (unsigned long long) end); 922 923 qs = GNUNET_PQ_eval_prepared_multi_select ( 924 pg->conn, 925 "get_reserve_history", 926 lparams, 927 &handle_history_entry, 928 &rhc); 929 switch (qs) 930 { 931 case GNUNET_DB_STATUS_HARD_ERROR: 932 TALER_EXCHANGEDB_rollback (pg); 933 return qs; 934 case GNUNET_DB_STATUS_SOFT_ERROR: 935 TALER_EXCHANGEDB_rollback (pg); 936 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 937 rhc.rh = NULL; 938 rhc.rh_tail = NULL; 939 continue; 940 default: 941 break; 942 } 943 if (rhc.failed) 944 { 945 TALER_EXCHANGEDB_rollback (pg); 946 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 947 return GNUNET_DB_STATUS_SOFT_ERROR; 948 } 949 qs = TALER_EXCHANGEDB_commit (pg); 950 switch (qs) 951 { 952 case GNUNET_DB_STATUS_HARD_ERROR: 953 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 954 return qs; 955 case GNUNET_DB_STATUS_SOFT_ERROR: 956 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 957 rhc.rh = NULL; 958 rhc.rh_tail = NULL; 959 continue; 960 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 961 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 962 *rhp = rhc.rh; 963 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 964 } 965 } 966 return GNUNET_DB_STATUS_SOFT_ERROR; 967 }