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