pg_get_coin_transactions.c (39797B)
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/taler_error_codes.h" 22 #include "taler/taler_exchangedb_lib.h" 23 #include "taler/taler_pq_lib.h" 24 #include "taler/exchange-database/get_coin_transactions.h" 25 #include "helper.h" 26 #include "taler/exchange-database/start_read_committed.h" 27 #include "taler/exchange-database/commit.h" 28 #include "taler/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_uint64 ("reserve_open_deposit_uuid", 662 &serial_id), 663 GNUNET_PQ_result_spec_end 664 }; 665 666 if (GNUNET_OK != 667 GNUNET_PQ_extract_result (result, 668 rs, 669 i)) 670 { 671 GNUNET_break (0); 672 GNUNET_free (role); 673 chc->failed = true; 674 return; 675 } 676 } 677 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 678 tl->next = chc->head; 679 tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN; 680 tl->details.reserve_open = role; 681 tl->serial_id = serial_id; 682 tl->coin_history_id = chc->chid; 683 chc->head = tl; 684 } 685 } 686 687 688 /** 689 * Work we need to do. 690 */ 691 struct Work 692 { 693 /** 694 * Name of the table. 695 */ 696 const char *table; 697 698 /** 699 * SQL prepared statement name. 700 */ 701 const char *statement; 702 703 /** 704 * Function to call to handle the result(s). 705 */ 706 GNUNET_PQ_PostgresResultHandler cb; 707 }; 708 709 710 /** 711 * We found a coin history entry. Lookup details 712 * from the respective table and store in @a cls. 713 * 714 * @param[in,out] cls a `struct CoinHistoryContext` 715 * @param result a coin history entry result set 716 * @param num_results total number of results in @a results 717 */ 718 static void 719 handle_history_entry (void *cls, 720 PGresult *result, 721 unsigned int num_results) 722 { 723 struct CoinHistoryContext *chc = cls; 724 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 725 static const struct Work work[] = { 726 [TALER_EXCHANGEDB_TT_DEPOSIT] = 727 { "coin_deposits", 728 "get_deposit_with_coin_pub", 729 &add_coin_deposit }, 730 [TALER_EXCHANGEDB_TT_MELT] = 731 { "refresh", 732 "get_refresh_by_coin", 733 &add_coin_melt }, 734 [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] = 735 { "purse_deposits", 736 "get_purse_deposit_by_coin_pub", 737 &add_coin_purse_deposit }, 738 [TALER_EXCHANGEDB_TT_PURSE_REFUND] = 739 { "purse_decision", 740 "get_purse_decision_by_coin_pub", 741 &add_coin_purse_decision }, 742 [TALER_EXCHANGEDB_TT_REFUND] = 743 { "refunds", 744 "get_refunds_by_coin", 745 &add_coin_refund }, 746 [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] = 747 { "recoup", 748 "recoup_by_coin", 749 &add_coin_recoup }, 750 [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] = 751 { "recoup_refresh::NEW", 752 "recoup_by_refreshed_coin", 753 &add_coin_recoup_refresh }, 754 [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] = 755 { "recoup_refresh::OLD", 756 "recoup_by_old_coin", 757 &add_old_coin_recoup }, 758 [TALER_EXCHANGEDB_TT_RESERVE_OPEN] = 759 { "reserves_open_deposits", 760 "reserve_open_by_coin", 761 &add_coin_reserve_open }, 762 { NULL, NULL, NULL } 763 }; 764 char *table_name; 765 uint64_t serial_id; 766 struct GNUNET_PQ_ResultSpec rs[] = { 767 GNUNET_PQ_result_spec_string ("table_name", 768 &table_name), 769 GNUNET_PQ_result_spec_uint64 ("serial_id", 770 &serial_id), 771 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 772 &chc->chid), 773 GNUNET_PQ_result_spec_end 774 }; 775 struct GNUNET_PQ_QueryParam params[] = { 776 GNUNET_PQ_query_param_auto_from_type (chc->coin_pub), 777 GNUNET_PQ_query_param_uint64 (&serial_id), 778 GNUNET_PQ_query_param_end 779 }; 780 781 for (unsigned int i = 0; i<num_results; i++) 782 { 783 enum GNUNET_DB_QueryStatus qs; 784 bool found = false; 785 786 if (GNUNET_OK != 787 GNUNET_PQ_extract_result (result, 788 rs, 789 i)) 790 { 791 GNUNET_break (0); 792 chc->failed = true; 793 return; 794 } 795 796 for (unsigned int s = 0; 797 NULL != work[s].statement; 798 s++) 799 { 800 if (0 != strcmp (table_name, 801 work[s].table)) 802 continue; 803 found = true; 804 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 805 work[s].statement, 806 params, 807 work[s].cb, 808 chc); 809 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 810 "Coin %s had %d transactions at %llu in table %s\n", 811 TALER_B2S (chc->coin_pub), 812 (int) qs, 813 (unsigned long long) serial_id, 814 table_name); 815 if (0 > qs) 816 chc->failed = true; 817 break; 818 } 819 if (! found) 820 { 821 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 822 "Coin history includes unsupported table `%s`\n", 823 table_name); 824 chc->failed = true; 825 } 826 GNUNET_PQ_cleanup_result (rs); 827 if (chc->failed) 828 break; 829 } 830 } 831 832 833 enum GNUNET_DB_QueryStatus 834 TALER_EXCHANGEDB_get_coin_transactions ( 835 struct TALER_EXCHANGEDB_PostgresContext *pg, 836 bool begin_transaction, 837 const struct TALER_CoinSpendPublicKeyP *coin_pub, 838 uint64_t start_off, 839 uint64_t etag_in, 840 uint64_t *etag_out, 841 struct TALER_Amount *balance, 842 struct TALER_DenominationHashP *h_denom_pub, 843 struct TALER_EXCHANGEDB_TransactionList **tlp) 844 { 845 struct GNUNET_PQ_QueryParam params[] = { 846 GNUNET_PQ_query_param_auto_from_type (coin_pub), 847 GNUNET_PQ_query_param_end 848 }; 849 struct GNUNET_PQ_QueryParam lparams[] = { 850 GNUNET_PQ_query_param_auto_from_type (coin_pub), 851 GNUNET_PQ_query_param_uint64 (&start_off), 852 GNUNET_PQ_query_param_end 853 }; 854 struct CoinHistoryContext chc = { 855 .head = NULL, 856 .coin_pub = coin_pub, 857 .pg = pg 858 }; 859 860 *tlp = NULL; 861 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 862 "Getting transactions for coin %s\n", 863 TALER_B2S (coin_pub)); 864 PREPARE (pg, 865 "get_coin_history_etag_balance", 866 "SELECT" 867 " ch.coin_history_serial_id" 868 ",kc.remaining" 869 ",denom.denom_pub_hash" 870 " FROM coin_history ch" 871 " JOIN known_coins kc" 872 " USING (coin_pub)" 873 " JOIN denominations denom" 874 " USING (denominations_serial)" 875 " WHERE coin_pub=$1" 876 " ORDER BY coin_history_serial_id DESC" 877 " LIMIT 1;"); 878 PREPARE (pg, 879 "get_coin_history", 880 "SELECT" 881 " table_name" 882 ",serial_id" 883 ",coin_history_serial_id" 884 " FROM coin_history" 885 " WHERE coin_pub=$1" 886 " AND coin_history_serial_id > $2" 887 " ORDER BY coin_history_serial_id DESC;"); 888 PREPARE (pg, 889 "get_deposit_with_coin_pub", 890 "SELECT" 891 " cdep.amount_with_fee" 892 ",denoms.fee_deposit" 893 ",denoms.denom_pub_hash" 894 ",kc.age_commitment_hash" 895 ",bdep.wallet_timestamp" 896 ",bdep.refund_deadline" 897 ",bdep.wire_deadline" 898 ",bdep.merchant_pub" 899 ",bdep.h_contract_terms" 900 ",bdep.wallet_data_hash" 901 ",bdep.wire_salt" 902 ",wt.payto_uri" 903 ",cdep.coin_sig" 904 ",cdep.coin_deposit_serial_id" 905 ",bdep.done" 906 " FROM coin_deposits cdep" 907 " JOIN batch_deposits bdep" 908 " USING (batch_deposit_serial_id)" 909 " JOIN wire_targets wt" 910 " USING (wire_target_h_payto)" 911 " JOIN known_coins kc" 912 " ON (kc.coin_pub = cdep.coin_pub)" 913 " JOIN denominations denoms" 914 " USING (denominations_serial)" 915 " WHERE cdep.coin_pub=$1" 916 " AND cdep.coin_deposit_serial_id=$2;"); 917 PREPARE (pg, 918 "get_refresh_by_coin", 919 "SELECT" 920 " rc" 921 ",refresh_seed" 922 ",blinding_seed" 923 ",old_coin_sig" 924 ",amount_with_fee" 925 ",denoms.denom_pub_hash" 926 ",denoms.fee_refresh" 927 ",kc.age_commitment_hash" 928 ",refresh_id" 929 " FROM refresh" 930 " JOIN known_coins kc" 931 " ON (refresh.old_coin_pub = kc.coin_pub)" 932 " JOIN denominations denoms" 933 " USING (denominations_serial)" 934 " WHERE old_coin_pub=$1" 935 " AND refresh_id=$2;"); 936 PREPARE (pg, 937 "get_purse_deposit_by_coin_pub", 938 "SELECT" 939 " partner_base_url" 940 ",pd.amount_with_fee" 941 ",denoms.fee_deposit" 942 ",denoms.denom_pub_hash" 943 ",pd.purse_pub" 944 ",kc.age_commitment_hash" 945 ",pd.coin_sig" 946 ",pd.purse_deposit_serial_id" 947 ",pdes.refunded" 948 " FROM purse_deposits pd" 949 " LEFT JOIN partners" 950 " USING (partner_serial_id)" 951 " JOIN purse_requests pr" 952 " USING (purse_pub)" 953 " LEFT JOIN purse_decision pdes" 954 " USING (purse_pub)" 955 " JOIN known_coins kc" 956 " ON (pd.coin_pub = kc.coin_pub)" 957 " JOIN denominations denoms" 958 " USING (denominations_serial)" 959 " WHERE pd.purse_deposit_serial_id=$2" 960 " AND pd.coin_pub=$1;"); 961 PREPARE (pg, 962 "get_purse_decision_by_coin_pub", 963 "SELECT" 964 " pdes.purse_pub" 965 ",pd.amount_with_fee" 966 ",denom.fee_refund" 967 ",pdes.purse_decision_serial_id" 968 " FROM purse_decision pdes" 969 " JOIN purse_deposits pd" 970 " USING (purse_pub)" 971 " JOIN known_coins kc" 972 " ON (pd.coin_pub = kc.coin_pub)" 973 " JOIN denominations denom" 974 " USING (denominations_serial)" 975 " WHERE pd.coin_pub=$1" 976 " AND pdes.purse_decision_serial_id=$2" 977 " AND pdes.refunded;"); 978 PREPARE (pg, 979 "get_refunds_by_coin", 980 "SELECT" 981 " bdep.merchant_pub" 982 ",ref.merchant_sig" 983 ",bdep.h_contract_terms" 984 ",ref.rtransaction_id" 985 ",ref.amount_with_fee" 986 ",denom.fee_refund" 987 ",ref.refund_serial_id" 988 " FROM refunds ref" 989 " JOIN coin_deposits cdep" 990 " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)" 991 " JOIN batch_deposits bdep" 992 " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)" 993 " JOIN known_coins kc" 994 " ON (ref.coin_pub = kc.coin_pub)" 995 " JOIN denominations denom" 996 " USING (denominations_serial)" 997 " WHERE ref.coin_pub=$1" 998 " AND ref.refund_serial_id=$2;"); 999 PREPARE (pg, 1000 "recoup_by_old_coin", 1001 "SELECT" 1002 " coins.coin_pub" 1003 ",rr.coin_sig" 1004 ",rr.coin_blind" 1005 ",rr.amount" 1006 ",rr.recoup_timestamp" 1007 ",denoms.denom_pub_hash" 1008 ",coins.denom_sig" 1009 ",rr.recoup_refresh_uuid" 1010 " FROM recoup_refresh rr" 1011 " JOIN known_coins coins" 1012 " USING (coin_pub)" 1013 " JOIN denominations denoms" 1014 " USING (denominations_serial)" 1015 " WHERE recoup_refresh_uuid=$2" 1016 " AND refresh_id IN" 1017 " (SELECT refresh_id" 1018 " FROM refresh" 1019 " WHERE refresh.old_coin_pub=$1);"); 1020 PREPARE (pg, 1021 "recoup_by_coin", 1022 "SELECT" 1023 " res.reserve_pub" 1024 ",denoms.denom_pub_hash" 1025 ",rcp.coin_sig" 1026 ",rcp.coin_blind" 1027 ",rcp.amount" 1028 ",rcp.recoup_timestamp" 1029 ",rcp.recoup_uuid" 1030 " FROM recoup rcp" 1031 " JOIN withdraw ro" 1032 " USING (withdraw_id)" 1033 " JOIN reserves res" 1034 " USING (reserve_pub)" 1035 " JOIN known_coins coins" 1036 " USING (coin_pub)" 1037 " JOIN denominations denoms" 1038 " ON (denoms.denominations_serial = coins.denominations_serial)" 1039 " WHERE rcp.recoup_uuid=$2" 1040 " AND coins.coin_pub=$1;"); 1041 /* Used to obtain recoup transactions 1042 for a refreshed coin */ 1043 PREPARE (pg, 1044 "recoup_by_refreshed_coin", 1045 "SELECT" 1046 " old_coins.coin_pub AS old_coin_pub" 1047 ",rr.coin_sig" 1048 ",rr.coin_blind" 1049 ",rr.amount" 1050 ",rr.recoup_timestamp" 1051 ",denoms.denom_pub_hash" 1052 ",coins.denom_sig" 1053 ",recoup_refresh_uuid" 1054 " FROM recoup_refresh rr" 1055 " JOIN refresh rfc" 1056 " ON (rr.refresh_id = rfc.refresh_id)" 1057 " JOIN known_coins old_coins" 1058 " ON (rfc.old_coin_pub = old_coins.coin_pub)" 1059 " JOIN known_coins coins" 1060 " ON (rr.coin_pub = coins.coin_pub)" 1061 " JOIN denominations denoms" 1062 " ON (denoms.denominations_serial = coins.denominations_serial)" 1063 " WHERE rr.recoup_refresh_uuid=$2" 1064 " AND coins.coin_pub=$1;"); 1065 PREPARE (pg, 1066 "reserve_open_by_coin", 1067 "SELECT" 1068 " reserve_open_deposit_uuid" 1069 ",coin_sig" 1070 ",reserve_sig" 1071 ",contribution" 1072 " FROM reserves_open_deposits" 1073 " WHERE coin_pub=$1" 1074 " AND reserve_open_deposit_uuid=$2;"); 1075 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1076 " --- landed here 1\n"); 1077 for (unsigned int i = 0; i<RETRIES; i++) 1078 { 1079 enum GNUNET_DB_QueryStatus qs; 1080 uint64_t end; 1081 struct GNUNET_PQ_ResultSpec rs[] = { 1082 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 1083 &end), 1084 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 1085 h_denom_pub), 1086 TALER_PQ_RESULT_SPEC_AMOUNT ("remaining", 1087 balance), 1088 GNUNET_PQ_result_spec_end 1089 }; 1090 1091 if (begin_transaction) 1092 { 1093 if (GNUNET_OK != 1094 TALER_TALER_EXCHANGEDB_start_read_committed (pg, 1095 "get-coin-transactions")) 1096 { 1097 GNUNET_break (0); 1098 return GNUNET_DB_STATUS_HARD_ERROR; 1099 } 1100 } 1101 /* First only check the last item, to see if 1102 we even need to iterate */ 1103 qs = GNUNET_PQ_eval_prepared_singleton_select ( 1104 pg->conn, 1105 "get_coin_history_etag_balance", 1106 params, 1107 rs); 1108 switch (qs) 1109 { 1110 case GNUNET_DB_STATUS_HARD_ERROR: 1111 if (begin_transaction) 1112 TALER_EXCHANGEDB_rollback (pg); 1113 return qs; 1114 case GNUNET_DB_STATUS_SOFT_ERROR: 1115 if (begin_transaction) 1116 TALER_EXCHANGEDB_rollback (pg); 1117 continue; 1118 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1119 if (begin_transaction) 1120 TALER_EXCHANGEDB_rollback (pg); 1121 return qs; 1122 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1123 *etag_out = end; 1124 if (end == etag_in) 1125 return qs; 1126 } 1127 /* We indeed need to iterate over the history */ 1128 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1129 "Current ETag for coin %s is %llu\n", 1130 TALER_B2S (coin_pub), 1131 (unsigned long long) end); 1132 1133 qs = GNUNET_PQ_eval_prepared_multi_select ( 1134 pg->conn, 1135 "get_coin_history", 1136 lparams, 1137 &handle_history_entry, 1138 &chc); 1139 switch (qs) 1140 { 1141 case GNUNET_DB_STATUS_HARD_ERROR: 1142 if (begin_transaction) 1143 TALER_EXCHANGEDB_rollback (pg); 1144 return qs; 1145 case GNUNET_DB_STATUS_SOFT_ERROR: 1146 if (begin_transaction) 1147 TALER_EXCHANGEDB_rollback (pg); 1148 continue; 1149 default: 1150 break; 1151 } 1152 if (chc.failed) 1153 { 1154 if (begin_transaction) 1155 TALER_EXCHANGEDB_rollback (pg); 1156 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1157 return GNUNET_DB_STATUS_SOFT_ERROR; 1158 } 1159 if (! begin_transaction) 1160 { 1161 *tlp = chc.head; 1162 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1163 } 1164 qs = TALER_EXCHANGEDB_commit (pg); 1165 switch (qs) 1166 { 1167 case GNUNET_DB_STATUS_HARD_ERROR: 1168 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1169 chc.head = NULL; 1170 return qs; 1171 case GNUNET_DB_STATUS_SOFT_ERROR: 1172 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1173 chc.head = NULL; 1174 continue; 1175 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1176 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1177 *tlp = chc.head; 1178 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1179 } 1180 } 1181 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1182 " --- landed here 2\n"); 1183 return GNUNET_DB_STATUS_SOFT_ERROR; 1184 }