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