test_recoup_refresh.c (24562B)
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_recoup_refresh.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `recoup_refresh` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_recoup_refresh() and 23 * #TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id(). 24 * 25 * `recoup_refresh` references `known_coins` and `refresh`, so each check 26 * builds a melt first. do_recoup_refresh() moves the fresh coin's whole 27 * remaining balance back to the old coin. 28 */ 29 #include "test_common.h" 30 #include "exchange-database/do_recoup_refresh.h" 31 #include "exchange-database/do_refresh.h" 32 #include "exchange-database/iterate_recoup_refreshes_above_serial_id.h" 33 #include "exchange-database/iterate_records_by_table.h" 34 #include "exchange-database/insert_records_by_table.h" 35 36 37 /** 38 * Account the checks fund their reserves from. 39 */ 40 static struct TDB_Account account; 41 42 43 /** 44 * Denomination the checks use. 45 */ 46 static struct TDB_Denom denom; 47 48 49 /** 50 * Melt @a coin and return the row of the melt. 51 * 52 * @param pg the database context 53 * @param coin coin to melt 54 * @param seed seed for the commitment and signatures 55 * @param amount how much to melt, e.g. "1" 56 * @return row of the melt in `refresh` 57 */ 58 static uint64_t 59 melt (struct TALER_EXCHANGEDB_PostgresContext *pg, 60 const struct TALER_CoinPublicInfo *coin, 61 uint32_t seed, 62 const char *amount) 63 { 64 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 65 struct TALER_BlindedDenominationSignature denom_sig; 66 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 67 struct TALER_Amount coin_balance; 68 uint64_t denom_serial = denom.serial; 69 uint64_t refresh_id; 70 uint32_t noreveal_index; 71 bool found; 72 bool zombie_required = false; 73 bool nonce_reuse; 74 bool balance_ok; 75 76 memset (&rf, 77 0, 78 sizeof (rf)); 79 rf.coin.coin_pub = coin->coin_pub; 80 rf.coin.denom_pub_hash = coin->denom_pub_hash; 81 rf.coin.no_age_commitment = coin->no_age_commitment; 82 TDB_fill (&rf.coin_sig, 83 sizeof (rf.coin_sig), 84 seed); 85 TDB_fill (&rf.rc, 86 sizeof (rf.rc), 87 seed); 88 TDB_fill (&rf.refresh_seed, 89 sizeof (rf.refresh_seed), 90 seed); 91 TDB_fill (&rf.planchets_h, 92 sizeof (rf.planchets_h), 93 seed); 94 TDB_fill (&rf.selected_h, 95 sizeof (rf.selected_h), 96 seed + 1); 97 rf.amount_with_fee = TDB_amount (amount); 98 rf.num_coins = 1; 99 rf.denom_serials = &denom_serial; 100 TDB_blinded_denom_sig (seed, 101 &denom_sig); 102 rf.denom_sigs = &denom_sig; 103 rf.noreveal_index = 0; 104 rf.is_v27_refresh = true; 105 rf.no_blinding_seed = true; 106 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 107 TALER_EXCHANGEDB_do_refresh (pg, 108 &rf, 109 &now, 110 &found, 111 &noreveal_index, 112 &zombie_required, 113 &nonce_reuse, 114 &balance_ok, 115 &coin_balance)); 116 GNUNET_assert (balance_ok); 117 TALER_blinded_denom_sig_free (&denom_sig); 118 { 119 struct GNUNET_PQ_QueryParam params[] = { 120 GNUNET_PQ_query_param_auto_from_type (&rf.rc), 121 GNUNET_PQ_query_param_end 122 }; 123 struct GNUNET_PQ_ResultSpec rs[] = { 124 GNUNET_PQ_result_spec_uint64 ("refresh_id", 125 &refresh_id), 126 GNUNET_PQ_result_spec_end 127 }; 128 129 GNUNET_assert (GNUNET_OK == 130 GNUNET_PQ_prepare_anon (pg->conn, 131 "SELECT refresh_id" 132 " FROM refresh" 133 " WHERE rc=$1;")); 134 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 135 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 136 "", 137 params, 138 rs)); 139 } 140 return refresh_id; 141 } 142 143 144 /** 145 * Callback for 146 * #TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id(). 147 * 148 * @param cls a pointer to an `unsigned int` counter 149 * @param rowid row of the recoup 150 * @param timestamp when it happened 151 * @param amount how much went back 152 * @param old_coin_pub the old coin 153 * @param old_denom_pub_hash denomination of the old coin 154 * @param coin the fresh coin 155 * @param denom_pub denomination of @a coin 156 * @param coin_sig signature over the request 157 * @param coin_blind blinding secret of the fresh coin 158 * @return #GNUNET_OK 159 */ 160 static enum GNUNET_GenericReturnValue 161 recoup_refresh_cb (void *cls, 162 uint64_t rowid, 163 struct GNUNET_TIME_Timestamp timestamp, 164 const struct TALER_Amount *amount, 165 const struct TALER_CoinSpendPublicKeyP *old_coin_pub, 166 const struct TALER_DenominationHashP *old_denom_pub_hash, 167 const struct TALER_CoinPublicInfo *coin, 168 const struct TALER_DenominationPublicKey *denom_pub, 169 const struct TALER_CoinSpendSignatureP *coin_sig, 170 const union GNUNET_CRYPTO_BlindingSecretP *coin_blind) 171 { 172 unsigned int *total = cls; 173 174 (void) rowid; 175 (void) timestamp; 176 (void) amount; 177 (void) old_coin_pub; 178 (void) old_denom_pub_hash; 179 (void) coin; 180 (void) denom_pub; 181 (void) coin_sig; 182 (void) coin_blind; 183 (*total)++; 184 return GNUNET_OK; 185 } 186 187 188 /** 189 * Outcome of a recoup-refresh request. 190 */ 191 struct RecoupStatus 192 { 193 /** 194 * Was the recoup accepted? 195 */ 196 bool recoup_ok; 197 198 /** 199 * Did something go wrong inside the database? 200 */ 201 bool internal_failure; 202 203 /** 204 * When the recoup happened. 205 */ 206 struct GNUNET_TIME_Timestamp recoup_timestamp; 207 208 /** 209 * Amount the recoup credited. 210 */ 211 struct TALER_Amount recoup_amount; 212 }; 213 214 215 /** 216 * Recoup a fresh coin back onto the coin it was refreshed from. 217 * 218 * @param pg the database context 219 * @param old_coin_pub coin to credit 220 * @param refresh_id melt that justifies the recoup 221 * @param coin_pub fresh coin to drain 222 * @param known_coin_id row of @a coin_pub 223 * @param seed seed for the blinding secret and coin signature 224 * @param[out] st set to the outcome 225 * @return transaction status 226 */ 227 static enum GNUNET_DB_QueryStatus 228 run_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg, 229 const struct TALER_CoinSpendPublicKeyP *old_coin_pub, 230 uint64_t refresh_id, 231 const struct TALER_CoinSpendPublicKeyP *coin_pub, 232 uint64_t known_coin_id, 233 uint32_t seed, 234 struct RecoupStatus *st) 235 { 236 union GNUNET_CRYPTO_BlindingSecretP coin_bks; 237 struct TALER_CoinSpendSignatureP coin_sig; 238 239 TDB_fill (&coin_bks, 240 sizeof (coin_bks), 241 seed); 242 TDB_fill (&coin_sig, 243 sizeof (coin_sig), 244 seed); 245 memset (st, 246 0, 247 sizeof (*st)); 248 st->recoup_timestamp = GNUNET_TIME_timestamp_get (); 249 return TALER_EXCHANGEDB_do_recoup_refresh (pg, 250 old_coin_pub, 251 refresh_id, 252 0, 253 &coin_bks, 254 coin_pub, 255 known_coin_id, 256 &coin_sig, 257 &st->recoup_timestamp, 258 &st->recoup_amount, 259 &st->recoup_ok, 260 &st->internal_failure); 261 } 262 263 264 /** 265 * Recouping a coin the exchange does not know is an internal failure. 266 * 267 * @param pg the database context 268 * @return 0 on success 269 */ 270 static int 271 check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 272 { 273 struct TALER_ReservePublicKeyP reserve_pub; 274 struct TALER_CoinPublicInfo old_coin; 275 struct TALER_CoinSpendPublicKeyP coin_pub; 276 struct RecoupStatus st; 277 uint64_t refresh_id; 278 279 TDB_denom (pg, 280 10, 281 "5", 282 "0.1", 283 &denom); 284 TDB_account (pg, 285 10, 286 &account); 287 TDB_reserve_in (pg, 288 &account, 289 10, 290 "10", 291 &reserve_pub); 292 TDB_coin (pg, 293 &denom, 294 20, 295 &old_coin, 296 NULL); 297 refresh_id = melt (pg, 298 &old_coin, 299 1, 300 "1"); 301 TDB_FILL (coin_pub, 302 99); 303 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 304 run_recoup (pg, 305 &old_coin.coin_pub, 306 refresh_id, 307 &coin_pub, 308 1, 309 99, 310 &st), 311 TDB_coin_free (&old_coin)); 312 TDB_coin_free (&old_coin); 313 FAILIF (! st.internal_failure); 314 FAILIF (st.recoup_ok); 315 FAILIF (0 != TDB_count (pg, 316 "FROM recoup_refresh")); 317 return 0; 318 } 319 320 321 /** 322 * A fresh coin with nothing left on it and no earlier recoup is refused. 323 * 324 * @param pg the database context 325 * @return 0 on success 326 */ 327 static int 328 check_empty_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 329 { 330 struct TALER_CoinPublicInfo old_coin; 331 struct TALER_CoinPublicInfo fresh; 332 struct RecoupStatus st; 333 uint64_t known_coin_id; 334 uint64_t refresh_id; 335 char *hex; 336 337 TDB_coin (pg, 338 &denom, 339 21, 340 &old_coin, 341 NULL); 342 refresh_id = melt (pg, 343 &old_coin, 344 2, 345 "1"); 346 TDB_coin (pg, 347 &denom, 348 22, 349 &fresh, 350 &known_coin_id); 351 hex = TDB_hex (&fresh.coin_pub, 352 sizeof (fresh.coin_pub)); 353 FAILIF_C (GNUNET_OK != 354 TDB_exec (pg, 355 "UPDATE known_coins" 356 " SET remaining=ROW(0,0)::taler_amount" 357 " WHERE coin_pub=decode('%s','hex');", 358 hex), 359 GNUNET_free (hex); 360 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 361 GNUNET_free (hex); 362 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 363 run_recoup (pg, 364 &old_coin.coin_pub, 365 refresh_id, 366 &fresh.coin_pub, 367 known_coin_id, 368 22, 369 &st), 370 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 371 TDB_coin_free (&fresh); 372 TDB_coin_free (&old_coin); 373 FAILIF (st.internal_failure); 374 FAILIF (st.recoup_ok); 375 FAILIF (0 != TDB_count (pg, 376 "FROM recoup_refresh")); 377 return 0; 378 } 379 380 381 /** 382 * A funded fresh coin is drained back onto the old coin. 383 * 384 * @param pg the database context 385 * @return 0 on success 386 */ 387 static int 388 check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg) 389 { 390 struct TALER_CoinPublicInfo old_coin; 391 struct TALER_CoinPublicInfo fresh; 392 struct RecoupStatus st; 393 uint64_t known_coin_id; 394 uint64_t refresh_id; 395 char *hex; 396 397 TDB_coin (pg, 398 &denom, 399 23, 400 &old_coin, 401 NULL); 402 refresh_id = melt (pg, 403 &old_coin, 404 3, 405 "1"); 406 TDB_coin (pg, 407 &denom, 408 24, 409 &fresh, 410 &known_coin_id); 411 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 412 run_recoup (pg, 413 &old_coin.coin_pub, 414 refresh_id, 415 &fresh.coin_pub, 416 known_coin_id, 417 24, 418 &st), 419 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 420 FAILIF_C (st.internal_failure, 421 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 422 FAILIF_C (! st.recoup_ok, 423 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 424 FAILIF_C (1 != TDB_count (pg, 425 "FROM recoup_refresh"), 426 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 427 428 /* the fresh coin is empty */ 429 hex = TDB_hex (&fresh.coin_pub, 430 sizeof (fresh.coin_pub)); 431 FAILIF_C (1 != TDB_count (pg, 432 "FROM known_coins" 433 " WHERE coin_pub=decode('%s','hex')" 434 " AND remaining=ROW(0,0)::taler_amount", 435 hex), 436 GNUNET_free (hex); 437 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 438 GNUNET_free (hex); 439 /* the old coin got the EUR:5 back on top of the EUR:4 it had left */ 440 hex = TDB_hex (&old_coin.coin_pub, 441 sizeof (old_coin.coin_pub)); 442 FAILIF_C (1 != TDB_count (pg, 443 "FROM known_coins" 444 " WHERE coin_pub=decode('%s','hex')" 445 " AND remaining=ROW(9,0)::taler_amount", 446 hex), 447 GNUNET_free (hex); 448 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 449 GNUNET_free (hex); 450 451 /* recouping the same coin again finds the earlier recoup */ 452 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 453 run_recoup (pg, 454 &old_coin.coin_pub, 455 refresh_id, 456 &fresh.coin_pub, 457 known_coin_id, 458 24, 459 &st), 460 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 461 FAILIF_C (! st.recoup_ok, 462 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 463 FAILIF_C (1 != TDB_count (pg, 464 "FROM recoup_refresh"), 465 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 466 467 /* ...but a recoup of the same coin for another refresh operation 468 is not the same recoup: the coin has nothing left for it */ 469 { 470 uint64_t other_refresh_id; 471 472 other_refresh_id = melt (pg, 473 &old_coin, 474 5, 475 "1"); 476 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 477 run_recoup (pg, 478 &old_coin.coin_pub, 479 other_refresh_id, 480 &fresh.coin_pub, 481 known_coin_id, 482 24, 483 &st), 484 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 485 FAILIF_C (st.recoup_ok, 486 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 487 FAILIF_C (st.internal_failure, 488 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 489 FAILIF_C (1 != TDB_count (pg, 490 "FROM recoup_refresh"), 491 TDB_coin_free (&fresh); TDB_coin_free (&old_coin)); 492 } 493 TDB_coin_free (&fresh); 494 TDB_coin_free (&old_coin); 495 return 0; 496 } 497 498 499 /** 500 * Closure for #replication_cb(). 501 */ 502 struct ReplicationContext 503 { 504 /** 505 * Coin index every record must carry. 506 */ 507 uint32_t expected_coin_index; 508 509 /** 510 * Records seen. 511 */ 512 unsigned int seen; 513 514 /** 515 * Set if a record carried another coin index. 516 */ 517 bool mismatch; 518 }; 519 520 521 /** 522 * Counts the replication records of the recoup table and checks 523 * their coin index. 524 * 525 * @param cls a `struct ReplicationContext *` 526 * @param td the record 527 * @return #GNUNET_OK to continue 528 */ 529 static int 530 replication_cb (void *cls, 531 const struct TALER_EXCHANGEDB_TableData *td) 532 { 533 struct ReplicationContext *rc = cls; 534 535 if (TALER_EXCHANGEDB_RT_RECOUP_REFRESH != td->table) 536 { 537 rc->mismatch = true; 538 return GNUNET_OK; 539 } 540 rc->seen++; 541 if (rc->expected_coin_index != td->details.recoup_refresh.coin_index) 542 rc->mismatch = true; 543 return GNUNET_OK; 544 } 545 546 547 /** 548 * Replication carries the coin index of a recoup-refresh. 549 * 550 * @param pg the database context 551 * @return 0 on success 552 */ 553 static int 554 check_replication (struct TALER_EXCHANGEDB_PostgresContext *pg) 555 { 556 struct ReplicationContext rc = { 557 .expected_coin_index = 0 558 }; 559 struct TALER_EXCHANGEDB_TableData td = { 560 .table = TALER_EXCHANGEDB_RT_RECOUP_REFRESH, 561 .serial = 77, 562 .details.recoup_refresh.coin_index = 7, 563 .details.recoup_refresh.amount = TDB_amount ("1"), 564 .details.recoup_refresh.recoup_timestamp = GNUNET_TIME_timestamp_get () 565 }; 566 struct TALER_CoinPublicInfo old_coin; 567 struct TALER_CoinPublicInfo fresh; 568 569 /* the row made by check_recoup_refresh() is reported with its index 0 */ 570 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 571 TALER_EXCHANGEDB_iterate_records_by_table ( 572 pg, 573 TALER_EXCHANGEDB_RT_RECOUP_REFRESH, 574 0, 575 &replication_cb, 576 &rc)); 577 FAILIF (1 != rc.seen); 578 FAILIF (rc.mismatch); 579 /* a replicated row keeps its index */ 580 TDB_coin (pg, 581 &denom, 582 25, 583 &old_coin, 584 NULL); 585 TDB_coin (pg, 586 &denom, 587 26, 588 &fresh, 589 &td.details.recoup_refresh.known_coin_id); 590 td.details.recoup_refresh.coin_pub = fresh.coin_pub; 591 td.details.recoup_refresh.refresh_id = melt (pg, 592 &old_coin, 593 6, 594 "1"); 595 TDB_coin_free (&old_coin); 596 TDB_coin_free (&fresh); 597 TDB_FILL (td.details.recoup_refresh.coin_sig, 598 77); 599 TDB_FILL (td.details.recoup_refresh.coin_blind, 600 77); 601 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 602 TALER_EXCHANGEDB_insert_records_by_table (pg, 603 &td)); 604 FAILIF (1 != TDB_count (pg, 605 "FROM recoup_refresh WHERE coin_index=7")); 606 rc.seen = 0; 607 rc.expected_coin_index = 7; 608 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 609 TALER_EXCHANGEDB_iterate_records_by_table ( 610 pg, 611 TALER_EXCHANGEDB_RT_RECOUP_REFRESH, 612 76, 613 &replication_cb, 614 &rc)); 615 FAILIF (1 != rc.seen); 616 FAILIF (rc.mismatch); 617 return 0; 618 } 619 620 621 /** 622 * The iterator must not report a recoup-refresh that is not there. 623 * 624 * This is all it can be checked for today: its statement joins 625 * `refresh_revealed_coins` and `refresh_commitments`, tables the schema no 626 * longer has, so it cannot return a row at all (EDB-16). The assertion 627 * below holds both now and once that is repaired. 628 * 629 * @param pg the database context 630 * @return 0 on success 631 */ 632 static int 633 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 634 { 635 unsigned int total = 0; 636 637 /* the one recoup made in check_recoup_refresh() */ 638 FAILIF (1 != 639 TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id ( 640 pg, 641 0, 642 &recoup_refresh_cb, 643 &total)); 644 FAILIF (1 != total); 645 total = 0; 646 FAILIF (0 < 647 TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id ( 648 pg, 649 1000, 650 &recoup_refresh_cb, 651 &total)); 652 FAILIF (0 != total); 653 return 0; 654 } 655 656 657 /** 658 * A recouped coin that was credited again (a refund does that) can be 659 * recouped a second time. Replaying the request afterwards must report 660 * the latest recoup, not an arbitrary earlier one. 661 * 662 * @param pg the database context 663 * @return 0 on success 664 */ 665 static int 666 check_replay_latest (struct TALER_EXCHANGEDB_PostgresContext *pg) 667 { 668 struct TALER_CoinPublicInfo old_coin; 669 struct TALER_CoinPublicInfo fresh; 670 struct RecoupStatus st; 671 struct TALER_Amount expect_first = TDB_amount ("5"); 672 struct TALER_Amount expect_second = TDB_amount ("2"); 673 uint64_t known_coin_id; 674 uint64_t refresh_id; 675 char *hex; 676 int ret = 1; 677 678 TDB_coin (pg, 679 &denom, 680 27, 681 &old_coin, 682 NULL); 683 refresh_id = melt (pg, 684 &old_coin, 685 7, 686 "1"); 687 TDB_coin (pg, 688 &denom, 689 28, 690 &fresh, 691 &known_coin_id); 692 hex = TDB_hex (&fresh.coin_pub, 693 sizeof (fresh.coin_pub)); 694 695 /* first recoup drains the fresh coin's EUR:5 */ 696 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 697 run_recoup (pg, 698 &old_coin.coin_pub, 699 refresh_id, 700 &fresh.coin_pub, 701 known_coin_id, 702 28, 703 &st), 704 goto cleanup); 705 FAILIF_C ( (! st.recoup_ok) || 706 (st.internal_failure) || 707 (0 != TALER_amount_cmp (&st.recoup_amount, 708 &expect_first)), 709 goto cleanup); 710 711 /* the fresh coin gets EUR:2 back, as a refund would do */ 712 FAILIF_C (GNUNET_OK != 713 TDB_exec (pg, 714 "UPDATE known_coins" 715 " SET remaining=ROW(2,0)::taler_amount" 716 " WHERE coin_pub=decode('%s','hex');", 717 hex), 718 goto cleanup); 719 720 /* second recoup for the same refresh drains the EUR:2 */ 721 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 722 run_recoup (pg, 723 &old_coin.coin_pub, 724 refresh_id, 725 &fresh.coin_pub, 726 known_coin_id, 727 28, 728 &st), 729 goto cleanup); 730 FAILIF_C ( (! st.recoup_ok) || 731 (st.internal_failure) || 732 (0 != TALER_amount_cmp (&st.recoup_amount, 733 &expect_second)), 734 goto cleanup); 735 FAILIF_C (2 != TDB_count (pg, 736 "FROM recoup_refresh" 737 " WHERE coin_pub=decode('%s','hex')", 738 hex), 739 goto cleanup); 740 741 /* replaying the request reports the latest recoup... */ 742 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 743 run_recoup (pg, 744 &old_coin.coin_pub, 745 refresh_id, 746 &fresh.coin_pub, 747 known_coin_id, 748 28, 749 &st), 750 goto cleanup); 751 FAILIF_C ( (! st.recoup_ok) || 752 (st.internal_failure), 753 goto cleanup); 754 FAILIF_C (0 != TALER_amount_cmp (&st.recoup_amount, 755 &expect_second), 756 goto cleanup); 757 /* ...and does not touch the tables again */ 758 FAILIF_C (2 != TDB_count (pg, 759 "FROM recoup_refresh" 760 " WHERE coin_pub=decode('%s','hex')", 761 hex), 762 goto cleanup); 763 ret = 0; 764 cleanup: 765 GNUNET_free (hex); 766 TDB_coin_free (&fresh); 767 TDB_coin_free (&old_coin); 768 return ret; 769 } 770 771 772 /** 773 * The checks to run, in order. 774 */ 775 static const struct TDB_Test tests[] = { 776 { "recoup-refresh-unknown-coin", 777 &check_unknown_coin }, 778 { "recoup-refresh-empty-coin", 779 &check_empty_coin }, 780 { "recoup-refresh-recoup", 781 &check_recoup_refresh }, 782 { "recoup-refresh-iterate", 783 &check_iterate }, 784 { "recoup-refresh-replication", 785 &check_replication }, 786 { "recoup-refresh-replay-latest", 787 &check_replay_latest }, 788 { NULL, NULL } 789 }; 790 791 792 int 793 main (int argc, 794 char *const *argv) 795 { 796 int ret; 797 798 ret = TDB_main (argc, 799 argv, 800 "test-recoup-refresh", 801 "Tests for the exchangedb `recoup_refresh' table", 802 tests); 803 TDB_account_free (&account); 804 TDB_denom_free (&denom); 805 return ret; 806 } 807 808 809 /* end of test_recoup_refresh.c */