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