pg_get_coin_transactions.c (39606B)
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_coin_transactions.c 18 * @brief Low-level (statement-level) Postgres database access for the exchange 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_exchangedb_plugin.h" 25 #include "taler/taler_pq_lib.h" 26 #include "pg_get_coin_transactions.h" 27 #include "pg_helper.h" 28 #include "pg_start_read_committed.h" 29 #include "pg_commit.h" 30 #include "pg_rollback.h" 31 #include "plugin_exchangedb_common.h" 32 33 /** 34 * How often do we re-try when encountering DB serialization issues? 35 * (We are read-only, so can only happen due to concurrent insert, 36 * which should be very rare.) 37 */ 38 #define RETRIES 3 39 40 /** 41 * Closure for callbacks called from #TEH_PG_get_coin_transactions() 42 */ 43 struct CoinHistoryContext 44 { 45 /** 46 * Head of the coin's history list. 47 */ 48 struct TALER_EXCHANGEDB_TransactionList *head; 49 50 /** 51 * Public key of the coin we are building the history for. 52 */ 53 const struct TALER_CoinSpendPublicKeyP *coin_pub; 54 55 /** 56 * Plugin context. 57 */ 58 struct PostgresClosure *pg; 59 60 /** 61 * Our current offset in the coin history. 62 */ 63 uint64_t chid; 64 65 /** 66 * Set to 'true' if the transaction failed. 67 */ 68 bool failed; 69 70 }; 71 72 73 /** 74 * Function to be called with the results of a SELECT statement 75 * that has returned @a num_results results. 76 * 77 * @param cls closure of type `struct CoinHistoryContext` 78 * @param result the postgres result 79 * @param num_results the number of results in @a result 80 */ 81 static void 82 add_coin_deposit (void *cls, 83 PGresult *result, 84 unsigned int num_results) 85 { 86 struct CoinHistoryContext *chc = cls; 87 struct PostgresClosure *pg = chc->pg; 88 89 for (unsigned int i = 0; i < num_results; i++) 90 { 91 struct TALER_EXCHANGEDB_DepositListEntry *deposit; 92 struct TALER_EXCHANGEDB_TransactionList *tl; 93 uint64_t serial_id; 94 95 deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); 96 { 97 struct GNUNET_PQ_ResultSpec rs[] = { 98 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 99 &deposit->amount_with_fee), 100 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", 101 &deposit->deposit_fee), 102 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 103 &deposit->h_denom_pub), 104 GNUNET_PQ_result_spec_allow_null ( 105 GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", 106 &deposit->h_age_commitment), 107 &deposit->no_age_commitment), 108 GNUNET_PQ_result_spec_allow_null ( 109 GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash", 110 &deposit->wallet_data_hash), 111 &deposit->no_wallet_data_hash), 112 GNUNET_PQ_result_spec_timestamp ("wallet_timestamp", 113 &deposit->timestamp), 114 GNUNET_PQ_result_spec_timestamp ("refund_deadline", 115 &deposit->refund_deadline), 116 GNUNET_PQ_result_spec_timestamp ("wire_deadline", 117 &deposit->wire_deadline), 118 GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", 119 &deposit->merchant_pub), 120 GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", 121 &deposit->h_contract_terms), 122 GNUNET_PQ_result_spec_auto_from_type ("wire_salt", 123 &deposit->wire_salt), 124 GNUNET_PQ_result_spec_string ("payto_uri", 125 &deposit->receiver_wire_account.full_payto 126 ), 127 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 128 &deposit->csig), 129 GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id", 130 &serial_id), 131 GNUNET_PQ_result_spec_auto_from_type ("done", 132 &deposit->done), 133 GNUNET_PQ_result_spec_end 134 }; 135 136 if (GNUNET_OK != 137 GNUNET_PQ_extract_result (result, 138 rs, 139 i)) 140 { 141 GNUNET_break (0); 142 GNUNET_free (deposit); 143 chc->failed = true; 144 return; 145 } 146 } 147 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 148 tl->next = chc->head; 149 tl->type = TALER_EXCHANGEDB_TT_DEPOSIT; 150 tl->details.deposit = deposit; 151 tl->serial_id = serial_id; 152 tl->coin_history_id = chc->chid; 153 chc->head = tl; 154 } 155 } 156 157 158 /** 159 * Function to be called with the results of a SELECT statement 160 * that has returned @a num_results results. 161 * 162 * @param cls closure of type `struct CoinHistoryContext` 163 * @param result the postgres result 164 * @param num_results the number of results in @a result 165 */ 166 static void 167 add_coin_purse_deposit (void *cls, 168 PGresult *result, 169 unsigned int num_results) 170 { 171 struct CoinHistoryContext *chc = cls; 172 struct PostgresClosure *pg = chc->pg; 173 174 for (unsigned int i = 0; i < num_results; i++) 175 { 176 struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit; 177 struct TALER_EXCHANGEDB_TransactionList *tl; 178 uint64_t serial_id; 179 180 deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry); 181 { 182 bool not_finished; 183 struct GNUNET_PQ_ResultSpec rs[] = { 184 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 185 &deposit->amount), 186 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", 187 &deposit->deposit_fee), 188 GNUNET_PQ_result_spec_auto_from_type ("purse_pub", 189 &deposit->purse_pub), 190 GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id", 191 &serial_id), 192 GNUNET_PQ_result_spec_allow_null ( 193 GNUNET_PQ_result_spec_string ("partner_base_url", 194 &deposit->exchange_base_url), 195 NULL), 196 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 197 &deposit->coin_sig), 198 GNUNET_PQ_result_spec_allow_null ( 199 GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", 200 &deposit->h_age_commitment), 201 &deposit->no_age_commitment), 202 GNUNET_PQ_result_spec_allow_null ( 203 GNUNET_PQ_result_spec_bool ("refunded", 204 &deposit->refunded), 205 ¬_finished), 206 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 207 &deposit->h_denom_pub), 208 GNUNET_PQ_result_spec_end 209 }; 210 211 if (GNUNET_OK != 212 GNUNET_PQ_extract_result (result, 213 rs, 214 i)) 215 { 216 GNUNET_break (0); 217 GNUNET_free (deposit); 218 chc->failed = true; 219 return; 220 } 221 if (not_finished) 222 deposit->refunded = false; 223 /* double-check for all-zeros age commitment */ 224 if (! deposit->no_age_commitment) 225 deposit->no_age_commitment 226 = GNUNET_is_zero (&deposit->h_age_commitment); 227 } 228 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 229 tl->next = chc->head; 230 tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT; 231 tl->details.purse_deposit = deposit; 232 tl->serial_id = serial_id; 233 tl->coin_history_id = chc->chid; 234 chc->head = tl; 235 } 236 } 237 238 239 /** 240 * Function to be called with the results of a SELECT statement 241 * that has returned @a num_results results. 242 * 243 * @param cls closure of type `struct CoinHistoryContext` 244 * @param result the postgres result 245 * @param num_results the number of results in @a result 246 */ 247 static void 248 add_coin_melt (void *cls, 249 PGresult *result, 250 unsigned int num_results) 251 { 252 struct CoinHistoryContext *chc = cls; 253 struct PostgresClosure *pg = chc->pg; 254 255 for (unsigned int i = 0; i<num_results; i++) 256 { 257 struct TALER_EXCHANGEDB_MeltListEntry *melt; 258 struct TALER_EXCHANGEDB_TransactionList *tl; 259 uint64_t serial_id; 260 261 melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry); 262 { 263 struct GNUNET_PQ_ResultSpec rs[] = { 264 GNUNET_PQ_result_spec_auto_from_type ("rc", 265 &melt->rc), 266 /* oldcoin_index not needed */ 267 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 268 &melt->h_denom_pub), 269 GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", 270 &melt->coin_sig), 271 GNUNET_PQ_result_spec_auto_from_type ("refresh_seed", 272 &melt->refresh_seed), 273 GNUNET_PQ_result_spec_allow_null ( 274 GNUNET_PQ_result_spec_auto_from_type ("blinding_seed", 275 &melt->blinding_seed), 276 &melt->no_blinding_seed), 277 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 278 &melt->amount_with_fee), 279 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh", 280 &melt->melt_fee), 281 GNUNET_PQ_result_spec_allow_null ( 282 GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", 283 &melt->h_age_commitment), 284 &melt->no_age_commitment), 285 GNUNET_PQ_result_spec_uint64 ("refresh_id", 286 &serial_id), 287 GNUNET_PQ_result_spec_end 288 }; 289 290 if (GNUNET_OK != 291 GNUNET_PQ_extract_result (result, 292 rs, 293 i)) 294 { 295 GNUNET_break (0); 296 GNUNET_free (melt); 297 chc->failed = true; 298 return; 299 } 300 } 301 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 302 tl->next = chc->head; 303 tl->type = TALER_EXCHANGEDB_TT_MELT; 304 tl->details.melt = melt; 305 tl->serial_id = serial_id; 306 tl->coin_history_id = chc->chid; 307 chc->head = tl; 308 } 309 } 310 311 312 /** 313 * Function to be called with the results of a SELECT statement 314 * that has returned @a num_results results. 315 * 316 * @param cls closure of type `struct CoinHistoryContext` 317 * @param result the postgres result 318 * @param num_results the number of results in @a result 319 */ 320 static void 321 add_coin_refund (void *cls, 322 PGresult *result, 323 unsigned int num_results) 324 { 325 struct CoinHistoryContext *chc = cls; 326 struct PostgresClosure *pg = chc->pg; 327 328 for (unsigned int i = 0; i<num_results; i++) 329 { 330 struct TALER_EXCHANGEDB_RefundListEntry *refund; 331 struct TALER_EXCHANGEDB_TransactionList *tl; 332 uint64_t serial_id; 333 334 refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry); 335 { 336 struct GNUNET_PQ_ResultSpec rs[] = { 337 GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", 338 &refund->merchant_pub), 339 GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", 340 &refund->merchant_sig), 341 GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", 342 &refund->h_contract_terms), 343 GNUNET_PQ_result_spec_uint64 ("rtransaction_id", 344 &refund->rtransaction_id), 345 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 346 &refund->refund_amount), 347 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", 348 &refund->refund_fee), 349 GNUNET_PQ_result_spec_uint64 ("refund_serial_id", 350 &serial_id), 351 GNUNET_PQ_result_spec_end 352 }; 353 354 if (GNUNET_OK != 355 GNUNET_PQ_extract_result (result, 356 rs, 357 i)) 358 { 359 GNUNET_break (0); 360 GNUNET_free (refund); 361 chc->failed = true; 362 return; 363 } 364 } 365 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 366 tl->next = chc->head; 367 tl->type = TALER_EXCHANGEDB_TT_REFUND; 368 tl->details.refund = refund; 369 tl->serial_id = serial_id; 370 tl->coin_history_id = chc->chid; 371 chc->head = tl; 372 } 373 } 374 375 376 /** 377 * Function to be called with the results of a SELECT statement 378 * that has returned @a num_results results. 379 * 380 * @param cls closure of type `struct CoinHistoryContext` 381 * @param result the postgres result 382 * @param num_results the number of results in @a result 383 */ 384 static void 385 add_coin_purse_decision (void *cls, 386 PGresult *result, 387 unsigned int num_results) 388 { 389 struct CoinHistoryContext *chc = cls; 390 struct PostgresClosure *pg = chc->pg; 391 392 for (unsigned int i = 0; i<num_results; i++) 393 { 394 struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund; 395 struct TALER_EXCHANGEDB_TransactionList *tl; 396 uint64_t serial_id; 397 398 prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry); 399 { 400 struct GNUNET_PQ_ResultSpec rs[] = { 401 GNUNET_PQ_result_spec_auto_from_type ("purse_pub", 402 &prefund->purse_pub), 403 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 404 &prefund->refund_amount), 405 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", 406 &prefund->refund_fee), 407 GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id", 408 &serial_id), 409 GNUNET_PQ_result_spec_end 410 }; 411 412 if (GNUNET_OK != 413 GNUNET_PQ_extract_result (result, 414 rs, 415 i)) 416 { 417 GNUNET_break (0); 418 GNUNET_free (prefund); 419 chc->failed = true; 420 return; 421 } 422 } 423 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 424 tl->next = chc->head; 425 tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND; 426 tl->details.purse_refund = prefund; 427 tl->serial_id = serial_id; 428 tl->coin_history_id = chc->chid; 429 chc->head = tl; 430 } 431 } 432 433 434 /** 435 * Function to be called with the results of a SELECT statement 436 * that has returned @a num_results results. 437 * 438 * @param cls closure of type `struct CoinHistoryContext` 439 * @param result the postgres result 440 * @param num_results the number of results in @a result 441 */ 442 static void 443 add_old_coin_recoup (void *cls, 444 PGresult *result, 445 unsigned int num_results) 446 { 447 struct CoinHistoryContext *chc = cls; 448 struct PostgresClosure *pg = chc->pg; 449 450 for (unsigned int i = 0; i<num_results; i++) 451 { 452 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup; 453 struct TALER_EXCHANGEDB_TransactionList *tl; 454 uint64_t serial_id; 455 456 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry); 457 { 458 struct GNUNET_PQ_ResultSpec rs[] = { 459 GNUNET_PQ_result_spec_auto_from_type ("coin_pub", 460 &recoup->coin.coin_pub), 461 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 462 &recoup->coin_sig), 463 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 464 &recoup->coin_blind), 465 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 466 &recoup->value), 467 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 468 &recoup->timestamp), 469 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 470 &recoup->coin.denom_pub_hash), 471 TALER_PQ_result_spec_denom_sig ("denom_sig", 472 &recoup->coin.denom_sig), 473 GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", 474 &serial_id), 475 GNUNET_PQ_result_spec_end 476 }; 477 478 if (GNUNET_OK != 479 GNUNET_PQ_extract_result (result, 480 rs, 481 i)) 482 { 483 GNUNET_break (0); 484 GNUNET_free (recoup); 485 chc->failed = true; 486 return; 487 } 488 recoup->old_coin_pub = *chc->coin_pub; 489 } 490 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 491 tl->next = chc->head; 492 tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP; 493 tl->details.old_coin_recoup = recoup; 494 tl->serial_id = serial_id; 495 tl->coin_history_id = chc->chid; 496 chc->head = tl; 497 } 498 } 499 500 501 /** 502 * Function to be called with the results of a SELECT statement 503 * that has returned @a num_results results. 504 * 505 * @param cls closure of type `struct CoinHistoryContext` 506 * @param result the postgres result 507 * @param num_results the number of results in @a result 508 */ 509 static void 510 add_coin_recoup (void *cls, 511 PGresult *result, 512 unsigned int num_results) 513 { 514 struct CoinHistoryContext *chc = cls; 515 struct PostgresClosure *pg = chc->pg; 516 517 for (unsigned int i = 0; i<num_results; i++) 518 { 519 struct TALER_EXCHANGEDB_RecoupListEntry *recoup; 520 struct TALER_EXCHANGEDB_TransactionList *tl; 521 uint64_t serial_id; 522 523 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry); 524 { 525 struct GNUNET_PQ_ResultSpec rs[] = { 526 GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", 527 &recoup->reserve_pub), 528 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 529 &recoup->coin_sig), 530 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 531 &recoup->h_denom_pub), 532 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 533 &recoup->coin_blind), 534 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 535 &recoup->value), 536 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 537 &recoup->timestamp), 538 GNUNET_PQ_result_spec_uint64 ("recoup_uuid", 539 &serial_id), 540 GNUNET_PQ_result_spec_end 541 }; 542 543 if (GNUNET_OK != 544 GNUNET_PQ_extract_result (result, 545 rs, 546 i)) 547 { 548 GNUNET_break (0); 549 GNUNET_free (recoup); 550 chc->failed = true; 551 return; 552 } 553 } 554 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 555 tl->next = chc->head; 556 tl->type = TALER_EXCHANGEDB_TT_RECOUP; 557 tl->details.recoup = recoup; 558 tl->serial_id = serial_id; 559 tl->coin_history_id = chc->chid; 560 chc->head = tl; 561 } 562 } 563 564 565 /** 566 * Function to be called with the results of a SELECT statement 567 * that has returned @a num_results results. 568 * 569 * @param cls closure of type `struct CoinHistoryContext` 570 * @param result the postgres result 571 * @param num_results the number of results in @a result 572 */ 573 static void 574 add_coin_recoup_refresh (void *cls, 575 PGresult *result, 576 unsigned int num_results) 577 { 578 struct CoinHistoryContext *chc = cls; 579 struct PostgresClosure *pg = chc->pg; 580 581 for (unsigned int i = 0; i<num_results; i++) 582 { 583 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup; 584 struct TALER_EXCHANGEDB_TransactionList *tl; 585 uint64_t serial_id; 586 587 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry); 588 { 589 struct GNUNET_PQ_ResultSpec rs[] = { 590 GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub", 591 &recoup->old_coin_pub), 592 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 593 &recoup->coin_sig), 594 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 595 &recoup->coin_blind), 596 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 597 &recoup->value), 598 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 599 &recoup->timestamp), 600 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 601 &recoup->coin.denom_pub_hash), 602 TALER_PQ_result_spec_denom_sig ("denom_sig", 603 &recoup->coin.denom_sig), 604 GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", 605 &serial_id), 606 GNUNET_PQ_result_spec_end 607 }; 608 609 if (GNUNET_OK != 610 GNUNET_PQ_extract_result (result, 611 rs, 612 i)) 613 { 614 GNUNET_break (0); 615 GNUNET_free (recoup); 616 chc->failed = true; 617 return; 618 } 619 recoup->coin.coin_pub = *chc->coin_pub; 620 } 621 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 622 tl->next = chc->head; 623 tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH; 624 tl->details.recoup_refresh = recoup; 625 tl->serial_id = serial_id; 626 tl->coin_history_id = chc->chid; 627 chc->head = tl; 628 } 629 } 630 631 632 /** 633 * Function to be called with the results of a SELECT statement 634 * that has returned @a num_results results. 635 * 636 * @param cls closure of type `struct CoinHistoryContext` 637 * @param result the postgres result 638 * @param num_results the number of results in @a result 639 */ 640 static void 641 add_coin_reserve_open (void *cls, 642 PGresult *result, 643 unsigned int num_results) 644 { 645 struct CoinHistoryContext *chc = cls; 646 struct PostgresClosure *pg = chc->pg; 647 648 for (unsigned int i = 0; i<num_results; i++) 649 { 650 struct TALER_EXCHANGEDB_ReserveOpenListEntry *role; 651 struct TALER_EXCHANGEDB_TransactionList *tl; 652 uint64_t serial_id; 653 654 role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry); 655 { 656 struct GNUNET_PQ_ResultSpec rs[] = { 657 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 658 &role->reserve_sig), 659 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 660 &role->coin_sig), 661 TALER_PQ_RESULT_SPEC_AMOUNT ("contribution", 662 &role->coin_contribution), 663 GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid", 664 &serial_id), 665 GNUNET_PQ_result_spec_end 666 }; 667 668 if (GNUNET_OK != 669 GNUNET_PQ_extract_result (result, 670 rs, 671 i)) 672 { 673 GNUNET_break (0); 674 GNUNET_free (role); 675 chc->failed = true; 676 return; 677 } 678 } 679 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 680 tl->next = chc->head; 681 tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN; 682 tl->details.reserve_open = role; 683 tl->serial_id = serial_id; 684 tl->coin_history_id = chc->chid; 685 chc->head = tl; 686 } 687 } 688 689 690 /** 691 * Work we need to do. 692 */ 693 struct Work 694 { 695 /** 696 * Name of the table. 697 */ 698 const char *table; 699 700 /** 701 * SQL prepared statement name. 702 */ 703 const char *statement; 704 705 /** 706 * Function to call to handle the result(s). 707 */ 708 GNUNET_PQ_PostgresResultHandler cb; 709 }; 710 711 712 /** 713 * We found a coin history entry. Lookup details 714 * from the respective table and store in @a cls. 715 * 716 * @param[in,out] cls a `struct CoinHistoryContext` 717 * @param result a coin history entry result set 718 * @param num_results total number of results in @a results 719 */ 720 static void 721 handle_history_entry (void *cls, 722 PGresult *result, 723 unsigned int num_results) 724 { 725 struct CoinHistoryContext *chc = cls; 726 struct PostgresClosure *pg = chc->pg; 727 static const struct Work work[] = { 728 [TALER_EXCHANGEDB_TT_DEPOSIT] = 729 { "coin_deposits", 730 "get_deposit_with_coin_pub", 731 &add_coin_deposit }, 732 [TALER_EXCHANGEDB_TT_MELT] = 733 { "refresh", 734 "get_refresh_by_coin", 735 &add_coin_melt }, 736 [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] = 737 { "purse_deposits", 738 "get_purse_deposit_by_coin_pub", 739 &add_coin_purse_deposit }, 740 [TALER_EXCHANGEDB_TT_PURSE_REFUND] = 741 { "purse_decision", 742 "get_purse_decision_by_coin_pub", 743 &add_coin_purse_decision }, 744 [TALER_EXCHANGEDB_TT_REFUND] = 745 { "refunds", 746 "get_refunds_by_coin", 747 &add_coin_refund }, 748 [TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP] = 749 { "recoup_refresh::OLD", 750 "recoup_by_old_coin", 751 &add_old_coin_recoup }, 752 [TALER_EXCHANGEDB_TT_RECOUP] = 753 { "recoup", 754 "recoup_by_coin", 755 &add_coin_recoup }, 756 [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] = 757 { "recoup_refresh::NEW", 758 "recoup_by_refreshed_coin", 759 &add_coin_recoup_refresh }, 760 [TALER_EXCHANGEDB_TT_RESERVE_OPEN] = 761 { "reserves_open_deposits", 762 "reserve_open_by_coin", 763 &add_coin_reserve_open }, 764 { NULL, NULL, NULL } 765 }; 766 char *table_name; 767 uint64_t serial_id; 768 struct GNUNET_PQ_ResultSpec rs[] = { 769 GNUNET_PQ_result_spec_string ("table_name", 770 &table_name), 771 GNUNET_PQ_result_spec_uint64 ("serial_id", 772 &serial_id), 773 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 774 &chc->chid), 775 GNUNET_PQ_result_spec_end 776 }; 777 struct GNUNET_PQ_QueryParam params[] = { 778 GNUNET_PQ_query_param_auto_from_type (chc->coin_pub), 779 GNUNET_PQ_query_param_uint64 (&serial_id), 780 GNUNET_PQ_query_param_end 781 }; 782 783 for (unsigned int i = 0; i<num_results; i++) 784 { 785 enum GNUNET_DB_QueryStatus qs; 786 bool found = false; 787 788 if (GNUNET_OK != 789 GNUNET_PQ_extract_result (result, 790 rs, 791 i)) 792 { 793 GNUNET_break (0); 794 chc->failed = true; 795 return; 796 } 797 798 for (unsigned int s = 0; 799 NULL != work[s].statement; 800 s++) 801 { 802 if (0 != strcmp (table_name, 803 work[s].table)) 804 continue; 805 found = true; 806 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 807 work[s].statement, 808 params, 809 work[s].cb, 810 chc); 811 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 812 "Coin %s had %d transactions at %llu in table %s\n", 813 TALER_B2S (chc->coin_pub), 814 (int) qs, 815 (unsigned long long) serial_id, 816 table_name); 817 if (0 > qs) 818 chc->failed = true; 819 break; 820 } 821 if (! found) 822 { 823 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 824 "Coin history includes unsupported table `%s`\n", 825 table_name); 826 chc->failed = true; 827 } 828 GNUNET_PQ_cleanup_result (rs); 829 if (chc->failed) 830 break; 831 } 832 } 833 834 835 enum GNUNET_DB_QueryStatus 836 TEH_PG_get_coin_transactions ( 837 void *cls, 838 bool begin_transaction, 839 const struct TALER_CoinSpendPublicKeyP *coin_pub, 840 uint64_t start_off, 841 uint64_t etag_in, 842 uint64_t *etag_out, 843 struct TALER_Amount *balance, 844 struct TALER_DenominationHashP *h_denom_pub, 845 struct TALER_EXCHANGEDB_TransactionList **tlp) 846 { 847 struct PostgresClosure *pg = cls; 848 struct GNUNET_PQ_QueryParam params[] = { 849 GNUNET_PQ_query_param_auto_from_type (coin_pub), 850 GNUNET_PQ_query_param_end 851 }; 852 struct GNUNET_PQ_QueryParam lparams[] = { 853 GNUNET_PQ_query_param_auto_from_type (coin_pub), 854 GNUNET_PQ_query_param_uint64 (&start_off), 855 GNUNET_PQ_query_param_end 856 }; 857 struct CoinHistoryContext chc = { 858 .head = NULL, 859 .coin_pub = coin_pub, 860 .pg = pg 861 }; 862 863 *tlp = NULL; 864 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 865 "Getting transactions for coin %s\n", 866 TALER_B2S (coin_pub)); 867 PREPARE (pg, 868 "get_coin_history_etag_balance", 869 "SELECT" 870 " ch.coin_history_serial_id" 871 ",kc.remaining" 872 ",denom.denom_pub_hash" 873 " FROM coin_history ch" 874 " JOIN known_coins kc" 875 " USING (coin_pub)" 876 " JOIN denominations denom" 877 " USING (denominations_serial)" 878 " WHERE coin_pub=$1" 879 " ORDER BY coin_history_serial_id DESC" 880 " LIMIT 1;"); 881 PREPARE (pg, 882 "get_coin_history", 883 "SELECT" 884 " table_name" 885 ",serial_id" 886 ",coin_history_serial_id" 887 " FROM coin_history" 888 " WHERE coin_pub=$1" 889 " AND coin_history_serial_id > $2" 890 " ORDER BY coin_history_serial_id DESC;"); 891 PREPARE (pg, 892 "get_deposit_with_coin_pub", 893 "SELECT" 894 " cdep.amount_with_fee" 895 ",denoms.fee_deposit" 896 ",denoms.denom_pub_hash" 897 ",kc.age_commitment_hash" 898 ",bdep.wallet_timestamp" 899 ",bdep.refund_deadline" 900 ",bdep.wire_deadline" 901 ",bdep.merchant_pub" 902 ",bdep.h_contract_terms" 903 ",bdep.wallet_data_hash" 904 ",bdep.wire_salt" 905 ",wt.payto_uri" 906 ",cdep.coin_sig" 907 ",cdep.coin_deposit_serial_id" 908 ",bdep.done" 909 " FROM coin_deposits cdep" 910 " JOIN batch_deposits bdep" 911 " USING (batch_deposit_serial_id)" 912 " JOIN wire_targets wt" 913 " USING (wire_target_h_payto)" 914 " JOIN known_coins kc" 915 " ON (kc.coin_pub = cdep.coin_pub)" 916 " JOIN denominations denoms" 917 " USING (denominations_serial)" 918 " WHERE cdep.coin_pub=$1" 919 " AND cdep.coin_deposit_serial_id=$2;"); 920 PREPARE (pg, 921 "get_refresh_by_coin", 922 "SELECT" 923 " rc" 924 ",refresh_seed" 925 ",blinding_seed" 926 ",old_coin_sig" 927 ",amount_with_fee" 928 ",denoms.denom_pub_hash" 929 ",denoms.fee_refresh" 930 ",kc.age_commitment_hash" 931 ",refresh_id" 932 " FROM refresh" 933 " JOIN known_coins kc" 934 " ON (refresh.old_coin_pub = kc.coin_pub)" 935 " JOIN denominations denoms" 936 " USING (denominations_serial)" 937 " WHERE old_coin_pub=$1" 938 " AND refresh_id=$2;"); 939 PREPARE (pg, 940 "get_purse_deposit_by_coin_pub", 941 "SELECT" 942 " partner_base_url" 943 ",pd.amount_with_fee" 944 ",denoms.fee_deposit" 945 ",denoms.denom_pub_hash" 946 ",pd.purse_pub" 947 ",kc.age_commitment_hash" 948 ",pd.coin_sig" 949 ",pd.purse_deposit_serial_id" 950 ",pdes.refunded" 951 " FROM purse_deposits pd" 952 " LEFT JOIN partners" 953 " USING (partner_serial_id)" 954 " JOIN purse_requests pr" 955 " USING (purse_pub)" 956 " LEFT JOIN purse_decision pdes" 957 " USING (purse_pub)" 958 " JOIN known_coins kc" 959 " ON (pd.coin_pub = kc.coin_pub)" 960 " JOIN denominations denoms" 961 " USING (denominations_serial)" 962 " WHERE pd.purse_deposit_serial_id=$2" 963 " AND pd.coin_pub=$1;"); 964 PREPARE (pg, 965 "get_purse_decision_by_coin_pub", 966 "SELECT" 967 " pdes.purse_pub" 968 ",pd.amount_with_fee" 969 ",denom.fee_refund" 970 ",pdes.purse_decision_serial_id" 971 " FROM purse_decision pdes" 972 " JOIN purse_deposits pd" 973 " USING (purse_pub)" 974 " JOIN known_coins kc" 975 " ON (pd.coin_pub = kc.coin_pub)" 976 " JOIN denominations denom" 977 " USING (denominations_serial)" 978 " WHERE pd.coin_pub=$1" 979 " AND pdes.purse_decision_serial_id=$2" 980 " AND pdes.refunded;"); 981 PREPARE (pg, 982 "get_refunds_by_coin", 983 "SELECT" 984 " bdep.merchant_pub" 985 ",ref.merchant_sig" 986 ",bdep.h_contract_terms" 987 ",ref.rtransaction_id" 988 ",ref.amount_with_fee" 989 ",denom.fee_refund" 990 ",ref.refund_serial_id" 991 " FROM refunds ref" 992 " JOIN coin_deposits cdep" 993 " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)" 994 " JOIN batch_deposits bdep" 995 " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)" 996 " JOIN known_coins kc" 997 " ON (ref.coin_pub = kc.coin_pub)" 998 " JOIN denominations denom" 999 " USING (denominations_serial)" 1000 " WHERE ref.coin_pub=$1" 1001 " AND ref.refund_serial_id=$2;"); 1002 PREPARE (pg, 1003 "recoup_by_old_coin", 1004 "SELECT" 1005 " coins.coin_pub" 1006 ",rr.coin_sig" 1007 ",rr.coin_blind" 1008 ",rr.amount" 1009 ",rr.recoup_timestamp" 1010 ",denoms.denom_pub_hash" 1011 ",coins.denom_sig" 1012 ",rr.recoup_refresh_uuid" 1013 " FROM recoup_refresh rr" 1014 " JOIN known_coins coins" 1015 " USING (coin_pub)" 1016 " JOIN denominations denoms" 1017 " USING (denominations_serial)" 1018 " WHERE recoup_refresh_uuid=$2" 1019 " AND refresh_id IN" 1020 " (SELECT refresh_id" 1021 " FROM refresh" 1022 " WHERE refresh.old_coin_pub=$1);"); 1023 PREPARE (pg, 1024 "recoup_by_coin", 1025 "SELECT" 1026 " res.reserve_pub" 1027 ",denoms.denom_pub_hash" 1028 ",rcp.coin_sig" 1029 ",rcp.coin_blind" 1030 ",rcp.amount" 1031 ",rcp.recoup_timestamp" 1032 ",rcp.recoup_uuid" 1033 " FROM recoup rcp" 1034 " JOIN withdraw ro" 1035 " USING (withdraw_id)" 1036 " JOIN reserves res" 1037 " USING (reserve_pub)" 1038 " JOIN known_coins coins" 1039 " USING (coin_pub)" 1040 " JOIN denominations denoms" 1041 " ON (denoms.denominations_serial = coins.denominations_serial)" 1042 " WHERE rcp.recoup_uuid=$2" 1043 " AND coins.coin_pub=$1;"); 1044 /* Used to obtain recoup transactions 1045 for a refreshed coin */ 1046 PREPARE (pg, 1047 "recoup_by_refreshed_coin", 1048 "SELECT" 1049 " old_coins.coin_pub AS old_coin_pub" 1050 ",rr.coin_sig" 1051 ",rr.coin_blind" 1052 ",rr.amount" 1053 ",rr.recoup_timestamp" 1054 ",denoms.denom_pub_hash" 1055 ",coins.denom_sig" 1056 ",recoup_refresh_uuid" 1057 " FROM recoup_refresh rr" 1058 " JOIN refresh rfc" 1059 " ON (rr.refresh_id = rfc.refresh_id)" 1060 " JOIN known_coins old_coins" 1061 " ON (rfc.old_coin_pub = old_coins.coin_pub)" 1062 " JOIN known_coins coins" 1063 " ON (rr.coin_pub = coins.coin_pub)" 1064 " JOIN denominations denoms" 1065 " ON (denoms.denominations_serial = coins.denominations_serial)" 1066 " WHERE rr.recoup_refresh_uuid=$2" 1067 " AND coins.coin_pub=$1;"); 1068 PREPARE (pg, 1069 "reserve_open_by_coin", 1070 "SELECT" 1071 " reserve_open_deposit_uuid" 1072 ",coin_sig" 1073 ",reserve_sig" 1074 ",contribution" 1075 " FROM reserves_open_deposits" 1076 " WHERE coin_pub=$1" 1077 " AND reserve_open_deposit_uuid=$2;"); 1078 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1079 " --- landed here 1\n"); 1080 for (unsigned int i = 0; i<RETRIES; i++) 1081 { 1082 enum GNUNET_DB_QueryStatus qs; 1083 uint64_t end; 1084 struct GNUNET_PQ_ResultSpec rs[] = { 1085 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 1086 &end), 1087 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 1088 h_denom_pub), 1089 TALER_PQ_RESULT_SPEC_AMOUNT ("remaining", 1090 balance), 1091 GNUNET_PQ_result_spec_end 1092 }; 1093 1094 if (begin_transaction) 1095 { 1096 if (GNUNET_OK != 1097 TEH_PG_start_read_committed (pg, 1098 "get-coin-transactions")) 1099 { 1100 GNUNET_break (0); 1101 return GNUNET_DB_STATUS_HARD_ERROR; 1102 } 1103 } 1104 /* First only check the last item, to see if 1105 we even need to iterate */ 1106 qs = GNUNET_PQ_eval_prepared_singleton_select ( 1107 pg->conn, 1108 "get_coin_history_etag_balance", 1109 params, 1110 rs); 1111 switch (qs) 1112 { 1113 case GNUNET_DB_STATUS_HARD_ERROR: 1114 if (begin_transaction) 1115 TEH_PG_rollback (pg); 1116 return qs; 1117 case GNUNET_DB_STATUS_SOFT_ERROR: 1118 if (begin_transaction) 1119 TEH_PG_rollback (pg); 1120 continue; 1121 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1122 if (begin_transaction) 1123 TEH_PG_rollback (pg); 1124 return qs; 1125 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1126 *etag_out = end; 1127 if (end == etag_in) 1128 return qs; 1129 } 1130 /* We indeed need to iterate over the history */ 1131 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1132 "Current ETag for coin %s is %llu\n", 1133 TALER_B2S (coin_pub), 1134 (unsigned long long) end); 1135 1136 qs = GNUNET_PQ_eval_prepared_multi_select ( 1137 pg->conn, 1138 "get_coin_history", 1139 lparams, 1140 &handle_history_entry, 1141 &chc); 1142 switch (qs) 1143 { 1144 case GNUNET_DB_STATUS_HARD_ERROR: 1145 if (begin_transaction) 1146 TEH_PG_rollback (pg); 1147 return qs; 1148 case GNUNET_DB_STATUS_SOFT_ERROR: 1149 if (begin_transaction) 1150 TEH_PG_rollback (pg); 1151 continue; 1152 default: 1153 break; 1154 } 1155 if (chc.failed) 1156 { 1157 if (begin_transaction) 1158 TEH_PG_rollback (pg); 1159 TEH_COMMON_free_coin_transaction_list (pg, 1160 chc.head); 1161 return GNUNET_DB_STATUS_SOFT_ERROR; 1162 } 1163 if (! begin_transaction) 1164 { 1165 *tlp = chc.head; 1166 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1167 } 1168 qs = TEH_PG_commit (pg); 1169 switch (qs) 1170 { 1171 case GNUNET_DB_STATUS_HARD_ERROR: 1172 TEH_COMMON_free_coin_transaction_list (pg, 1173 chc.head); 1174 chc.head = NULL; 1175 return qs; 1176 case GNUNET_DB_STATUS_SOFT_ERROR: 1177 TEH_COMMON_free_coin_transaction_list (pg, 1178 chc.head); 1179 chc.head = NULL; 1180 continue; 1181 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1182 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1183 *tlp = chc.head; 1184 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1185 } 1186 } 1187 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1188 " --- landed here 2\n"); 1189 return GNUNET_DB_STATUS_SOFT_ERROR; 1190 }