test_coin_history.c (34838B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 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 exchangedb/test_coin_history.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `coin_history` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_get_coin_transactions(). 23 * 24 * Nothing writes to `coin_history` directly: every table that can spend or 25 * credit a coin has an INSERT trigger that appends a row naming itself and 26 * its own serial. get_coin_transactions() walks those rows and looks each 27 * one up in the table it names. So the interesting part of this test is 28 * spending one coin in as many different ways as possible and then checking 29 * that every one of them comes back with the right type and amount. 30 */ 31 #include "test_common.h" 32 #include "exchange-database/do_purse_deposit.h" 33 #include "exchange-database/rollback.h" 34 #include "exchange-database/start.h" 35 #include "exchange-database/do_recoup.h" 36 #include "exchange-database/do_recoup_refresh.h" 37 #include "exchange-database/do_refresh.h" 38 #include "exchange-database/do_refund.h" 39 #include "exchange-database/get_coin_transactions.h" 40 #include "exchange-database/insert_reserve_open_deposit.h" 41 #include "exchange-database/update_to_refresh_revealed.h" 42 43 44 /** 45 * Account the checks fund their reserves from. 46 */ 47 static struct TDB_Account account; 48 49 50 /** 51 * Denomination the checks use. 52 */ 53 static struct TDB_Denom denom; 54 55 56 /** 57 * A different denomination used among the refresh outputs. 58 */ 59 static struct TDB_Denom fresh_denom; 60 61 62 /** 63 * The coin whose history we build up. 64 */ 65 static struct TALER_CoinPublicInfo coin; 66 67 68 /** 69 * Row of @e coin in `known_coins`. 70 */ 71 static uint64_t known_coin_id; 72 73 74 /** 75 * Reserve @e coin was withdrawn from. 76 */ 77 static struct TALER_ReservePublicKeyP reserve_pub; 78 79 80 /** 81 * Row of the withdraw that created @e coin. 82 */ 83 static uint64_t withdraw_id; 84 85 86 /** 87 * The deposit we later refund. 88 */ 89 static struct TDB_Deposit deposit; 90 91 92 /** 93 * ETag of the history right after the deposit was refunded. 94 */ 95 static uint64_t etag_after_refund; 96 97 98 /** 99 * Count the entries of @a tl and remember which types occurred. 100 * 101 * @param tl transaction list to walk 102 * @param[out] types set to the bitmask of the types seen 103 * @return number of entries in @a tl 104 */ 105 static unsigned int 106 summarize (const struct TALER_EXCHANGEDB_TransactionList *tl, 107 unsigned int *types) 108 { 109 unsigned int cnt = 0; 110 111 *types = 0; 112 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 113 NULL != pos; 114 pos = pos->next) 115 { 116 cnt++; 117 *types |= 1U << pos->type; 118 } 119 return cnt; 120 } 121 122 123 /** 124 * Ask for the full history of @e coin. 125 * 126 * @param pg the database context 127 * @param start_off offset to start from 128 * @param etag_in ETag the caller already has 129 * @param[out] etag_out set to the current ETag 130 * @param[out] tlp set to the history 131 * @return transaction status 132 */ 133 static enum GNUNET_DB_QueryStatus 134 history (struct TALER_EXCHANGEDB_PostgresContext *pg, 135 uint64_t start_off, 136 uint64_t etag_in, 137 uint64_t *etag_out, 138 struct TALER_EXCHANGEDB_TransactionList **tlp) 139 { 140 struct TALER_Amount balance; 141 struct TALER_DenominationHashP h_denom_pub; 142 143 return TALER_EXCHANGEDB_get_coin_transactions (pg, 144 true, 145 &coin.coin_pub, 146 start_off, 147 etag_in, 148 etag_out, 149 &balance, 150 &h_denom_pub, 151 tlp); 152 } 153 154 155 /** 156 * A coin the exchange never saw has no history. 157 * 158 * @param pg the database context 159 * @return 0 on success 160 */ 161 static int 162 check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 163 { 164 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 165 struct TALER_CoinSpendPublicKeyP unknown; 166 struct TALER_Amount balance; 167 struct TALER_DenominationHashP h_denom_pub; 168 uint64_t etag = 42; 169 170 TDB_account (pg, 171 10, 172 &account); 173 TDB_denom (pg, 174 10, 175 "5", 176 "0.1", 177 &denom); 178 TDB_FILL (unknown, 179 99); 180 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 181 TALER_EXCHANGEDB_get_coin_transactions (pg, 182 true, 183 &unknown, 184 0, 185 0, 186 &etag, 187 &balance, 188 &h_denom_pub, 189 &tl)); 190 FAILIF (NULL != tl); 191 return 0; 192 } 193 194 195 /** 196 * A coin that was withdrawn but never spent has no history either: the 197 * `withdraw` table has no coin history trigger. 198 * 199 * @param pg the database context 200 * @return 0 on success 201 */ 202 static int 203 check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 204 { 205 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 206 uint64_t etag = 42; 207 208 TDB_reserve_in (pg, 209 &account, 210 11, 211 "10", 212 &reserve_pub); 213 withdraw_id = TDB_withdraw (pg, 214 &denom, 215 &reserve_pub, 216 11, 217 "5"); 218 TDB_coin (pg, 219 &denom, 220 20, 221 &coin, 222 &known_coin_id); 223 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 224 history (pg, 225 0, 226 0, 227 &etag, 228 &tl)); 229 FAILIF (NULL != tl); 230 FAILIF (0 != TDB_count (pg, 231 "FROM coin_history")); 232 return 0; 233 } 234 235 236 /** 237 * A deposit and its refund show up as two entries. 238 * 239 * @param pg the database context 240 * @return 0 on success 241 */ 242 static int 243 check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg) 244 { 245 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 246 struct TALER_EXCHANGEDB_Refund refund; 247 struct TALER_Amount deposit_fee = TDB_amount ("0.1"); 248 struct TALER_Amount balance; 249 struct TALER_Amount expect = TDB_amount ("4.5"); 250 struct TALER_DenominationHashP h_denom_pub; 251 unsigned int types; 252 uint64_t etag = 0; 253 bool not_found; 254 bool refund_ok; 255 bool gone; 256 bool conflict; 257 258 TDB_deposit (pg, 259 &account, 260 &coin, 261 30, 262 "1", 263 "0.1", 264 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 265 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 266 &deposit); 267 FAILIF (1 != TDB_count (pg, 268 "FROM coin_history")); 269 270 memset (&refund, 271 0, 272 sizeof (refund)); 273 refund.coin = coin; 274 refund.details.merchant_pub = deposit.merchant_pub; 275 TDB_FILL (refund.details.merchant_sig, 276 31); 277 refund.details.h_contract_terms = deposit.h_contract_terms; 278 refund.details.rtransaction_id = 1; 279 refund.details.refund_amount = TDB_amount ("0.5"); 280 refund.details.refund_fee = TDB_amount ("0"); 281 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 282 TALER_EXCHANGEDB_do_refund (pg, 283 &refund, 284 &deposit_fee, 285 0, 286 ¬_found, 287 &refund_ok, 288 &gone, 289 &conflict)); 290 FAILIF (not_found); 291 FAILIF (! refund_ok); 292 FAILIF (2 != TDB_count (pg, 293 "FROM coin_history")); 294 295 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 296 TALER_EXCHANGEDB_get_coin_transactions (pg, 297 true, 298 &coin.coin_pub, 299 0, 300 0, 301 &etag, 302 &balance, 303 &h_denom_pub, 304 &tl)); 305 FAILIF_C (2 != summarize (tl, 306 &types), 307 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 308 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)), 309 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 310 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)), 311 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 312 /* 5 - 1 + 0.5 */ 313 FAILIF_C (0 != TALER_amount_cmp (&balance, 314 &expect), 315 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 316 FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub, 317 &denom.h_denom_pub), 318 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 319 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 320 FAILIF (0 == etag); 321 etag_after_refund = etag; 322 return 0; 323 } 324 325 326 /** 327 * A caller that is already up to date gets no list back, and the 328 * transaction the lookup opened is not left dangling. 329 * 330 * @param pg the database context 331 * @return 0 on success 332 */ 333 static int 334 check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg) 335 { 336 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 337 uint64_t etag = 0; 338 339 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 340 history (pg, 341 0, 342 etag_after_refund, 343 &etag, 344 &tl)); 345 FAILIF (NULL != tl); 346 FAILIF (etag != etag_after_refund); 347 /* the lookup must not leave a transaction open (it starts one of its 348 own when begin_transaction is true) */ 349 FAILIF (NULL != pg->transaction_name); 350 351 /* an ETag that is not the current one still returns the history */ 352 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 353 history (pg, 354 0, 355 etag_after_refund - 1, 356 &etag, 357 &tl)); 358 FAILIF (NULL == tl); 359 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 360 FAILIF (NULL != pg->transaction_name); 361 return 0; 362 } 363 364 365 /** 366 * The offset skips everything up to and including it. 367 * 368 * @param pg the database context 369 * @return 0 on success 370 */ 371 static int 372 check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg) 373 { 374 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 375 unsigned int types; 376 uint64_t etag = 0; 377 378 /* everything after the deposit: only the refund is left */ 379 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 380 history (pg, 381 etag_after_refund - 1, 382 0, 383 &etag, 384 &tl)); 385 FAILIF_C (1 != summarize (tl, 386 &types), 387 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 388 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)), 389 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 390 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 391 392 /* everything after the last entry: nothing, but the ETag is still 393 reported */ 394 tl = (void *) 1; 395 etag = 0; 396 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 397 history (pg, 398 etag_after_refund, 399 0, 400 &etag, 401 &tl)); 402 FAILIF (NULL != tl); 403 FAILIF (etag != etag_after_refund); 404 return 0; 405 } 406 407 408 /** 409 * Spending the coin into a purse, on a reserve and in a melt adds one 410 * entry each. 411 * 412 * @param pg the database context 413 * @return 0 on success 414 */ 415 static int 416 check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg) 417 { 418 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 419 struct TDB_Purse purse; 420 struct TALER_CoinSpendSignatureP coin_sig; 421 struct TALER_ReserveSignatureP reserve_sig; 422 struct TALER_Amount one = TDB_amount ("1"); 423 struct TALER_Amount zero = TDB_amount ("0"); 424 unsigned int types; 425 uint64_t etag = 0; 426 bool balance_ok; 427 bool too_late; 428 bool conflict; 429 bool insufficient_funds; 430 431 /* into a purse */ 432 TDB_purse (pg, 433 40, 434 "1", 435 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 436 &purse); 437 TDB_FILL (coin_sig, 438 41); 439 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 440 TALER_EXCHANGEDB_do_purse_deposit (pg, 441 &purse.purse_pub, 442 &coin.coin_pub, 443 &one, 444 &coin_sig, 445 &one, 446 &balance_ok, 447 &too_late, 448 &conflict)); 449 FAILIF (! balance_ok); 450 FAILIF (conflict); 451 452 /* to keep a reserve open */ 453 TDB_FILL (coin_sig, 454 42); 455 TDB_FILL (reserve_sig, 456 42); 457 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 458 TALER_EXCHANGEDB_insert_reserve_open_deposit (pg, 459 &coin, 460 &coin_sig, 461 known_coin_id, 462 &one, 463 &reserve_sig, 464 &reserve_pub, 465 &insufficient_funds)); 466 FAILIF (insufficient_funds); 467 468 /* and into a melt */ 469 { 470 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 471 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 472 struct TALER_Amount coin_balance; 473 bool found; 474 bool zombie_required = false; 475 bool nonce_reuse; 476 bool melt_balance_ok; 477 uint32_t noreveal_index; 478 enum GNUNET_DB_QueryStatus qs; 479 480 memset (&rf, 481 0, 482 sizeof (rf)); 483 rf.coin.coin_pub = coin.coin_pub; 484 rf.coin.denom_pub_hash = coin.denom_pub_hash; 485 rf.coin.no_age_commitment = coin.no_age_commitment; 486 TDB_FILL (rf.coin_sig, 487 43); 488 TDB_FILL (rf.rc, 489 43); 490 TDB_FILL (rf.refresh_seed, 491 43); 492 TDB_FILL (rf.planchets_h, 493 43); 494 TDB_FILL (rf.selected_h, 495 44); 496 rf.amount_with_fee = one; 497 TDB_denom (pg, 498 11, 499 "0.1", 500 "0.01", 501 &fresh_denom); 502 rf.num_coins = 3; 503 rf.denom_serials = GNUNET_new_array (rf.num_coins, 504 uint64_t); 505 /* Deliberately neither sorted nor distinct. */ 506 rf.denom_serials[0] = fresh_denom.serial; 507 rf.denom_serials[1] = denom.serial; 508 rf.denom_serials[2] = fresh_denom.serial; 509 rf.denom_sigs = GNUNET_new_array ( 510 rf.num_coins, 511 struct TALER_BlindedDenominationSignature); 512 for (unsigned int i = 0; i < rf.num_coins; i++) 513 TDB_blinded_denom_sig (43 + i, 514 &rf.denom_sigs[i]); 515 rf.noreveal_index = 1; 516 rf.is_v27_refresh = true; 517 rf.no_blinding_seed = true; 518 qs = TALER_EXCHANGEDB_do_refresh (pg, 519 &rf, 520 &now, 521 &found, 522 &noreveal_index, 523 &zombie_required, 524 &nonce_reuse, 525 &melt_balance_ok, 526 &coin_balance); 527 for (unsigned int i = 0; i < rf.num_coins; i++) 528 TALER_blinded_denom_sig_free (&rf.denom_sigs[i]); 529 GNUNET_free (rf.denom_sigs); 530 GNUNET_free (rf.denom_serials); 531 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs); 532 FAILIF (! melt_balance_ok); 533 534 /* The original output list is available before and after reveal. */ 535 for (unsigned int revealed = 0; revealed < 2; revealed++) 536 { 537 bool found_melt = false; 538 539 if (revealed) 540 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 541 TALER_EXCHANGEDB_update_to_refresh_revealed (pg, 542 &rf.rc)); 543 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 544 history (pg, 545 0, 546 0, 547 &etag, 548 &tl)); 549 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 550 NULL != pos; 551 pos = pos->next) 552 { 553 const struct TALER_EXCHANGEDB_MeltListEntry *melt; 554 555 if (TALER_EXCHANGEDB_TT_MELT != pos->type) 556 continue; 557 found_melt = true; 558 melt = pos->details.melt; 559 FAILIF_C (3 != melt->num_coins, 560 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 561 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0], 562 &fresh_denom.h_denom_pub), 563 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 564 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1], 565 &denom.h_denom_pub), 566 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 567 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2], 568 &fresh_denom.h_denom_pub), 569 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 570 } 571 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 572 tl = NULL; 573 FAILIF (! found_melt); 574 } 575 } 576 577 FAILIF (5 != TDB_count (pg, 578 "FROM coin_history")); 579 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 580 history (pg, 581 0, 582 0, 583 &etag, 584 &tl)); 585 FAILIF_C (5 != summarize (tl, 586 &types), 587 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 588 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)), 589 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 590 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)), 591 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 592 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)), 593 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 594 595 /* the ETag is the highest coin history row of the list */ 596 { 597 uint64_t max = 0; 598 599 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 600 NULL != pos; 601 pos = pos->next) 602 max = GNUNET_MAX (max, 603 pos->coin_history_id); 604 FAILIF_C (etag != max, 605 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 606 } 607 608 /* the spends add up to what the coin no longer has */ 609 { 610 struct TALER_Amount total; 611 struct TALER_Amount expect = TDB_amount ("3.5"); 612 613 FAILIF_C (GNUNET_OK != 614 TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, 615 &zero, 616 &total), 617 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 618 FAILIF_C (0 != TALER_amount_cmp (&total, 619 &expect), 620 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 621 } 622 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 623 return 0; 624 } 625 626 627 /** 628 * Recouping the coin adds the last entry and empties it. 629 * 630 * @param pg the database context 631 * @return 0 on success 632 */ 633 static int 634 check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) 635 { 636 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 637 struct TALER_CoinSpendSignatureP coin_sig; 638 union GNUNET_CRYPTO_BlindingSecretP coin_bks; 639 struct GNUNET_TIME_Timestamp recoup_timestamp; 640 struct TALER_Amount recoup_amount; 641 struct TALER_Amount balance; 642 struct TALER_Amount zero = TDB_amount ("0"); 643 struct TALER_DenominationHashP h_denom_pub; 644 unsigned int types; 645 uint64_t etag = 0; 646 bool recoup_ok; 647 bool internal_failure; 648 649 TDB_FILL (coin_sig, 650 50); 651 TDB_FILL (coin_bks, 652 50); 653 /* in/out: the caller picks the time, the callee only overwrites it if 654 the coin was recouped before */ 655 recoup_timestamp = GNUNET_TIME_timestamp_get (); 656 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 657 TALER_EXCHANGEDB_do_recoup (pg, 658 &reserve_pub, 659 withdraw_id, 660 3, 661 &coin_bks, 662 &coin.coin_pub, 663 known_coin_id, 664 &coin_sig, 665 &recoup_timestamp, 666 &recoup_amount, 667 &recoup_ok, 668 &internal_failure)); 669 FAILIF (internal_failure); 670 FAILIF (! recoup_ok); 671 FAILIF (6 != TDB_count (pg, 672 "FROM coin_history")); 673 674 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 675 TALER_EXCHANGEDB_get_coin_transactions (pg, 676 true, 677 &coin.coin_pub, 678 0, 679 0, 680 &etag, 681 &balance, 682 &h_denom_pub, 683 &tl)); 684 FAILIF_C (6 != summarize (tl, 685 &types), 686 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 687 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)), 688 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 689 /* the entry names the withdraw operation (by its commitment, seeded 690 like the withdraw in check_fresh_coin()) and the coin's position */ 691 { 692 const struct TALER_EXCHANGEDB_RecoupListEntry *recoup = NULL; 693 struct TALER_HashBlindedPlanchetsP planchets_h; 694 695 TDB_FILL (planchets_h, 696 11); 697 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 698 NULL != pos; 699 pos = pos->next) 700 if (TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW == pos->type) 701 recoup = pos->details.recoup; 702 FAILIF_C (NULL == recoup, 703 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 704 FAILIF_C (3 != recoup->coin_index, 705 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 706 FAILIF_C (0 != GNUNET_memcmp (&recoup->planchets_h, 707 &planchets_h), 708 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 709 FAILIF_C (0 != GNUNET_memcmp (&recoup->reserve_pub, 710 &reserve_pub), 711 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 712 FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_sig, 713 &coin_sig), 714 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 715 FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_blind, 716 &coin_bks), 717 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 718 } 719 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 720 FAILIF (0 != TALER_amount_cmp (&balance, 721 &zero)); 722 return 0; 723 } 724 725 726 /** 727 * With begin_transaction false the caller's transaction is used and left 728 * open. 729 * 730 * @param pg the database context 731 * @return 0 on success 732 */ 733 static int 734 check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg) 735 { 736 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 737 struct TALER_Amount balance; 738 struct TALER_DenominationHashP h_denom_pub; 739 unsigned int types; 740 uint64_t etag = 0; 741 742 FAILIF (GNUNET_OK != 743 TALER_EXCHANGEDB_start (pg, 744 "coin-history")); 745 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 746 TALER_EXCHANGEDB_get_coin_transactions (pg, 747 false, 748 &coin.coin_pub, 749 0, 750 0, 751 &etag, 752 &balance, 753 &h_denom_pub, 754 &tl), 755 TALER_EXCHANGEDB_rollback (pg)); 756 FAILIF_C (6 != summarize (tl, 757 &types), 758 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 759 TALER_EXCHANGEDB_rollback (pg)); 760 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 761 FAILIF_C (NULL == pg->transaction_name, 762 TALER_EXCHANGEDB_rollback (pg)); 763 TALER_EXCHANGEDB_rollback (pg); 764 return 0; 765 } 766 767 768 /** 769 * Recouping a coin that was refreshed from @e coin credits @e coin and adds 770 * a RECOUP-REFRESH-RECEIVER entry to its history that names the refresh 771 * operation (by its commitment, seeded like the melt in 772 * check_other_spends()), the recouped coin and its position in the batch. 773 * The recouped coin's own history gets the matching RECOUP-REFRESH entry. 774 * 775 * @param pg the database context 776 * @return 0 on success 777 */ 778 static int 779 check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg) 780 { 781 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 782 const struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr = NULL; 783 struct TALER_CoinPublicInfo fresh_coin; 784 struct TALER_RefreshCommitmentP rc; 785 struct TALER_CoinSpendSignatureP coin_sig; 786 union GNUNET_CRYPTO_BlindingSecretP coin_bks; 787 struct GNUNET_TIME_Timestamp recoup_timestamp; 788 struct TALER_Amount recoup_amount; 789 struct TALER_Amount balance; 790 struct TALER_Amount zero = TDB_amount ("0"); 791 struct TALER_Amount credited = TDB_amount ("0.1"); 792 struct TALER_DenominationHashP h_denom_pub; 793 uint64_t fresh_known_coin_id; 794 uint64_t refresh_id; 795 uint64_t etag = 0; 796 unsigned int types; 797 bool recoup_ok; 798 bool internal_failure; 799 800 TDB_FILL (rc, 801 43); 802 /* row of the melt made in check_other_spends() */ 803 { 804 struct GNUNET_PQ_QueryParam params[] = { 805 GNUNET_PQ_query_param_auto_from_type (&rc), 806 GNUNET_PQ_query_param_end 807 }; 808 struct GNUNET_PQ_ResultSpec rs[] = { 809 GNUNET_PQ_result_spec_uint64 ("refresh_id", 810 &refresh_id), 811 GNUNET_PQ_result_spec_end 812 }; 813 814 FAILIF (GNUNET_OK != 815 GNUNET_PQ_prepare_anon (pg->conn, 816 "SELECT refresh_id" 817 " FROM refresh" 818 " WHERE rc=$1;")); 819 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 820 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 821 "", 822 params, 823 rs)); 824 } 825 TDB_coin (pg, 826 &fresh_denom, 827 60, 828 &fresh_coin, 829 &fresh_known_coin_id); 830 TDB_FILL (coin_sig, 831 60); 832 TDB_FILL (coin_bks, 833 60); 834 recoup_timestamp = GNUNET_TIME_timestamp_get (); 835 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 836 TALER_EXCHANGEDB_do_recoup_refresh (pg, 837 &coin.coin_pub, 838 refresh_id, 839 2, 840 &coin_bks, 841 &fresh_coin.coin_pub, 842 fresh_known_coin_id, 843 &coin_sig, 844 &recoup_timestamp, 845 &recoup_amount, 846 &recoup_ok, 847 &internal_failure), 848 TDB_coin_free (&fresh_coin)); 849 FAILIF_C (internal_failure || ! recoup_ok, 850 TDB_coin_free (&fresh_coin)); 851 FAILIF_C (0 != TALER_amount_cmp (&recoup_amount, 852 &credited), 853 TDB_coin_free (&fresh_coin)); 854 855 /* the old coin is credited and sees the receiver entry */ 856 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 857 TALER_EXCHANGEDB_get_coin_transactions (pg, 858 true, 859 &coin.coin_pub, 860 0, 861 0, 862 &etag, 863 &balance, 864 &h_denom_pub, 865 &tl), 866 TDB_coin_free (&fresh_coin)); 867 FAILIF_C (7 != summarize (tl, 868 &types), 869 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 870 TDB_coin_free (&fresh_coin)); 871 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 872 NULL != pos; 873 pos = pos->next) 874 if (TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER == pos->type) 875 rr = pos->details.old_coin_recoup; 876 FAILIF_C (NULL == rr, 877 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 878 TDB_coin_free (&fresh_coin)); 879 FAILIF_C (2 != rr->coin_index, 880 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 881 TDB_coin_free (&fresh_coin)); 882 FAILIF_C (0 != GNUNET_memcmp (&rr->rc, 883 &rc), 884 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 885 TDB_coin_free (&fresh_coin)); 886 FAILIF_C (0 != GNUNET_memcmp (&rr->coin.coin_pub, 887 &fresh_coin.coin_pub), 888 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 889 TDB_coin_free (&fresh_coin)); 890 FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub, 891 &coin.coin_pub), 892 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 893 TDB_coin_free (&fresh_coin)); 894 FAILIF_C (0 != TALER_amount_cmp (&rr->value, 895 &credited), 896 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 897 TDB_coin_free (&fresh_coin)); 898 FAILIF_C (0 != TALER_amount_cmp (&balance, 899 &credited), 900 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 901 TDB_coin_free (&fresh_coin)); 902 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 903 tl = NULL; 904 905 /* the recouped coin is emptied and sees the debit entry */ 906 etag = 0; 907 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 908 TALER_EXCHANGEDB_get_coin_transactions (pg, 909 true, 910 &fresh_coin.coin_pub, 911 0, 912 0, 913 &etag, 914 &balance, 915 &h_denom_pub, 916 &tl), 917 TDB_coin_free (&fresh_coin)); 918 FAILIF_C (1 != summarize (tl, 919 &types), 920 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 921 TDB_coin_free (&fresh_coin)); 922 FAILIF_C (TALER_EXCHANGEDB_TT_RECOUP_REFRESH != tl->type, 923 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 924 TDB_coin_free (&fresh_coin)); 925 rr = tl->details.recoup_refresh; 926 FAILIF_C (2 != rr->coin_index, 927 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 928 TDB_coin_free (&fresh_coin)); 929 FAILIF_C (0 != GNUNET_memcmp (&rr->rc, 930 &rc), 931 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 932 TDB_coin_free (&fresh_coin)); 933 FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub, 934 &coin.coin_pub), 935 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 936 TDB_coin_free (&fresh_coin)); 937 FAILIF_C (0 != GNUNET_memcmp (&rr->coin_sig, 938 &coin_sig), 939 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 940 TDB_coin_free (&fresh_coin)); 941 FAILIF_C (0 != TALER_amount_cmp (&balance, 942 &zero), 943 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 944 TDB_coin_free (&fresh_coin)); 945 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 946 TDB_coin_free (&fresh_coin); 947 return 0; 948 } 949 950 951 /** 952 * The checks to run, in order. 953 */ 954 static const struct TDB_Test tests[] = { 955 { "coin-history-unknown-coin", 956 &check_unknown_coin }, 957 { "coin-history-fresh-coin", 958 &check_fresh_coin }, 959 { "coin-history-deposit-and-refund", 960 &check_deposit_and_refund }, 961 { "coin-history-etag", 962 &check_etag }, 963 { "coin-history-offset", 964 &check_offset }, 965 { "coin-history-other-spends", 966 &check_other_spends }, 967 { "coin-history-recoup", 968 &check_recoup }, 969 { "coin-history-in-transaction", 970 &check_in_transaction }, 971 { "coin-history-recoup-refresh", 972 &check_recoup_refresh }, 973 { NULL, NULL } 974 }; 975 976 977 int 978 main (int argc, 979 char *const *argv) 980 { 981 int ret; 982 983 ret = TDB_main (argc, 984 argv, 985 "test-coin-history", 986 "Tests for the exchangedb `coin_history' table", 987 tests); 988 TDB_coin_free (&coin); 989 TDB_denom_free (&denom); 990 TDB_denom_free (&fresh_denom); 991 TDB_account_free (&account); 992 return ret; 993 } 994 995 996 /* end of test_coin_history.c */