get_coin_transactions.c (41454B)
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 TALER_DenominationHashP *denom_pub_hashes = NULL; 262 struct GNUNET_PQ_ResultSpec rs[] = { 263 GNUNET_PQ_result_spec_auto_from_type ("rc", 264 &melt->rc), 265 /* oldcoin_index not needed */ 266 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 267 &melt->h_denom_pub), 268 GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", 269 &melt->coin_sig), 270 GNUNET_PQ_result_spec_auto_from_type ("refresh_seed", 271 &melt->refresh_seed), 272 TALER_PQ_result_spec_array_denom_hash (pg->conn, 273 "denom_pub_hashes", 274 &melt->num_coins, 275 &denom_pub_hashes), 276 GNUNET_PQ_result_spec_allow_null ( 277 GNUNET_PQ_result_spec_auto_from_type ("blinding_seed", 278 &melt->blinding_seed), 279 &melt->no_blinding_seed), 280 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 281 &melt->amount_with_fee), 282 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh", 283 &melt->melt_fee), 284 GNUNET_PQ_result_spec_allow_null ( 285 GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", 286 &melt->h_age_commitment), 287 &melt->no_age_commitment), 288 GNUNET_PQ_result_spec_uint64 ("refresh_id", 289 &serial_id), 290 GNUNET_PQ_result_spec_end 291 }; 292 293 if (GNUNET_OK != 294 GNUNET_PQ_extract_result (result, 295 rs, 296 i)) 297 { 298 GNUNET_break (0); 299 GNUNET_PQ_cleanup_result (rs); 300 GNUNET_free (melt); 301 chc->failed = true; 302 return; 303 } 304 melt->denom_pub_hashes = denom_pub_hashes; 305 denom_pub_hashes = NULL; 306 GNUNET_PQ_cleanup_result (rs); 307 } 308 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 309 tl->next = chc->head; 310 tl->type = TALER_EXCHANGEDB_TT_MELT; 311 tl->details.melt = melt; 312 tl->serial_id = serial_id; 313 tl->coin_history_id = chc->chid; 314 chc->head = tl; 315 } 316 } 317 318 319 /** 320 * Function to be called with the results of a SELECT statement 321 * that has returned @a num_results results. 322 * 323 * @param cls closure of type `struct CoinHistoryContext` 324 * @param result the postgres result 325 * @param num_results the number of results in @a result 326 */ 327 static void 328 add_coin_refund (void *cls, 329 PGresult *result, 330 unsigned int num_results) 331 { 332 struct CoinHistoryContext *chc = cls; 333 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 334 335 for (unsigned int i = 0; i<num_results; i++) 336 { 337 struct TALER_EXCHANGEDB_RefundListEntry *refund; 338 struct TALER_EXCHANGEDB_TransactionList *tl; 339 uint64_t serial_id; 340 341 refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry); 342 { 343 struct GNUNET_PQ_ResultSpec rs[] = { 344 GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", 345 &refund->merchant_pub), 346 GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", 347 &refund->merchant_sig), 348 GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", 349 &refund->h_contract_terms), 350 GNUNET_PQ_result_spec_uint64 ("rtransaction_id", 351 &refund->rtransaction_id), 352 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 353 &refund->refund_amount), 354 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", 355 &refund->refund_fee), 356 GNUNET_PQ_result_spec_uint64 ("refund_serial_id", 357 &serial_id), 358 GNUNET_PQ_result_spec_end 359 }; 360 361 if (GNUNET_OK != 362 GNUNET_PQ_extract_result (result, 363 rs, 364 i)) 365 { 366 GNUNET_break (0); 367 GNUNET_free (refund); 368 chc->failed = true; 369 return; 370 } 371 } 372 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 373 tl->next = chc->head; 374 tl->type = TALER_EXCHANGEDB_TT_REFUND; 375 tl->details.refund = refund; 376 tl->serial_id = serial_id; 377 tl->coin_history_id = chc->chid; 378 chc->head = tl; 379 } 380 } 381 382 383 /** 384 * Function to be called with the results of a SELECT statement 385 * that has returned @a num_results results. 386 * 387 * @param cls closure of type `struct CoinHistoryContext` 388 * @param result the postgres result 389 * @param num_results the number of results in @a result 390 */ 391 static void 392 add_coin_purse_decision (void *cls, 393 PGresult *result, 394 unsigned int num_results) 395 { 396 struct CoinHistoryContext *chc = cls; 397 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 398 399 for (unsigned int i = 0; i<num_results; i++) 400 { 401 struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund; 402 struct TALER_EXCHANGEDB_TransactionList *tl; 403 uint64_t serial_id; 404 405 prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry); 406 { 407 struct GNUNET_PQ_ResultSpec rs[] = { 408 GNUNET_PQ_result_spec_auto_from_type ("purse_pub", 409 &prefund->purse_pub), 410 TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", 411 &prefund->refund_amount), 412 TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", 413 &prefund->refund_fee), 414 GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id", 415 &serial_id), 416 GNUNET_PQ_result_spec_end 417 }; 418 419 if (GNUNET_OK != 420 GNUNET_PQ_extract_result (result, 421 rs, 422 i)) 423 { 424 GNUNET_break (0); 425 GNUNET_free (prefund); 426 chc->failed = true; 427 return; 428 } 429 } 430 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 431 tl->next = chc->head; 432 tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND; 433 tl->details.purse_refund = prefund; 434 tl->serial_id = serial_id; 435 tl->coin_history_id = chc->chid; 436 chc->head = tl; 437 } 438 } 439 440 441 /** 442 * Function to be called with the results of a SELECT statement 443 * that has returned @a num_results results. 444 * 445 * @param cls closure of type `struct CoinHistoryContext` 446 * @param result the postgres result 447 * @param num_results the number of results in @a result 448 */ 449 static void 450 add_old_coin_recoup (void *cls, 451 PGresult *result, 452 unsigned int num_results) 453 { 454 struct CoinHistoryContext *chc = cls; 455 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 456 457 for (unsigned int i = 0; i<num_results; i++) 458 { 459 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup; 460 struct TALER_EXCHANGEDB_TransactionList *tl; 461 uint64_t serial_id; 462 463 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry); 464 { 465 struct GNUNET_PQ_ResultSpec rs[] = { 466 GNUNET_PQ_result_spec_auto_from_type ("coin_pub", 467 &recoup->coin.coin_pub), 468 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 469 &recoup->coin_sig), 470 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 471 &recoup->coin_blind), 472 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 473 &recoup->value), 474 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 475 &recoup->timestamp), 476 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 477 &recoup->coin.denom_pub_hash), 478 TALER_PQ_result_spec_denom_sig ("denom_sig", 479 &recoup->coin.denom_sig), 480 GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", 481 &serial_id), 482 GNUNET_PQ_result_spec_end 483 }; 484 485 if (GNUNET_OK != 486 GNUNET_PQ_extract_result (result, 487 rs, 488 i)) 489 { 490 GNUNET_break (0); 491 GNUNET_free (recoup); 492 chc->failed = true; 493 return; 494 } 495 recoup->old_coin_pub = *chc->coin_pub; 496 } 497 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 498 tl->next = chc->head; 499 tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER; 500 tl->details.old_coin_recoup = recoup; 501 tl->serial_id = serial_id; 502 tl->coin_history_id = chc->chid; 503 chc->head = tl; 504 } 505 } 506 507 508 /** 509 * Function to be called with the results of a SELECT statement 510 * that has returned @a num_results results. 511 * 512 * @param cls closure of type `struct CoinHistoryContext` 513 * @param result the postgres result 514 * @param num_results the number of results in @a result 515 */ 516 static void 517 add_coin_recoup (void *cls, 518 PGresult *result, 519 unsigned int num_results) 520 { 521 struct CoinHistoryContext *chc = cls; 522 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 523 524 for (unsigned int i = 0; i<num_results; i++) 525 { 526 struct TALER_EXCHANGEDB_RecoupListEntry *recoup; 527 struct TALER_EXCHANGEDB_TransactionList *tl; 528 uint64_t serial_id; 529 530 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry); 531 { 532 struct GNUNET_PQ_ResultSpec rs[] = { 533 GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", 534 &recoup->reserve_pub), 535 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 536 &recoup->coin_sig), 537 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 538 &recoup->h_denom_pub), 539 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 540 &recoup->coin_blind), 541 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 542 &recoup->value), 543 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 544 &recoup->timestamp), 545 GNUNET_PQ_result_spec_uint64 ("recoup_uuid", 546 &serial_id), 547 GNUNET_PQ_result_spec_end 548 }; 549 550 if (GNUNET_OK != 551 GNUNET_PQ_extract_result (result, 552 rs, 553 i)) 554 { 555 GNUNET_break (0); 556 GNUNET_free (recoup); 557 chc->failed = true; 558 return; 559 } 560 } 561 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 562 tl->next = chc->head; 563 tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW; 564 tl->details.recoup = recoup; 565 tl->serial_id = serial_id; 566 tl->coin_history_id = chc->chid; 567 chc->head = tl; 568 } 569 } 570 571 572 /** 573 * Function to be called with the results of a SELECT statement 574 * that has returned @a num_results results. 575 * 576 * @param cls closure of type `struct CoinHistoryContext` 577 * @param result the postgres result 578 * @param num_results the number of results in @a result 579 */ 580 static void 581 add_coin_recoup_refresh (void *cls, 582 PGresult *result, 583 unsigned int num_results) 584 { 585 struct CoinHistoryContext *chc = cls; 586 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 587 588 for (unsigned int i = 0; i<num_results; i++) 589 { 590 struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup; 591 struct TALER_EXCHANGEDB_TransactionList *tl; 592 uint64_t serial_id; 593 594 recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry); 595 { 596 struct GNUNET_PQ_ResultSpec rs[] = { 597 GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub", 598 &recoup->old_coin_pub), 599 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 600 &recoup->coin_sig), 601 GNUNET_PQ_result_spec_auto_from_type ("coin_blind", 602 &recoup->coin_blind), 603 TALER_PQ_RESULT_SPEC_AMOUNT ("amount", 604 &recoup->value), 605 GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", 606 &recoup->timestamp), 607 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 608 &recoup->coin.denom_pub_hash), 609 TALER_PQ_result_spec_denom_sig ("denom_sig", 610 &recoup->coin.denom_sig), 611 GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", 612 &serial_id), 613 GNUNET_PQ_result_spec_end 614 }; 615 616 if (GNUNET_OK != 617 GNUNET_PQ_extract_result (result, 618 rs, 619 i)) 620 { 621 GNUNET_break (0); 622 GNUNET_free (recoup); 623 chc->failed = true; 624 return; 625 } 626 recoup->coin.coin_pub = *chc->coin_pub; 627 } 628 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 629 tl->next = chc->head; 630 tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH; 631 tl->details.recoup_refresh = recoup; 632 tl->serial_id = serial_id; 633 tl->coin_history_id = chc->chid; 634 chc->head = tl; 635 } 636 } 637 638 639 /** 640 * Function to be called with the results of a SELECT statement 641 * that has returned @a num_results results. 642 * 643 * @param cls closure of type `struct CoinHistoryContext` 644 * @param result the postgres result 645 * @param num_results the number of results in @a result 646 */ 647 static void 648 add_coin_reserve_open (void *cls, 649 PGresult *result, 650 unsigned int num_results) 651 { 652 struct CoinHistoryContext *chc = cls; 653 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 654 655 for (unsigned int i = 0; i<num_results; i++) 656 { 657 struct TALER_EXCHANGEDB_ReserveOpenListEntry *role; 658 struct TALER_EXCHANGEDB_TransactionList *tl; 659 uint64_t serial_id; 660 661 role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry); 662 { 663 struct GNUNET_PQ_ResultSpec rs[] = { 664 GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", 665 &role->reserve_sig), 666 GNUNET_PQ_result_spec_auto_from_type ("coin_sig", 667 &role->coin_sig), 668 TALER_PQ_RESULT_SPEC_AMOUNT ("contribution", 669 &role->coin_contribution), 670 GNUNET_PQ_result_spec_allow_null ( 671 GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", 672 &role->h_age_commitment), 673 &role->no_age_commitment), 674 GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid", 675 &serial_id), 676 GNUNET_PQ_result_spec_end 677 }; 678 679 if (GNUNET_OK != 680 GNUNET_PQ_extract_result (result, 681 rs, 682 i)) 683 { 684 GNUNET_break (0); 685 GNUNET_free (role); 686 chc->failed = true; 687 return; 688 } 689 } 690 tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); 691 tl->next = chc->head; 692 tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN; 693 tl->details.reserve_open = role; 694 tl->serial_id = serial_id; 695 tl->coin_history_id = chc->chid; 696 chc->head = tl; 697 } 698 } 699 700 701 /** 702 * Work we need to do. 703 */ 704 struct Work 705 { 706 /** 707 * Name of the table. 708 */ 709 const char *table; 710 711 /** 712 * SQL prepared statement name. 713 */ 714 const char *statement; 715 716 /** 717 * Function to call to handle the result(s). 718 */ 719 GNUNET_PQ_PostgresResultHandler cb; 720 }; 721 722 723 /** 724 * We found a coin history entry. Lookup details 725 * from the respective table and store in @a cls. 726 * 727 * @param[in,out] cls a `struct CoinHistoryContext` 728 * @param result a coin history entry result set 729 * @param num_results total number of results in @a results 730 */ 731 static void 732 handle_history_entry (void *cls, 733 PGresult *result, 734 unsigned int num_results) 735 { 736 struct CoinHistoryContext *chc = cls; 737 struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg; 738 static const struct Work work[] = { 739 [TALER_EXCHANGEDB_TT_DEPOSIT] = 740 { "coin_deposits", 741 "get_coin_transactions_deposit_with_coin_pub", 742 &add_coin_deposit }, 743 [TALER_EXCHANGEDB_TT_MELT] = 744 { "refresh", 745 "get_coin_transactions_refresh_by_coin", 746 &add_coin_melt }, 747 [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] = 748 { "purse_deposits", 749 "get_coin_transactions_purse_deposit_by_coin_pub", 750 &add_coin_purse_deposit }, 751 [TALER_EXCHANGEDB_TT_PURSE_REFUND] = 752 { "purse_decision", 753 "get_coin_transactions_purse_decision_by_coin_pub", 754 &add_coin_purse_decision }, 755 [TALER_EXCHANGEDB_TT_REFUND] = 756 { "refunds", 757 "get_coin_transactions_refunds_by_coin", 758 &add_coin_refund }, 759 [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] = 760 { "recoup", 761 "get_coin_transactions_recoup_by_coin", 762 &add_coin_recoup }, 763 [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] = 764 { "recoup_refresh::NEW", 765 "get_coin_transactions_recoup_by_refreshed_coin", 766 &add_coin_recoup_refresh }, 767 [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] = 768 { "recoup_refresh::OLD", 769 "get_coin_transactions_recoup_by_old_coin", 770 &add_old_coin_recoup }, 771 [TALER_EXCHANGEDB_TT_RESERVE_OPEN] = 772 { "reserves_open_deposits", 773 "get_coin_transactions_reserve_open_by_coin", 774 &add_coin_reserve_open }, 775 { NULL, NULL, NULL } 776 }; 777 char *table_name; 778 uint64_t serial_id; 779 struct GNUNET_PQ_ResultSpec rs[] = { 780 GNUNET_PQ_result_spec_string ("table_name", 781 &table_name), 782 GNUNET_PQ_result_spec_uint64 ("serial_id", 783 &serial_id), 784 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 785 &chc->chid), 786 GNUNET_PQ_result_spec_end 787 }; 788 struct GNUNET_PQ_QueryParam params[] = { 789 GNUNET_PQ_query_param_auto_from_type (chc->coin_pub), 790 GNUNET_PQ_query_param_uint64 (&serial_id), 791 GNUNET_PQ_query_param_end 792 }; 793 794 for (unsigned int i = 0; i<num_results; i++) 795 { 796 enum GNUNET_DB_QueryStatus qs; 797 bool found = false; 798 799 if (GNUNET_OK != 800 GNUNET_PQ_extract_result (result, 801 rs, 802 i)) 803 { 804 GNUNET_break (0); 805 chc->failed = true; 806 return; 807 } 808 809 for (unsigned int s = 0; 810 NULL != work[s].statement; 811 s++) 812 { 813 if (0 != strcmp (table_name, 814 work[s].table)) 815 continue; 816 found = true; 817 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 818 work[s].statement, 819 params, 820 work[s].cb, 821 chc); 822 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 823 "Coin %s had %d transactions at %llu in table %s\n", 824 TALER_B2S (chc->coin_pub), 825 (int) qs, 826 (unsigned long long) serial_id, 827 table_name); 828 if (0 > qs) 829 chc->failed = true; 830 break; 831 } 832 if (! found) 833 { 834 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 835 "Coin history includes unsupported table `%s`\n", 836 table_name); 837 chc->failed = true; 838 } 839 GNUNET_PQ_cleanup_result (rs); 840 if (chc->failed) 841 break; 842 } 843 } 844 845 846 enum GNUNET_DB_QueryStatus 847 TALER_EXCHANGEDB_get_coin_transactions ( 848 struct TALER_EXCHANGEDB_PostgresContext *pg, 849 bool begin_transaction, 850 const struct TALER_CoinSpendPublicKeyP *coin_pub, 851 uint64_t start_off, 852 uint64_t etag_in, 853 uint64_t *etag_out, 854 struct TALER_Amount *balance, 855 struct TALER_DenominationHashP *h_denom_pub, 856 struct TALER_EXCHANGEDB_TransactionList **tlp) 857 { 858 struct GNUNET_PQ_QueryParam params[] = { 859 GNUNET_PQ_query_param_auto_from_type (coin_pub), 860 GNUNET_PQ_query_param_end 861 }; 862 struct GNUNET_PQ_QueryParam lparams[] = { 863 GNUNET_PQ_query_param_auto_from_type (coin_pub), 864 GNUNET_PQ_query_param_uint64 (&start_off), 865 GNUNET_PQ_query_param_end 866 }; 867 struct CoinHistoryContext chc = { 868 .head = NULL, 869 .coin_pub = coin_pub, 870 .pg = pg 871 }; 872 873 *tlp = NULL; 874 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 875 "Getting transactions for coin %s\n", 876 TALER_B2S (coin_pub)); 877 PREPARE (pg, 878 "get_coin_transactions_history_etag_balance", 879 "SELECT" 880 " ch.coin_history_serial_id" 881 ",kc.remaining" 882 ",denom.denom_pub_hash" 883 " FROM coin_history ch" 884 " JOIN known_coins kc" 885 " USING (coin_pub)" 886 " JOIN denominations denom" 887 " USING (denominations_serial)" 888 " WHERE coin_pub=$1" 889 " ORDER BY coin_history_serial_id DESC" 890 " LIMIT 1;"); 891 PREPARE (pg, 892 "get_coin_transactions_history", 893 "SELECT" 894 " table_name" 895 ",serial_id" 896 ",coin_history_serial_id" 897 " FROM coin_history" 898 " WHERE coin_pub=$1" 899 " AND coin_history_serial_id > $2" 900 " ORDER BY coin_history_serial_id DESC;"); 901 PREPARE (pg, 902 "get_coin_transactions_deposit_with_coin_pub", 903 "SELECT" 904 " cdep.amount_with_fee" 905 ",denoms.fee_deposit" 906 ",denoms.denom_pub_hash" 907 ",kc.age_commitment_hash" 908 ",bdep.wallet_timestamp" 909 ",bdep.refund_deadline" 910 ",bdep.wire_deadline" 911 ",bdep.merchant_pub" 912 ",bdep.h_contract_terms" 913 ",bdep.wallet_data_hash" 914 ",bdep.wire_salt" 915 ",wt.payto_uri" 916 ",cdep.coin_sig" 917 ",cdep.coin_deposit_serial_id" 918 ",bdep.done" 919 " FROM coin_deposits cdep" 920 " JOIN batch_deposits bdep" 921 " USING (batch_deposit_serial_id)" 922 " JOIN wire_targets wt" 923 " USING (wire_target_h_payto)" 924 " JOIN known_coins kc" 925 " ON (kc.coin_pub = cdep.coin_pub)" 926 " JOIN denominations denoms" 927 " USING (denominations_serial)" 928 " WHERE cdep.coin_pub=$1" 929 " AND cdep.coin_deposit_serial_id=$2;"); 930 PREPARE (pg, 931 "get_coin_transactions_refresh_by_coin", 932 "SELECT" 933 " rc" 934 ",refresh_seed" 935 ",blinding_seed" 936 ",old_coin_sig" 937 ",amount_with_fee" 938 ",denoms.denom_pub_hash" 939 ",denoms.fee_refresh" 940 ",kc.age_commitment_hash" 941 ",refresh_id" 942 ",ARRAY(" 943 " SELECT fresh_denoms.denom_pub_hash" 944 " FROM unnest(refresh.denom_serials) WITH ORDINALITY" 945 " AS fresh(denom_serial, coin_index)" 946 " LEFT JOIN denominations fresh_denoms" 947 " ON fresh_denoms.denominations_serial=fresh.denom_serial" 948 " ORDER BY fresh.coin_index" 949 ") AS denom_pub_hashes" 950 " FROM refresh" 951 " JOIN known_coins kc" 952 " ON (refresh.old_coin_pub = kc.coin_pub)" 953 " JOIN denominations denoms" 954 " USING (denominations_serial)" 955 " WHERE old_coin_pub=$1" 956 " AND refresh_id=$2;"); 957 PREPARE (pg, 958 "get_coin_transactions_purse_deposit_by_coin_pub", 959 "SELECT" 960 " partner_base_url" 961 ",pd.amount_with_fee" 962 ",denoms.fee_deposit" 963 ",denoms.denom_pub_hash" 964 ",pd.purse_pub" 965 ",kc.age_commitment_hash" 966 ",pd.coin_sig" 967 ",pd.purse_deposit_serial_id" 968 ",pdes.refunded" 969 " FROM purse_deposits pd" 970 " LEFT JOIN partners" 971 " USING (partner_serial_id)" 972 " JOIN purse_requests pr" 973 " USING (purse_pub)" 974 " LEFT JOIN purse_decision pdes" 975 " USING (purse_pub)" 976 " JOIN known_coins kc" 977 " ON (pd.coin_pub = kc.coin_pub)" 978 " JOIN denominations denoms" 979 " USING (denominations_serial)" 980 " WHERE pd.purse_deposit_serial_id=$2" 981 " AND pd.coin_pub=$1;"); 982 PREPARE (pg, 983 "get_coin_transactions_purse_decision_by_coin_pub", 984 "SELECT" 985 " pdes.purse_pub" 986 ",pd.amount_with_fee" 987 ",denom.fee_refund" 988 ",pdes.purse_decision_serial_id" 989 " FROM purse_decision pdes" 990 " JOIN purse_deposits pd" 991 " USING (purse_pub)" 992 " JOIN known_coins kc" 993 " ON (pd.coin_pub = kc.coin_pub)" 994 " JOIN denominations denom" 995 " USING (denominations_serial)" 996 " WHERE pd.coin_pub=$1" 997 " AND pdes.purse_decision_serial_id=$2" 998 " AND pdes.refunded;"); 999 PREPARE (pg, 1000 "get_coin_transactions_refunds_by_coin", 1001 "SELECT" 1002 " bdep.merchant_pub" 1003 ",ref.merchant_sig" 1004 ",bdep.h_contract_terms" 1005 ",ref.rtransaction_id" 1006 ",ref.amount_with_fee" 1007 ",denom.fee_refund" 1008 ",ref.refund_serial_id" 1009 " FROM refunds ref" 1010 " JOIN coin_deposits cdep" 1011 " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)" 1012 " JOIN batch_deposits bdep" 1013 " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)" 1014 " JOIN known_coins kc" 1015 " ON (ref.coin_pub = kc.coin_pub)" 1016 " JOIN denominations denom" 1017 " USING (denominations_serial)" 1018 " WHERE ref.coin_pub=$1" 1019 " AND ref.refund_serial_id=$2;"); 1020 PREPARE (pg, 1021 "get_coin_transactions_recoup_by_old_coin", 1022 "SELECT" 1023 " coins.coin_pub" 1024 ",rr.coin_sig" 1025 ",rr.coin_blind" 1026 ",rr.amount" 1027 ",rr.recoup_timestamp" 1028 ",denoms.denom_pub_hash" 1029 ",coins.denom_sig" 1030 ",rr.recoup_refresh_uuid" 1031 " FROM recoup_refresh rr" 1032 " JOIN known_coins coins" 1033 " USING (coin_pub)" 1034 " JOIN denominations denoms" 1035 " USING (denominations_serial)" 1036 " WHERE recoup_refresh_uuid=$2" 1037 " AND refresh_id IN" 1038 " (SELECT refresh_id" 1039 " FROM refresh" 1040 " WHERE refresh.old_coin_pub=$1);"); 1041 PREPARE (pg, 1042 "get_coin_transactions_recoup_by_coin", 1043 "SELECT" 1044 " res.reserve_pub" 1045 ",denoms.denom_pub_hash" 1046 ",rcp.coin_sig" 1047 ",rcp.coin_blind" 1048 ",rcp.amount" 1049 ",rcp.recoup_timestamp" 1050 ",rcp.recoup_uuid" 1051 " FROM recoup rcp" 1052 " JOIN withdraw ro" 1053 " USING (withdraw_id)" 1054 " JOIN reserves res" 1055 " USING (reserve_pub)" 1056 " JOIN known_coins coins" 1057 " USING (coin_pub)" 1058 " JOIN denominations denoms" 1059 " ON (denoms.denominations_serial = coins.denominations_serial)" 1060 " WHERE rcp.recoup_uuid=$2" 1061 " AND coins.coin_pub=$1;"); 1062 /* Used to obtain recoup transactions 1063 for a refreshed coin */ 1064 PREPARE (pg, 1065 "get_coin_transactions_recoup_by_refreshed_coin", 1066 "SELECT" 1067 " old_coins.coin_pub AS old_coin_pub" 1068 ",rr.coin_sig" 1069 ",rr.coin_blind" 1070 ",rr.amount" 1071 ",rr.recoup_timestamp" 1072 ",denoms.denom_pub_hash" 1073 ",coins.denom_sig" 1074 ",recoup_refresh_uuid" 1075 " FROM recoup_refresh rr" 1076 " JOIN refresh rfc" 1077 " ON (rr.refresh_id = rfc.refresh_id)" 1078 " JOIN known_coins old_coins" 1079 " ON (rfc.old_coin_pub = old_coins.coin_pub)" 1080 " JOIN known_coins coins" 1081 " ON (rr.coin_pub = coins.coin_pub)" 1082 " JOIN denominations denoms" 1083 " ON (denoms.denominations_serial = coins.denominations_serial)" 1084 " WHERE rr.recoup_refresh_uuid=$2" 1085 " AND coins.coin_pub=$1;"); 1086 PREPARE (pg, 1087 "get_coin_transactions_reserve_open_by_coin", 1088 "SELECT" 1089 " rod.reserve_open_deposit_uuid" 1090 ",rod.coin_sig" 1091 ",rod.reserve_sig" 1092 ",rod.contribution" 1093 ",kc.age_commitment_hash" 1094 " FROM reserves_open_deposits rod" 1095 " JOIN known_coins kc" 1096 " ON (rod.coin_pub = kc.coin_pub)" 1097 " WHERE rod.coin_pub=$1" 1098 " AND rod.reserve_open_deposit_uuid=$2;"); 1099 for (unsigned int i = 0; i<RETRIES; i++) 1100 { 1101 enum GNUNET_DB_QueryStatus qs; 1102 uint64_t end; 1103 struct GNUNET_PQ_ResultSpec rs[] = { 1104 GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id", 1105 &end), 1106 GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", 1107 h_denom_pub), 1108 TALER_PQ_RESULT_SPEC_AMOUNT ("remaining", 1109 balance), 1110 GNUNET_PQ_result_spec_end 1111 }; 1112 1113 if (begin_transaction) 1114 { 1115 if (GNUNET_OK != 1116 TALER_EXCHANGEDB_start_read_committed (pg, 1117 "get-coin-transactions")) 1118 { 1119 GNUNET_break (0); 1120 return GNUNET_DB_STATUS_HARD_ERROR; 1121 } 1122 } 1123 /* First only check the last item, to see if 1124 we even need to iterate */ 1125 qs = GNUNET_PQ_eval_prepared_singleton_select ( 1126 pg->conn, 1127 "get_coin_transactions_history_etag_balance", 1128 params, 1129 rs); 1130 switch (qs) 1131 { 1132 case GNUNET_DB_STATUS_HARD_ERROR: 1133 if (begin_transaction) 1134 TALER_EXCHANGEDB_rollback (pg); 1135 return qs; 1136 case GNUNET_DB_STATUS_SOFT_ERROR: 1137 if (begin_transaction) 1138 TALER_EXCHANGEDB_rollback (pg); 1139 continue; 1140 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1141 if (begin_transaction) 1142 TALER_EXCHANGEDB_rollback (pg); 1143 return qs; 1144 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1145 *etag_out = end; 1146 if (end == etag_in) 1147 { 1148 /* client is up-to-date, nothing more to do */ 1149 if (begin_transaction) 1150 TALER_EXCHANGEDB_rollback (pg); 1151 return qs; 1152 } 1153 } 1154 /* We indeed need to iterate over the history */ 1155 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1156 "Current ETag for coin %s is %llu\n", 1157 TALER_B2S (coin_pub), 1158 (unsigned long long) end); 1159 1160 qs = GNUNET_PQ_eval_prepared_multi_select ( 1161 pg->conn, 1162 "get_coin_transactions_history", 1163 lparams, 1164 &handle_history_entry, 1165 &chc); 1166 switch (qs) 1167 { 1168 case GNUNET_DB_STATUS_HARD_ERROR: 1169 if (begin_transaction) 1170 TALER_EXCHANGEDB_rollback (pg); 1171 return qs; 1172 case GNUNET_DB_STATUS_SOFT_ERROR: 1173 if (begin_transaction) 1174 TALER_EXCHANGEDB_rollback (pg); 1175 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1176 chc.head = NULL; 1177 continue; 1178 default: 1179 break; 1180 } 1181 if (chc.failed) 1182 { 1183 if (begin_transaction) 1184 TALER_EXCHANGEDB_rollback (pg); 1185 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1186 return GNUNET_DB_STATUS_SOFT_ERROR; 1187 } 1188 if (! begin_transaction) 1189 { 1190 *tlp = chc.head; 1191 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1192 } 1193 qs = TALER_EXCHANGEDB_commit (pg); 1194 switch (qs) 1195 { 1196 case GNUNET_DB_STATUS_HARD_ERROR: 1197 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1198 chc.head = NULL; 1199 return qs; 1200 case GNUNET_DB_STATUS_SOFT_ERROR: 1201 TALER_EXCHANGEDB_free_coin_transaction_list (chc.head); 1202 chc.head = NULL; 1203 continue; 1204 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1205 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1206 *tlp = chc.head; 1207 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1208 } 1209 } 1210 return GNUNET_DB_STATUS_SOFT_ERROR; 1211 }