test_refresh.c (23027B)
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_refresh.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `refresh` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_refresh(), #TALER_EXCHANGEDB_get_refresh(), 23 * #TALER_EXCHANGEDB_update_to_refresh_revealed(), 24 * #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id() and, as far as it 25 * can be, #TALER_EXCHANGEDB_get_old_coin_by_h_blind(). 26 * 27 * `refresh` references `known_coins`, which TDB_coin() creates. The 28 * do_refresh() answers checked here are: unknown coin, insufficient coin 29 * balance, the zombie requirement, blinding-seed reuse and an idempotent 30 * replay. 31 * 32 * get_old_coin_by_h_blind() cannot be checked beyond "does not invent an 33 * answer": its statement selects on a `h_blind_evs` column that the current 34 * `refresh` table does not have, so it cannot succeed at all -- see EDBT-8 35 * in bugs.txt. 36 */ 37 #include "test_common.h" 38 #include "exchange-database/do_refresh.h" 39 #include "exchange-database/get_known_coin.h" 40 #include "exchange-database/get_old_coin_by_h_blind.h" 41 #include "exchange-database/get_refresh.h" 42 #include "exchange-database/iterate_refreshes_above_serial_id.h" 43 #include "exchange-database/update_to_refresh_revealed.h" 44 45 46 /** 47 * Denomination the checks melt. 48 */ 49 static struct TDB_Denom denom; 50 51 52 /** 53 * Fill in a refresh (melt) request for one fresh coin. 54 * 55 * @param seed seed for the commitment, signatures and seeds 56 * @param amount amount to melt, e.g. "1" 57 * @param coin coin being melted 58 * @param with_seed true to pass a blinding seed and a CS R value 59 * @param[out] rf set to the request; release with free_refresh() 60 */ 61 static void 62 make_refresh (uint32_t seed, 63 const char *amount, 64 const struct TALER_CoinPublicInfo *coin, 65 bool with_seed, 66 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf) 67 { 68 memset (rf, 69 0, 70 sizeof (*rf)); 71 rf->coin.coin_pub = coin->coin_pub; 72 rf->coin.denom_pub_hash = coin->denom_pub_hash; 73 rf->coin.no_age_commitment = coin->no_age_commitment; 74 TDB_fill (&rf->coin_sig, 75 sizeof (rf->coin_sig), 76 seed); 77 TDB_fill (&rf->rc, 78 sizeof (rf->rc), 79 seed); 80 TDB_fill (&rf->refresh_seed, 81 sizeof (rf->refresh_seed), 82 seed); 83 TDB_fill (&rf->planchets_h, 84 sizeof (rf->planchets_h), 85 seed); 86 TDB_fill (&rf->selected_h, 87 sizeof (rf->selected_h), 88 seed + 1); 89 rf->amount_with_fee = TDB_amount (amount); 90 rf->num_coins = 1; 91 rf->denom_serials = GNUNET_new (uint64_t); 92 rf->denom_serials[0] = denom.serial; 93 rf->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature); 94 TDB_blinded_denom_sig (seed, 95 &rf->denom_sigs[0]); 96 rf->noreveal_index = 1; 97 /* v27 refresh: the client provides no transfer public keys */ 98 rf->is_v27_refresh = true; 99 rf->no_blinding_seed = ! with_seed; 100 if (with_seed) 101 { 102 TDB_fill (&rf->blinding_seed, 103 sizeof (rf->blinding_seed), 104 seed); 105 rf->num_cs_r_values = 1; 106 rf->cs_r_values = GNUNET_new (struct GNUNET_CRYPTO_CSPublicRPairP); 107 TDB_fill (rf->cs_r_values, 108 sizeof (*rf->cs_r_values), 109 seed); 110 rf->cs_r_choices = 0; 111 } 112 } 113 114 115 /** 116 * Release what make_refresh() allocated. 117 * 118 * @param[in,out] rf request to clean up 119 */ 120 static void 121 free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf) 122 { 123 for (size_t i = 0; i<rf->num_coins; i++) 124 TALER_blinded_denom_sig_free (&rf->denom_sigs[i]); 125 GNUNET_free (rf->denom_sigs); 126 GNUNET_free (rf->denom_serials); 127 GNUNET_free (rf->cs_r_values); 128 GNUNET_free (rf->transfer_pubs); 129 GNUNET_free (rf->denom_pub_hashes); 130 } 131 132 133 /** 134 * Outcome flags of a refresh request. 135 */ 136 struct RefreshStatus 137 { 138 /** 139 * Was there already a row for this commitment? 140 */ 141 bool found; 142 143 /** 144 * Did the melt have to be of a zombie coin? 145 */ 146 bool zombie_required; 147 148 /** 149 * Was the blinding seed used before? 150 */ 151 bool nonce_reuse; 152 153 /** 154 * Was the coin balance sufficient? 155 */ 156 bool balance_ok; 157 158 /** 159 * Balance the coin had. 160 */ 161 struct TALER_Amount coin_balance; 162 163 /** 164 * Index the exchange chose not to reveal. 165 */ 166 uint32_t noreveal_index; 167 }; 168 169 170 /** 171 * Perform a refresh request. 172 * 173 * @param pg the database context 174 * @param rf the request 175 * @param zombie_required whether the coin must be a zombie 176 * @param[out] st set to the outcome 177 * @return transaction status 178 */ 179 static enum GNUNET_DB_QueryStatus 180 run_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg, 181 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf, 182 bool zombie_required, 183 struct RefreshStatus *st) 184 { 185 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 186 187 memset (st, 188 0, 189 sizeof (*st)); 190 st->zombie_required = zombie_required; 191 return TALER_EXCHANGEDB_do_refresh (pg, 192 rf, 193 &now, 194 &st->found, 195 &st->noreveal_index, 196 &st->zombie_required, 197 &st->nonce_reuse, 198 &st->balance_ok, 199 &st->coin_balance); 200 } 201 202 203 /** 204 * Melting a coin that is not known does nothing. 205 * 206 * @param pg the database context 207 * @return 0 on success 208 */ 209 static int 210 check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 211 { 212 struct TALER_CoinPublicInfo coin; 213 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 214 struct RefreshStatus st; 215 216 TDB_denom (pg, 217 10, 218 "5", 219 "0.1", 220 &denom); 221 memset (&coin, 222 0, 223 sizeof (coin)); 224 TDB_FILL (coin.coin_pub, 225 1); 226 coin.denom_pub_hash = denom.h_denom_pub; 227 coin.no_age_commitment = true; 228 make_refresh (1, 229 "1", 230 &coin, 231 false, 232 &rf); 233 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 234 run_refresh (pg, 235 &rf, 236 false, 237 &st), 238 free_refresh (&rf)); 239 free_refresh (&rf); 240 FAILIF (0 != TDB_count (pg, 241 "FROM refresh")); 242 return 0; 243 } 244 245 246 /** 247 * Melting more than the coin is worth does nothing. 248 * 249 * @param pg the database context 250 * @return 0 on success 251 */ 252 static int 253 check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg) 254 { 255 struct TALER_CoinPublicInfo coin; 256 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 257 struct RefreshStatus st; 258 struct TALER_Amount expect = TDB_amount ("5"); 259 260 TDB_coin (pg, 261 &denom, 262 20, 263 &coin, 264 NULL); 265 make_refresh (2, 266 "6", 267 &coin, 268 false, 269 &rf); 270 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 271 run_refresh (pg, 272 &rf, 273 false, 274 &st), 275 free_refresh (&rf); TDB_coin_free (&coin)); 276 free_refresh (&rf); 277 FAILIF_C (st.balance_ok, 278 TDB_coin_free (&coin)); 279 FAILIF_C (0 != TALER_amount_cmp (&st.coin_balance, 280 &expect), 281 TDB_coin_free (&coin)); 282 /* the row was written before the balance was checked, so the caller has 283 to roll back -- here the check simply notes that it is there */ 284 FAILIF_C (1 != TDB_count (pg, 285 "FROM refresh"), 286 TDB_coin_free (&coin)); 287 FAILIF_C (GNUNET_OK != 288 TDB_exec (pg, 289 "DELETE FROM refresh;"), 290 TDB_coin_free (&coin)); 291 TDB_coin_free (&coin); 292 return 0; 293 } 294 295 296 /** 297 * A melt within the coin's balance is recorded and debits the coin. 298 * 299 * @param pg the database context 300 * @return 0 on success 301 */ 302 static int 303 check_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg) 304 { 305 struct TALER_CoinPublicInfo coin; 306 struct TALER_CoinPublicInfo got_coin; 307 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 308 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got; 309 struct RefreshStatus st; 310 struct TALER_Amount expect_amount = TDB_amount ("1"); 311 312 TDB_coin (pg, 313 &denom, 314 20, 315 &coin, 316 NULL); 317 make_refresh (3, 318 "1", 319 &coin, 320 true, 321 &rf); 322 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 323 run_refresh (pg, 324 &rf, 325 false, 326 &st), 327 free_refresh (&rf); TDB_coin_free (&coin)); 328 FAILIF_C (! st.balance_ok, 329 free_refresh (&rf); TDB_coin_free (&coin)); 330 FAILIF_C (st.found, 331 free_refresh (&rf); TDB_coin_free (&coin)); 332 FAILIF_C (st.nonce_reuse, 333 free_refresh (&rf); TDB_coin_free (&coin)); 334 FAILIF_C (1 != TDB_count (pg, 335 "FROM refresh"), 336 free_refresh (&rf); TDB_coin_free (&coin)); 337 338 /* the melt is on file with everything it was created with */ 339 memset (&got, 340 0, 341 sizeof (got)); 342 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 343 TALER_EXCHANGEDB_get_refresh (pg, 344 &rf.rc, 345 &got), 346 free_refresh (&rf); TDB_coin_free (&coin)); 347 FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee, 348 &expect_amount), 349 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 350 FAILIF_C (0 != GNUNET_memcmp (&got.coin.coin_pub, 351 &coin.coin_pub), 352 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 353 FAILIF_C (0 != GNUNET_memcmp (&got.coin_sig, 354 &rf.coin_sig), 355 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 356 FAILIF_C (0 != GNUNET_memcmp (&got.refresh_seed, 357 &rf.refresh_seed), 358 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 359 FAILIF_C (1 != got.num_coins, 360 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 361 FAILIF_C (denom.serial != got.denom_serials[0], 362 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 363 FAILIF_C (rf.noreveal_index != got.noreveal_index, 364 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 365 FAILIF_C (got.revealed, 366 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 367 FAILIF_C (got.no_blinding_seed, 368 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 369 FAILIF_C (! got.is_v27_refresh, 370 free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin)); 371 free_refresh (&got); 372 373 /* the coin was debited */ 374 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 375 TALER_EXCHANGEDB_get_known_coin (pg, 376 &coin.coin_pub, 377 &got_coin), 378 free_refresh (&rf); TDB_coin_free (&coin)); 379 TALER_denom_sig_free (&got_coin.denom_sig); 380 { 381 char *hex = TDB_hex (&coin.coin_pub, 382 sizeof (coin.coin_pub)); 383 384 FAILIF_C (1 != TDB_count (pg, 385 "FROM known_coins" 386 " WHERE coin_pub=decode('%s','hex')" 387 " AND remaining=ROW(4,0)::taler_amount", 388 hex), 389 GNUNET_free (hex); 390 free_refresh (&rf); TDB_coin_free (&coin)); 391 GNUNET_free (hex); 392 } 393 394 /* a replay is idempotent and reports the index we chose */ 395 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 396 run_refresh (pg, 397 &rf, 398 false, 399 &st), 400 free_refresh (&rf); TDB_coin_free (&coin)); 401 FAILIF_C (! st.found, 402 free_refresh (&rf); TDB_coin_free (&coin)); 403 FAILIF_C (rf.noreveal_index != st.noreveal_index, 404 free_refresh (&rf); TDB_coin_free (&coin)); 405 FAILIF_C (1 != TDB_count (pg, 406 "FROM refresh"), 407 free_refresh (&rf); TDB_coin_free (&coin)); 408 free_refresh (&rf); 409 TDB_coin_free (&coin); 410 return 0; 411 } 412 413 414 /** 415 * Reusing a blinding seed is refused. 416 * 417 * @param pg the database context 418 * @return 0 on success 419 */ 420 static int 421 check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg) 422 { 423 struct TALER_CoinPublicInfo coin; 424 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 425 struct RefreshStatus st; 426 427 TDB_coin (pg, 428 &denom, 429 20, 430 &coin, 431 NULL); 432 /* a fresh commitment, but the blinding seed of check_refresh() */ 433 make_refresh (4, 434 "1", 435 &coin, 436 true, 437 &rf); 438 TDB_fill (&rf.blinding_seed, 439 sizeof (rf.blinding_seed), 440 3); 441 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 442 run_refresh (pg, 443 &rf, 444 false, 445 &st), 446 free_refresh (&rf); TDB_coin_free (&coin)); 447 FAILIF_C (! st.nonce_reuse, 448 free_refresh (&rf); TDB_coin_free (&coin)); 449 FAILIF_C (st.found, 450 free_refresh (&rf); TDB_coin_free (&coin)); 451 free_refresh (&rf); 452 TDB_coin_free (&coin); 453 FAILIF (1 != TDB_count (pg, 454 "FROM refresh")); 455 return 0; 456 } 457 458 459 /** 460 * A melt that insists on a zombie coin is refused when the coin never was 461 * one. 462 * 463 * @param pg the database context 464 * @return 0 on success 465 */ 466 static int 467 check_zombie_required (struct TALER_EXCHANGEDB_PostgresContext *pg) 468 { 469 struct TALER_CoinPublicInfo coin; 470 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 471 struct RefreshStatus st; 472 473 TDB_coin (pg, 474 &denom, 475 21, 476 &coin, 477 NULL); 478 make_refresh (5, 479 "1", 480 &coin, 481 false, 482 &rf); 483 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 484 run_refresh (pg, 485 &rf, 486 true, 487 &st), 488 free_refresh (&rf); TDB_coin_free (&coin)); 489 FAILIF_C (! st.zombie_required, 490 free_refresh (&rf); TDB_coin_free (&coin)); 491 FAILIF_C (st.balance_ok, 492 free_refresh (&rf); TDB_coin_free (&coin)); 493 free_refresh (&rf); 494 TDB_coin_free (&coin); 495 /* the row went in before the zombie check, so the caller must roll back */ 496 FAILIF (2 != TDB_count (pg, 497 "FROM refresh")); 498 FAILIF (GNUNET_OK != 499 TDB_exec (pg, 500 "DELETE FROM refresh WHERE NOT revealed" 501 " AND rc <> (SELECT rc FROM refresh" 502 " ORDER BY refresh_id ASC LIMIT 1);")); 503 return 0; 504 } 505 506 507 /** 508 * A melt can be marked as revealed, and marking one that does not exist 509 * does nothing. 510 * 511 * @param pg the database context 512 * @return 0 on success 513 */ 514 static int 515 check_revealed (struct TALER_EXCHANGEDB_PostgresContext *pg) 516 { 517 struct TALER_RefreshCommitmentP rc; 518 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got; 519 520 TDB_FILL (rc, 521 99); 522 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 523 TALER_EXCHANGEDB_update_to_refresh_revealed (pg, 524 &rc)); 525 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 526 TALER_EXCHANGEDB_get_refresh (pg, 527 &rc, 528 &got)); 529 530 TDB_FILL (rc, 531 3); 532 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 533 TALER_EXCHANGEDB_update_to_refresh_revealed (pg, 534 &rc)); 535 memset (&got, 536 0, 537 sizeof (got)); 538 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 539 TALER_EXCHANGEDB_get_refresh (pg, 540 &rc, 541 &got)); 542 FAILIF_C (! got.revealed, 543 free_refresh (&got)); 544 free_refresh (&got); 545 /* marking it again is still reported as a row touched */ 546 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 547 TALER_EXCHANGEDB_update_to_refresh_revealed (pg, 548 &rc)); 549 return 0; 550 } 551 552 553 /** 554 * Closure for #refreshes_cb(). 555 */ 556 struct RefreshesContext 557 { 558 /** 559 * How many melts did the callback see? 560 */ 561 unsigned int total; 562 563 /** 564 * Stop after this many melts; 0 for no limit. 565 */ 566 unsigned int stop_after; 567 568 /** 569 * Row of the last melt. 570 */ 571 uint64_t rowid; 572 573 /** 574 * Amount of the last melt. 575 */ 576 struct TALER_Amount amount; 577 578 /** 579 * Number of new denominations of the last melt. 580 */ 581 size_t num_nds; 582 }; 583 584 585 /** 586 * Callback for #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id(). 587 * 588 * @param cls a `struct RefreshesContext *` 589 * @param rowid row of the melt 590 * @param old_denom_pub denomination of the melted coin 591 * @param coin_pub the melted coin 592 * @param coin_sig signature authorising the melt 593 * @param h_age_commitment age commitment of the coin, NULL if none 594 * @param amount_with_fee how much was melted 595 * @param num_nds length of @a new_denom_serials 596 * @param new_denom_serials denominations of the fresh coins 597 * @param rc commitment of the melt 598 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 599 */ 600 static enum GNUNET_GenericReturnValue 601 refreshes_cb (void *cls, 602 uint64_t rowid, 603 const struct TALER_DenominationPublicKey *old_denom_pub, 604 const struct TALER_CoinSpendPublicKeyP *coin_pub, 605 const struct TALER_CoinSpendSignatureP *coin_sig, 606 const struct TALER_AgeCommitmentHashP *h_age_commitment, 607 const struct TALER_Amount *amount_with_fee, 608 size_t num_nds, 609 uint64_t new_denom_serials[static num_nds], 610 const struct TALER_RefreshCommitmentP *rc) 611 { 612 struct RefreshesContext *ctx = cls; 613 614 (void) old_denom_pub; 615 (void) coin_pub; 616 (void) coin_sig; 617 (void) h_age_commitment; 618 (void) new_denom_serials; 619 (void) rc; 620 ctx->total++; 621 ctx->rowid = rowid; 622 ctx->amount = *amount_with_fee; 623 ctx->num_nds = num_nds; 624 if ( (0 != ctx->stop_after) && 625 (ctx->total >= ctx->stop_after) ) 626 return GNUNET_SYSERR; 627 return GNUNET_OK; 628 } 629 630 631 /** 632 * The auditor's view walks the melts by serial and stops when asked to. 633 * 634 * @param pg the database context 635 * @return 0 on success 636 */ 637 static int 638 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 639 { 640 struct TALER_CoinPublicInfo coin; 641 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 642 struct RefreshStatus st; 643 struct RefreshesContext ctx; 644 uint64_t rows; 645 646 /* one more melt, so that "stop after the first" is distinguishable 647 from "saw everything" */ 648 TDB_coin (pg, 649 &denom, 650 42, 651 &coin, 652 NULL); 653 make_refresh (42, 654 "1", 655 &coin, 656 false, 657 &rf); 658 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 659 run_refresh (pg, 660 &rf, 661 false, 662 &st), 663 free_refresh (&rf); TDB_coin_free (&coin)); 664 free_refresh (&rf); 665 TDB_coin_free (&coin); 666 FAILIF (! st.balance_ok); 667 rows = TDB_count (pg, 668 "FROM refresh"); 669 FAILIF (2 != rows); 670 memset (&ctx, 671 0, 672 sizeof (ctx)); 673 FAILIF (0 >= 674 TALER_EXCHANGEDB_iterate_refreshes_above_serial_id ( 675 pg, 676 0, 677 &refreshes_cb, 678 &ctx)); 679 FAILIF (rows != ctx.total); 680 /* every melt here asked for exactly one fresh coin */ 681 FAILIF (1 != ctx.num_nds); 682 683 /* everything above the last row is nothing */ 684 memset (&ctx, 685 0, 686 sizeof (ctx)); 687 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 688 TALER_EXCHANGEDB_iterate_refreshes_above_serial_id ( 689 pg, 690 UINT32_MAX, 691 &refreshes_cb, 692 &ctx)); 693 FAILIF (0 != ctx.total); 694 695 /* a callback that says stop is not called again */ 696 memset (&ctx, 697 0, 698 sizeof (ctx)); 699 ctx.stop_after = 1; 700 FAILIF (0 >= 701 TALER_EXCHANGEDB_iterate_refreshes_above_serial_id ( 702 pg, 703 0, 704 &refreshes_cb, 705 &ctx)); 706 FAILIF (1 != ctx.total); 707 return 0; 708 } 709 710 711 /** 712 * A blinded coin hash that was never seen must not be attributed to any 713 * old coin. 714 * 715 * This is all get_old_coin_by_h_blind() can be checked for today: its 716 * statement selects on a `h_blind_evs` column that `refresh` does not 717 * have, so it cannot return a row at all (EDBT-8). The assertion below 718 * holds both now and once that is repaired. 719 * 720 * @param pg the database context 721 * @return 0 on success 722 */ 723 static int 724 check_old_coin_by_h_blind (struct TALER_EXCHANGEDB_PostgresContext *pg) 725 { 726 struct TALER_BlindedCoinHashP h_blind_ev; 727 struct TALER_CoinSpendPublicKeyP old_coin_pub; 728 uint64_t rrc_serial; 729 730 TDB_FILL (h_blind_ev, 731 123); 732 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 733 TALER_EXCHANGEDB_get_old_coin_by_h_blind (pg, 734 &h_blind_ev, 735 &old_coin_pub, 736 &rrc_serial)); 737 return 0; 738 } 739 740 741 /** 742 * The checks to run, in order. 743 */ 744 static const struct TDB_Test tests[] = { 745 { "refresh-unknown-coin", 746 &check_unknown_coin }, 747 { "refresh-insufficient-balance", 748 &check_insufficient_balance }, 749 { "refresh-refresh", 750 &check_refresh }, 751 { "refresh-nonce-reuse", 752 &check_nonce_reuse }, 753 { "refresh-zombie-required", 754 &check_zombie_required }, 755 { "refresh-revealed", 756 &check_revealed }, 757 { "refresh-iterate", 758 &check_iterate }, 759 { "refresh-old-coin-by-h-blind", 760 &check_old_coin_by_h_blind }, 761 { NULL, NULL } 762 }; 763 764 765 int 766 main (int argc, 767 char *const *argv) 768 { 769 int ret; 770 771 ret = TDB_main (argc, 772 argv, 773 "test-refresh", 774 "Tests for the exchangedb `refresh' table", 775 tests); 776 TDB_denom_free (&denom); 777 return ret; 778 } 779 780 781 /* end of test_refresh.c */