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