get_reserve_history.c (30232B)
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 ("denom_pub_hash", 303 &recoup->coin.denom_pub_hash), 304 TALER_PQ_result_spec_denom_sig ( 305 "denom_sig", 306 &recoup->coin.denom_sig), 307 GNUNET_PQ_result_spec_end 308 }; 309 310 if (GNUNET_OK != 311 GNUNET_PQ_extract_result (result, 312 rs, 313 --num_results)) 314 { 315 GNUNET_break (0); 316 GNUNET_free (recoup); 317 rhc->failed = true; 318 return; 319 } 320 } 321 recoup->reserve_pub = *rhc->reserve_pub; 322 tail = append_rh (rhc); 323 tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; 324 tail->details.recoup = recoup; 325 } /* end of 'while (0 < rows)' */ 326 } 327 328 329 /** 330 * Add exchange-to-bank transfers to result set for 331 * #TALER_EXCHANGEDB_get_reserve_history. 332 * 333 * @param cls a `struct ReserveHistoryContext *` 334 * @param result SQL result 335 * @param num_results number of rows in @a result 336 */ 337 static void 338 add_exchange_to_bank (void *cls, 339 PGresult *result, 340 unsigned int num_results) 341 { 342 struct ReserveHistoryContext *rhc = cls; 343 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 344 345 while (0 < num_results) 346 { 347 struct TALER_EXCHANGEDB_ClosingTransfer *closing; 348 struct TALER_EXCHANGEDB_ReserveHistory *tail; 349 350 closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer); 351 { 352 struct GNUNET_PQ_ResultSpec rs[] = { 353 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 354 &closing->amount), 355 TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", 356 &closing->closing_fee), 357 GNUNET_PQ_result_spec_timestamp ("execution_date", 358 &closing->execution_date), 359 GNUNET_PQ_result_spec_string ("receiver_account", 360 &closing->receiver_account_details. 361 full_payto), 362 GNUNET_PQ_result_spec_auto_from_type ("wtid", 363 &closing->wtid), 364 GNUNET_PQ_result_spec_end 365 }; 366 367 if (GNUNET_OK != 368 GNUNET_PQ_extract_result (result, 369 rs, 370 --num_results)) 371 { 372 GNUNET_break (0); 373 GNUNET_free (closing); 374 rhc->failed = true; 375 return; 376 } 377 } 378 closing->reserve_pub = *rhc->reserve_pub; 379 tail = append_rh (rhc); 380 tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; 381 tail->details.closing = closing; 382 } /* end of 'while (0 < rows)' */ 383 } 384 385 386 /** 387 * Add purse merge transfers to result set for 388 * #TALER_EXCHANGEDB_get_reserve_history. 389 * 390 * @param cls a `struct ReserveHistoryContext *` 391 * @param result SQL result 392 * @param num_results number of rows in @a result 393 */ 394 static void 395 add_p2p_merge (void *cls, 396 PGresult *result, 397 unsigned int num_results) 398 { 399 struct ReserveHistoryContext *rhc = cls; 400 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 401 402 while (0 < num_results) 403 { 404 struct TALER_EXCHANGEDB_PurseMerge *merge; 405 struct TALER_EXCHANGEDB_ReserveHistory *tail; 406 407 merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge); 408 { 409 uint32_t flags32; 410 struct TALER_Amount balance; 411 struct GNUNET_PQ_ResultSpec rs[] = { 412 TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee", 413 &merge->purse_fee), 414 TALER_PQ_RESULT_SPEC_AMOUNT ("balance", 415 &balance), 416 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 417 &merge->amount_with_fee), 418 GNUNET_PQ_result_spec_timestamp ("merge_timestamp", 419 &merge->merge_timestamp), 420 GNUNET_PQ_result_spec_timestamp ("purse_expiration", 421 &merge->purse_expiration), 422 GNUNET_PQ_result_spec_uint32 ("age_limit", 423 &merge->min_age), 424 GNUNET_PQ_result_spec_uint32 ("flags", 425 &flags32), 426 GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", 427 &merge->h_contract_terms), 428 GNUNET_PQ_result_spec_auto_from_type ("merge_pub", 429 &merge->merge_pub), 430 GNUNET_PQ_result_spec_auto_from_type ("purse_pub", 431 &merge->purse_pub), 432 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 433 &merge->reserve_sig), 434 GNUNET_PQ_result_spec_end 435 }; 436 437 if (GNUNET_OK != 438 GNUNET_PQ_extract_result (result, 439 rs, 440 --num_results)) 441 { 442 GNUNET_break (0); 443 GNUNET_free (merge); 444 rhc->failed = true; 445 return; 446 } 447 merge->flags = (enum TALER_WalletAccountMergeFlags) flags32; 448 if ( (! GNUNET_TIME_absolute_is_future ( 449 merge->merge_timestamp.abs_time)) && 450 (-1 != TALER_amount_cmp (&balance, 451 &merge->amount_with_fee)) ) 452 merge->merged = true; 453 } 454 merge->reserve_pub = *rhc->reserve_pub; 455 tail = append_rh (rhc); 456 tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE; 457 tail->details.merge = merge; 458 } 459 } 460 461 462 /** 463 * Add paid for history requests to result set for 464 * #TALER_EXCHANGEDB_get_reserve_history. 465 * 466 * @param cls a `struct ReserveHistoryContext *` 467 * @param result SQL result 468 * @param num_results number of rows in @a result 469 */ 470 static void 471 add_open_requests (void *cls, 472 PGresult *result, 473 unsigned int num_results) 474 { 475 struct ReserveHistoryContext *rhc = cls; 476 struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg; 477 478 while (0 < num_results) 479 { 480 struct TALER_EXCHANGEDB_OpenRequest *orq; 481 struct TALER_EXCHANGEDB_ReserveHistory *tail; 482 483 orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest); 484 { 485 struct GNUNET_PQ_ResultSpec rs[] = { 486 TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee", 487 &orq->open_fee), 488 GNUNET_PQ_result_spec_timestamp ("request_timestamp", 489 &orq->request_timestamp), 490 GNUNET_PQ_result_spec_timestamp ("expiration_date", 491 &orq->reserve_expiration), 492 GNUNET_PQ_result_spec_uint32 ("requested_purse_limit", 493 &orq->purse_limit), 494 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 495 &orq->reserve_sig), 496 GNUNET_PQ_result_spec_end 497 }; 498 499 if (GNUNET_OK != 500 GNUNET_PQ_extract_result (result, 501 rs, 502 --num_results)) 503 { 504 GNUNET_break (0); 505 GNUNET_free (orq); 506 rhc->failed = true; 507 return; 508 } 509 } 510 orq->reserve_pub = *rhc->reserve_pub; 511 tail = append_rh (rhc); 512 tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST; 513 tail->details.open_request = orq; 514 } 515 } 516 517 518 /** 519 * Add paid for history requests to result set for 520 * #TALER_EXCHANGEDB_get_reserve_history. 521 * 522 * @param cls a `struct ReserveHistoryContext *` 523 * @param result SQL result 524 * @param num_results number of rows in @a result 525 */ 526 static void 527 add_close_requests (void *cls, 528 PGresult *result, 529 unsigned int num_results) 530 { 531 struct ReserveHistoryContext *rhc = cls; 532 533 while (0 < num_results) 534 { 535 struct TALER_EXCHANGEDB_CloseRequest *crq; 536 struct TALER_EXCHANGEDB_ReserveHistory *tail; 537 538 crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest); 539 { 540 struct TALER_FullPayto payto_uri; 541 struct GNUNET_PQ_ResultSpec rs[] = { 542 GNUNET_PQ_result_spec_timestamp ("close_timestamp", 543 &crq->request_timestamp), 544 GNUNET_PQ_result_spec_string ("payto_uri", 545 &payto_uri.full_payto), 546 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 547 &crq->reserve_sig), 548 GNUNET_PQ_result_spec_end 549 }; 550 551 if (GNUNET_OK != 552 GNUNET_PQ_extract_result (result, 553 rs, 554 --num_results)) 555 { 556 GNUNET_break (0); 557 GNUNET_free (crq); 558 rhc->failed = true; 559 return; 560 } 561 TALER_full_payto_hash (payto_uri, 562 &crq->target_account_h_payto); 563 GNUNET_free (payto_uri.full_payto); 564 } 565 crq->reserve_pub = *rhc->reserve_pub; 566 tail = append_rh (rhc); 567 tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST; 568 tail->details.close_request = crq; 569 } 570 } 571 572 573 /** 574 * Add reserve history entries found. 575 * 576 * @param cls a `struct ReserveHistoryContext *` 577 * @param result SQL result 578 * @param num_results number of rows in @a result 579 */ 580 static void 581 handle_history_entry (void *cls, 582 PGresult *result, 583 unsigned int num_results) 584 { 585 static const struct 586 { 587 /** 588 * Table with reserve history entry we are responsible for. 589 */ 590 const char *table; 591 /** 592 * Name of the prepared statement to run. 593 */ 594 const char *statement; 595 /** 596 * Function to use to process the results. 597 */ 598 GNUNET_PQ_PostgresResultHandler cb; 599 } work[] = { 600 /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */ 601 { "reserves_in", 602 "reserves_in_get_transactions", 603 add_bank_to_exchange }, 604 /** #TALER_EXCHANGEDB_RO_WITHDRAW_COINS */ 605 { "withdraw", 606 "get_withdraw_details", 607 &add_withdraw }, 608 /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */ 609 { "recoup", 610 "recoup_by_reserve", 611 &add_recoup }, 612 /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */ 613 { "reserves_close", 614 "close_by_reserve", 615 &add_exchange_to_bank }, 616 /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */ 617 { "purse_decision", 618 "merge_by_reserve", 619 &add_p2p_merge }, 620 /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */ 621 { "reserves_open_requests", 622 "open_request_by_reserve", 623 &add_open_requests }, 624 /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */ 625 { "close_requests", 626 "close_request_by_reserve", 627 &add_close_requests }, 628 /* List terminator */ 629 { NULL, NULL, NULL } 630 }; 631 struct ReserveHistoryContext *rhc = cls; 632 char *table_name; 633 uint64_t serial_id; 634 struct GNUNET_PQ_ResultSpec rs[] = { 635 GNUNET_PQ_result_spec_string ("table_name", 636 &table_name), 637 GNUNET_PQ_result_spec_uint64 ("serial_id", 638 &serial_id), 639 GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id", 640 &rhc->current_history_offset), 641 GNUNET_PQ_result_spec_end 642 }; 643 struct GNUNET_PQ_QueryParam params[] = { 644 GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub), 645 GNUNET_PQ_query_param_uint64 (&serial_id), 646 GNUNET_PQ_query_param_end 647 }; 648 649 while (0 < num_results--) 650 { 651 enum GNUNET_DB_QueryStatus qs; 652 bool found = false; 653 654 if (GNUNET_OK != 655 GNUNET_PQ_extract_result (result, 656 rs, 657 num_results)) 658 { 659 GNUNET_break (0); 660 rhc->failed = true; 661 return; 662 } 663 664 for (unsigned int i = 0; 665 NULL != work[i].cb; 666 i++) 667 { 668 if (0 != strcmp (table_name, 669 work[i].table)) 670 continue; 671 found = true; 672 qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn, 673 work[i].statement, 674 params, 675 work[i].cb, 676 rhc); 677 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 678 "Reserve %s had %d transactions at %llu in table %s\n", 679 TALER_B2S (rhc->reserve_pub), 680 (int) qs, 681 (unsigned long long) serial_id, 682 table_name); 683 if (0 >= qs) 684 rhc->failed = true; 685 break; 686 } 687 if (! found) 688 { 689 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 690 "Reserve history includes unsupported table `%s`\n", 691 table_name); 692 rhc->failed = true; 693 } 694 GNUNET_PQ_cleanup_result (rs); 695 if (rhc->failed) 696 break; 697 } 698 } 699 700 701 enum GNUNET_DB_QueryStatus 702 TALER_EXCHANGEDB_get_reserve_history ( 703 struct TALER_EXCHANGEDB_PostgresContext *pg, 704 const struct TALER_ReservePublicKeyP *reserve_pub, 705 uint64_t start_off, 706 uint64_t etag_in, 707 uint64_t *etag_out, 708 struct TALER_Amount *balance, 709 struct TALER_EXCHANGEDB_ReserveHistory **rhp) 710 { 711 struct ReserveHistoryContext rhc = { 712 .pg = pg, 713 .reserve_pub = reserve_pub 714 }; 715 struct GNUNET_PQ_QueryParam params[] = { 716 GNUNET_PQ_query_param_auto_from_type (reserve_pub), 717 GNUNET_PQ_query_param_end 718 }; 719 struct GNUNET_PQ_QueryParam lparams[] = { 720 GNUNET_PQ_query_param_auto_from_type (reserve_pub), 721 GNUNET_PQ_query_param_uint64 (&start_off), 722 GNUNET_PQ_query_param_end 723 }; 724 725 *rhp = NULL; 726 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 727 "Getting transactions for reserve %s\n", 728 TALER_B2S (reserve_pub)); 729 PREPARE (pg, 730 "get_reserve_history_etag", 731 "SELECT" 732 " his.reserve_history_serial_id" 733 ",r.current_balance" 734 " FROM reserve_history his" 735 " JOIN reserves r USING (reserve_pub)" 736 " WHERE his.reserve_pub=$1" 737 " ORDER BY reserve_history_serial_id DESC" 738 " LIMIT 1;"); 739 PREPARE (pg, 740 "get_reserve_history", 741 "SELECT" 742 " table_name" 743 ",serial_id" 744 ",reserve_history_serial_id" 745 " FROM reserve_history" 746 " WHERE reserve_pub=$1" 747 " AND reserve_history_serial_id > $2" 748 " ORDER BY reserve_history_serial_id DESC;"); 749 PREPARE (pg, 750 "reserves_in_get_transactions", 751 "SELECT" 752 " ri.wire_reference" 753 ",ri.credit" 754 ",ri.execution_date" 755 ",wt.payto_uri AS sender_account_details" 756 " FROM reserves_in ri" 757 " JOIN wire_targets wt" 758 " ON (wire_source_h_payto = wire_target_h_payto)" 759 " WHERE ri.reserve_pub=$1" 760 " AND ri.reserve_in_serial_id=$2;"); 761 PREPARE (pg, 762 "get_withdraw_details", 763 "SELECT" 764 " planchets_h" 765 ",amount_with_fee" 766 ",reserve_sig" 767 ",max_age" 768 ",noreveal_index" 769 ",selected_h" 770 ",blinding_seed" 771 ",denom_serials" 772 ",ARRAY(" 773 " SELECT denominations.denom_pub_hash FROM (" 774 " SELECT UNNEST(denom_serials) AS id," 775 " generate_subscripts(denom_serials, 1) AS nr" /* for order */ 776 " ) AS denoms" 777 " LEFT JOIN denominations ON denominations.denominations_serial=denoms.id" 778 ") AS denom_pub_hashes" 779 " FROM withdraw " 780 " WHERE withdraw_id=$2" 781 " AND reserve_pub=$1;"); 782 PREPARE (pg, 783 "recoup_by_reserve", 784 "SELECT" 785 " rec.coin_pub" 786 ",rec.coin_sig" 787 ",rec.coin_blind" 788 ",rec.amount" 789 ",rec.recoup_timestamp" 790 ",denom.denom_pub_hash" 791 ",kc.denom_sig" 792 " FROM recoup rec" 793 " JOIN withdraw ro" 794 " USING (withdraw_id)" 795 " JOIN reserves res" 796 " USING (reserve_pub)" 797 " JOIN known_coins kc" 798 " USING (coin_pub)" 799 " JOIN denominations denom" 800 " ON (denom.denominations_serial = kc.denominations_serial)" 801 " WHERE rec.recoup_uuid=$2" 802 " AND res.reserve_pub=$1;"); 803 PREPARE (pg, 804 "close_by_reserve", 805 "SELECT" 806 " rc.amount" 807 ",rc.closing_fee" 808 ",rc.execution_date" 809 ",wt.payto_uri AS receiver_account" 810 ",rc.wtid" 811 " FROM reserves_close rc" 812 " JOIN wire_targets wt" 813 " USING (wire_target_h_payto)" 814 " WHERE reserve_pub=$1" 815 " AND close_uuid=$2;"); 816 PREPARE (pg, 817 "merge_by_reserve", 818 "SELECT" 819 " pr.amount_with_fee" 820 ",pr.balance" 821 ",pr.purse_fee" 822 ",pr.h_contract_terms" 823 ",pr.merge_pub" 824 ",am.reserve_sig" 825 ",pm.purse_pub" 826 ",pm.merge_timestamp" 827 ",pr.purse_expiration" 828 ",pr.age_limit" 829 ",pr.flags" 830 " FROM purse_decision pdes" 831 " JOIN purse_requests pr" 832 " ON (pr.purse_pub = pdes.purse_pub)" 833 " JOIN purse_merges pm" 834 " ON (pm.purse_pub = pdes.purse_pub)" 835 " JOIN account_merges am" 836 " ON (am.purse_pub = pm.purse_pub AND" 837 " am.reserve_pub = pm.reserve_pub)" 838 " WHERE pdes.purse_decision_serial_id=$2" 839 " AND pm.reserve_pub=$1" 840 " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */ 841 " AND NOT pdes.refunded;"); 842 PREPARE (pg, 843 "open_request_by_reserve", 844 "SELECT" 845 " reserve_payment AS open_fee" 846 ",request_timestamp" 847 ",expiration_date" 848 ",requested_purse_limit" 849 ",reserve_sig" 850 " FROM reserves_open_requests" 851 " WHERE reserve_pub=$1" 852 " AND open_request_uuid=$2;"); 853 PREPARE (pg, 854 "close_request_by_reserve", 855 "SELECT" 856 " close_timestamp" 857 ",payto_uri" 858 ",reserve_sig" 859 " FROM close_requests" 860 " WHERE reserve_pub=$1" 861 " AND close_request_serial_id=$2;"); 862 863 for (unsigned int i = 0; i<RETRIES; i++) 864 { 865 enum GNUNET_DB_QueryStatus qs; 866 uint64_t end; 867 struct GNUNET_PQ_ResultSpec rs[] = { 868 GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id", 869 &end), 870 TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance", 871 balance), 872 GNUNET_PQ_result_spec_end 873 }; 874 875 if (GNUNET_OK != 876 TALER_TALER_EXCHANGEDB_start_read_committed (pg, 877 "get-reserve-transactions") 878 ) 879 { 880 GNUNET_break (0); 881 return GNUNET_DB_STATUS_HARD_ERROR; 882 } 883 /* First only check the last item, to see if 884 we even need to iterate */ 885 qs = GNUNET_PQ_eval_prepared_singleton_select ( 886 pg->conn, 887 "get_reserve_history_etag", 888 params, 889 rs); 890 switch (qs) 891 { 892 case GNUNET_DB_STATUS_HARD_ERROR: 893 TALER_EXCHANGEDB_rollback (pg); 894 return qs; 895 case GNUNET_DB_STATUS_SOFT_ERROR: 896 TALER_EXCHANGEDB_rollback (pg); 897 continue; 898 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 899 TALER_EXCHANGEDB_rollback (pg); 900 return qs; 901 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 902 *etag_out = end; 903 if (end == etag_in) 904 return qs; 905 } 906 /* We indeed need to iterate over the history */ 907 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 908 "Current ETag for reserve %s is %llu\n", 909 TALER_B2S (reserve_pub), 910 (unsigned long long) end); 911 912 qs = GNUNET_PQ_eval_prepared_multi_select ( 913 pg->conn, 914 "get_reserve_history", 915 lparams, 916 &handle_history_entry, 917 &rhc); 918 switch (qs) 919 { 920 case GNUNET_DB_STATUS_HARD_ERROR: 921 TALER_EXCHANGEDB_rollback (pg); 922 return qs; 923 case GNUNET_DB_STATUS_SOFT_ERROR: 924 TALER_EXCHANGEDB_rollback (pg); 925 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 926 rhc.rh = NULL; 927 rhc.rh_tail = NULL; 928 continue; 929 default: 930 break; 931 } 932 if (rhc.failed) 933 { 934 TALER_EXCHANGEDB_rollback (pg); 935 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 936 return GNUNET_DB_STATUS_SOFT_ERROR; 937 } 938 qs = TALER_EXCHANGEDB_commit (pg); 939 switch (qs) 940 { 941 case GNUNET_DB_STATUS_HARD_ERROR: 942 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 943 return qs; 944 case GNUNET_DB_STATUS_SOFT_ERROR: 945 TALER_EXCHANGEDB_free_reserve_history (rhc.rh); 946 rhc.rh = NULL; 947 rhc.rh_tail = NULL; 948 continue; 949 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 950 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 951 *rhp = rhc.rh; 952 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 953 } 954 } 955 return GNUNET_DB_STATUS_SOFT_ERROR; 956 }