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