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