test_withdraw.c (23553B)
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_withdraw.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `withdraw` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_withdraw(), 23 * #TALER_EXCHANGEDB_get_withdraw(), 24 * #TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id() and 25 * #TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check(). 26 * 27 * `withdraw` references `reserves` and `denominations`. do_withdraw() has 28 * four distinct "did not do the work" answers -- unknown reserve, 29 * insufficient balance, age requirement not met and blinding-seed reuse -- 30 * plus an idempotent replay, and each of them is checked. 31 */ 32 #include "test_common.h" 33 #include "exchange-database/do_withdraw.h" 34 #include "exchange-database/get_withdraw.h" 35 #include "exchange-database/get_reserve.h" 36 #include "exchange-database/iterate_withdrawals_above_serial_id.h" 37 #include "exchange-database/iterate_withdraw_amounts_for_kyc_check.h" 38 39 40 /** 41 * Account the checks fund their reserves from. 42 */ 43 static struct TDB_Account account; 44 45 46 /** 47 * Denomination the checks withdraw. 48 */ 49 static struct TDB_Denom denom; 50 51 52 /** 53 * Fill in a withdraw request for one coin of #denom. 54 * 55 * @param seed seed for the planchet hash, signature and blinding seed 56 * @param amount amount to withdraw, e.g. "5" 57 * @param reserve_pub reserve to withdraw from 58 * @param with_seed true to pass a blinding seed 59 * @param[out] wd set to the request; release with free_withdraw() 60 */ 61 static void 62 make_withdraw (uint32_t seed, 63 const char *amount, 64 const struct TALER_ReservePublicKeyP *reserve_pub, 65 bool with_seed, 66 struct TALER_EXCHANGEDB_Withdraw *wd) 67 { 68 memset (wd, 69 0, 70 sizeof (*wd)); 71 wd->amount_with_fee = TDB_amount (amount); 72 wd->age_proof_required = false; 73 wd->reserve_pub = *reserve_pub; 74 TDB_fill (&wd->planchets_h, 75 sizeof (wd->planchets_h), 76 seed); 77 TDB_fill (&wd->reserve_sig, 78 sizeof (wd->reserve_sig), 79 seed); 80 wd->num_coins = 1; 81 wd->denom_serials = GNUNET_new (uint64_t); 82 wd->denom_serials[0] = denom.serial; 83 wd->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature); 84 TDB_blinded_denom_sig (seed, 85 &wd->denom_sigs[0]); 86 wd->no_blinding_seed = ! with_seed; 87 if (with_seed) 88 TDB_fill (&wd->blinding_seed, 89 sizeof (wd->blinding_seed), 90 seed); 91 } 92 93 94 /** 95 * Release what make_withdraw() allocated. 96 * 97 * @param[in,out] wd request to clean up 98 */ 99 static void 100 free_withdraw (struct TALER_EXCHANGEDB_Withdraw *wd) 101 { 102 for (size_t i = 0; i<wd->num_coins; i++) 103 TALER_blinded_denom_sig_free (&wd->denom_sigs[i]); 104 GNUNET_free (wd->denom_sigs); 105 GNUNET_free (wd->denom_serials); 106 } 107 108 109 /** 110 * Run a withdraw request. 111 * 112 * @param pg the database context 113 * @param wd the request 114 * @param[out] st set to the outcome flags 115 * @return transaction status 116 */ 117 struct WithdrawStatus 118 { 119 /** 120 * Was the balance sufficient? 121 */ 122 bool balance_ok; 123 124 /** 125 * Were the age requirements met? 126 */ 127 bool age_ok; 128 129 /** 130 * Was this a replay? 131 */ 132 bool idempotent; 133 134 /** 135 * Was the blinding seed used before? 136 */ 137 bool nonce_reuse; 138 139 /** 140 * Balance the reserve had. 141 */ 142 struct TALER_Amount reserve_balance; 143 144 /** 145 * Maximum age the reserve allows. 146 */ 147 uint16_t allowed_maximum_age; 148 149 /** 150 * Birthday recorded for the reserve. 151 */ 152 uint32_t reserve_birthday; 153 154 /** 155 * Index the exchange chose not to reveal. 156 */ 157 uint16_t noreveal_index; 158 }; 159 160 161 /** 162 * Perform a withdraw request. 163 * 164 * @param pg the database context 165 * @param wd the request 166 * @param[out] st set to the outcome 167 * @return transaction status 168 */ 169 static enum GNUNET_DB_QueryStatus 170 run_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg, 171 const struct TALER_EXCHANGEDB_Withdraw *wd, 172 struct WithdrawStatus *st) 173 { 174 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 175 176 memset (st, 177 0, 178 sizeof (*st)); 179 return TALER_EXCHANGEDB_do_withdraw (pg, 180 wd, 181 &now, 182 &st->balance_ok, 183 &st->reserve_balance, 184 &st->age_ok, 185 &st->allowed_maximum_age, 186 &st->reserve_birthday, 187 &st->idempotent, 188 &st->noreveal_index, 189 &st->nonce_reuse); 190 } 191 192 193 /** 194 * Closure for #withdraw_cb(). 195 */ 196 struct WithdrawContext 197 { 198 /** 199 * How many rows did the callback see? 200 */ 201 unsigned int total; 202 203 /** 204 * Stop after this many rows; 0 for no limit. 205 */ 206 unsigned int stop_after; 207 208 /** 209 * Planchet hash we are looking for, NULL to match nothing. 210 */ 211 const struct TALER_HashBlindedPlanchetsP *planchets_h; 212 213 /** 214 * How many times did we see it? 215 */ 216 unsigned int matched; 217 218 /** 219 * Amount reported for it. 220 */ 221 struct TALER_Amount amount; 222 223 /** 224 * Denomination serials reported for it. 225 */ 226 uint64_t denom_serial; 227 228 /** 229 * Number of denominations reported for it. 230 */ 231 size_t num_denom_serials; 232 233 /** 234 * Was a blinding seed reported for it? 235 */ 236 bool have_seed; 237 }; 238 239 240 /** 241 * Callback for #TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id(). 242 * 243 * @param cls a `struct WithdrawContext *` 244 * @param rowid row of the withdraw 245 * @param num_denom_serials number of denominations withdrawn 246 * @param denom_serials the denominations withdrawn 247 * @param selected_h hash over the selected planchets 248 * @param h_planchets hash over all planchets 249 * @param blinding_seed blinding seed, NULL if none 250 * @param age_proof_required was an age proof required? 251 * @param max_age maximum age of the coins 252 * @param noreveal_index index the exchange did not reveal 253 * @param reserve_pub reserve that was drained 254 * @param reserve_sig signature over the request 255 * @param execution_date when the withdraw happened 256 * @param amount_with_fee how much was withdrawn 257 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 258 */ 259 static enum GNUNET_GenericReturnValue 260 withdraw_cb (void *cls, 261 uint64_t rowid, 262 size_t num_denom_serials, 263 const uint64_t *denom_serials, 264 const struct TALER_HashBlindedPlanchetsP *selected_h, 265 const struct TALER_HashBlindedPlanchetsP *h_planchets, 266 const struct TALER_BlindingMasterSeedP *blinding_seed, 267 bool age_proof_required, 268 uint8_t max_age, 269 uint8_t noreveal_index, 270 const struct TALER_ReservePublicKeyP *reserve_pub, 271 const struct TALER_ReserveSignatureP *reserve_sig, 272 struct GNUNET_TIME_Timestamp execution_date, 273 const struct TALER_Amount *amount_with_fee) 274 { 275 struct WithdrawContext *ctx = cls; 276 277 (void) rowid; 278 (void) selected_h; 279 (void) age_proof_required; 280 (void) max_age; 281 (void) noreveal_index; 282 (void) reserve_pub; 283 (void) reserve_sig; 284 (void) execution_date; 285 ctx->total++; 286 if ( (NULL != ctx->planchets_h) && 287 (0 == GNUNET_memcmp (h_planchets, 288 ctx->planchets_h)) ) 289 { 290 ctx->matched++; 291 ctx->amount = *amount_with_fee; 292 ctx->num_denom_serials = num_denom_serials; 293 if (0 < num_denom_serials) 294 ctx->denom_serial = denom_serials[0]; 295 ctx->have_seed = (NULL != blinding_seed); 296 } 297 if ( (0 != ctx->stop_after) && 298 (ctx->total >= ctx->stop_after) ) 299 return GNUNET_SYSERR; 300 return GNUNET_OK; 301 } 302 303 304 /** 305 * Closure for #amount_cb(). 306 */ 307 struct AmountContext 308 { 309 /** 310 * How many amounts did the callback see? 311 */ 312 unsigned int total; 313 314 /** 315 * Sum of the whole-unit parts of the amounts seen. 316 */ 317 uint64_t value_sum; 318 319 /** 320 * Return this from the callback. 321 */ 322 enum GNUNET_GenericReturnValue ret; 323 }; 324 325 326 /** 327 * Callback for #TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check(). 328 * 329 * @param cls a `struct AmountContext *` 330 * @param amount the withdrawn amount 331 * @param date when it was withdrawn 332 * @return what @e ret of the closure says 333 */ 334 static enum GNUNET_GenericReturnValue 335 amount_cb (void *cls, 336 const struct TALER_Amount *amount, 337 struct GNUNET_TIME_Absolute date) 338 { 339 struct AmountContext *ctx = cls; 340 341 (void) date; 342 ctx->total++; 343 ctx->value_sum += amount->value; 344 return ctx->ret; 345 } 346 347 348 /** 349 * Withdrawing from a reserve that does not exist does nothing. 350 * 351 * @param pg the database context 352 * @return 0 on success 353 */ 354 static int 355 check_unknown_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg) 356 { 357 struct TALER_ReservePublicKeyP reserve_pub; 358 struct TALER_EXCHANGEDB_Withdraw wd; 359 struct WithdrawStatus st; 360 struct TALER_HashBlindedPlanchetsP h; 361 362 TDB_denom (pg, 363 10, 364 "5", 365 "0.1", 366 &denom); 367 TDB_FILL (reserve_pub, 368 1); 369 make_withdraw (1, 370 "5", 371 &reserve_pub, 372 false, 373 &wd); 374 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 375 run_withdraw (pg, 376 &wd, 377 &st), 378 free_withdraw (&wd)); 379 free_withdraw (&wd); 380 FAILIF (0 != TDB_count (pg, 381 "FROM withdraw")); 382 TDB_FILL (h, 383 1); 384 { 385 struct TALER_EXCHANGEDB_Withdraw got; 386 387 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 388 TALER_EXCHANGEDB_get_withdraw (pg, 389 &reserve_pub, 390 &h, 391 &got)); 392 } 393 return 0; 394 } 395 396 397 /** 398 * A reserve without enough money keeps it. 399 * 400 * @param pg the database context 401 * @return 0 on success 402 */ 403 static int 404 check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg) 405 { 406 struct TALER_ReservePublicKeyP reserve_pub; 407 struct TALER_EXCHANGEDB_Withdraw wd; 408 struct WithdrawStatus st; 409 struct TALER_Amount expect = TDB_amount ("1"); 410 411 TDB_account (pg, 412 10, 413 &account); 414 TDB_reserve_in (pg, 415 &account, 416 10, 417 "1", 418 &reserve_pub); 419 make_withdraw (2, 420 "5", 421 &reserve_pub, 422 false, 423 &wd); 424 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 425 run_withdraw (pg, 426 &wd, 427 &st), 428 free_withdraw (&wd)); 429 free_withdraw (&wd); 430 FAILIF (st.balance_ok); 431 FAILIF (! st.age_ok); 432 FAILIF (st.idempotent); 433 FAILIF (0 != TALER_amount_cmp (&st.reserve_balance, 434 &expect)); 435 FAILIF (0 != TDB_count (pg, 436 "FROM withdraw")); 437 return 0; 438 } 439 440 441 /** 442 * A funded reserve is drained and the withdraw is recorded. 443 * 444 * @param pg the database context 445 * @return 0 on success 446 */ 447 static int 448 check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg) 449 { 450 struct TALER_ReservePublicKeyP reserve_pub; 451 struct TALER_EXCHANGEDB_Withdraw wd; 452 struct TALER_EXCHANGEDB_Withdraw got; 453 struct TALER_EXCHANGEDB_Reserve reserve; 454 struct WithdrawStatus st; 455 struct TALER_Amount expect_balance = TDB_amount ("5"); 456 struct TALER_Amount expect_left = TDB_amount ("5"); 457 458 TDB_reserve_in (pg, 459 &account, 460 11, 461 "10", 462 &reserve_pub); 463 make_withdraw (11, 464 "5", 465 &reserve_pub, 466 true, 467 &wd); 468 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 469 run_withdraw (pg, 470 &wd, 471 &st), 472 free_withdraw (&wd)); 473 FAILIF_C (! st.balance_ok, 474 free_withdraw (&wd)); 475 FAILIF_C (! st.age_ok, 476 free_withdraw (&wd)); 477 FAILIF_C (st.idempotent, 478 free_withdraw (&wd)); 479 FAILIF_C (st.nonce_reuse, 480 free_withdraw (&wd)); 481 FAILIF_C (1 != TDB_count (pg, 482 "FROM withdraw"), 483 free_withdraw (&wd)); 484 485 /* the reserve was debited */ 486 memset (&reserve, 487 0, 488 sizeof (reserve)); 489 reserve.pub = reserve_pub; 490 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 491 TALER_EXCHANGEDB_get_reserve (pg, 492 &reserve), 493 free_withdraw (&wd)); 494 FAILIF_C (0 != TALER_amount_cmp (&reserve.balance, 495 &expect_left), 496 free_withdraw (&wd)); 497 498 /* the request can be looked up by its reserve and planchet hash */ 499 memset (&got, 500 0, 501 sizeof (got)); 502 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 503 TALER_EXCHANGEDB_get_withdraw (pg, 504 &reserve_pub, 505 &wd.planchets_h, 506 &got), 507 free_withdraw (&wd)); 508 FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee, 509 &expect_balance), 510 free_withdraw (&got); free_withdraw (&wd)); 511 /* ... and names its reserve and its row */ 512 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub, 513 &reserve_pub), 514 free_withdraw (&got); free_withdraw (&wd)); 515 FAILIF_C (0 == got.withdraw_id, 516 free_withdraw (&got); free_withdraw (&wd)); 517 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub, 518 &reserve_pub), 519 free_withdraw (&got); free_withdraw (&wd)); 520 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig, 521 &wd.reserve_sig), 522 free_withdraw (&got); free_withdraw (&wd)); 523 FAILIF_C (1 != got.num_coins, 524 free_withdraw (&got); free_withdraw (&wd)); 525 FAILIF_C (denom.serial != got.denom_serials[0], 526 free_withdraw (&got); free_withdraw (&wd)); 527 FAILIF_C (got.no_blinding_seed, 528 free_withdraw (&got); free_withdraw (&wd)); 529 free_withdraw (&got); 530 531 /* the planchet hash is only unique per reserve: another reserve does 532 not see this request */ 533 { 534 struct TALER_ReservePublicKeyP other_pub; 535 536 TDB_FILL (other_pub, 537 99); 538 memset (&got, 539 0, 540 sizeof (got)); 541 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 542 TALER_EXCHANGEDB_get_withdraw (pg, 543 &other_pub, 544 &wd.planchets_h, 545 &got), 546 free_withdraw (&wd)); 547 } 548 549 /* a replay of the same request is idempotent and does not debit again */ 550 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 551 run_withdraw (pg, 552 &wd, 553 &st), 554 free_withdraw (&wd)); 555 FAILIF_C (! st.idempotent, 556 free_withdraw (&wd)); 557 FAILIF_C (1 != TDB_count (pg, 558 "FROM withdraw"), 559 free_withdraw (&wd)); 560 memset (&reserve, 561 0, 562 sizeof (reserve)); 563 reserve.pub = reserve_pub; 564 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 565 TALER_EXCHANGEDB_get_reserve (pg, 566 &reserve), 567 free_withdraw (&wd)); 568 FAILIF_C (0 != TALER_amount_cmp (&reserve.balance, 569 &expect_left), 570 free_withdraw (&wd)); 571 free_withdraw (&wd); 572 return 0; 573 } 574 575 576 /** 577 * Reusing a blinding seed for a different withdraw is refused, and the 578 * reserve is left debited -- the caller has to roll back. 579 * 580 * @param pg the database context 581 * @return 0 on success 582 */ 583 static int 584 check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg) 585 { 586 struct TALER_ReservePublicKeyP reserve_pub; 587 struct TALER_EXCHANGEDB_Withdraw wd; 588 struct WithdrawStatus st; 589 590 TDB_reserve_in (pg, 591 &account, 592 12, 593 "10", 594 &reserve_pub); 595 /* a fresh planchet hash, but the blinding seed of check_withdraw() */ 596 make_withdraw (13, 597 "5", 598 &reserve_pub, 599 true, 600 &wd); 601 TDB_fill (&wd.blinding_seed, 602 sizeof (wd.blinding_seed), 603 11); 604 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 605 run_withdraw (pg, 606 &wd, 607 &st), 608 free_withdraw (&wd)); 609 FAILIF_C (! st.nonce_reuse, 610 free_withdraw (&wd)); 611 FAILIF_C (st.idempotent, 612 free_withdraw (&wd)); 613 free_withdraw (&wd); 614 /* No withdraw row was written... */ 615 FAILIF (1 != TDB_count (pg, 616 "FROM withdraw")); 617 /* ...but the reserve was debited before the seed was checked, so the 618 caller has to roll the transaction back. This is what the check is 619 really about: the function is not safe to call outside a transaction. */ 620 { 621 struct TALER_EXCHANGEDB_Reserve reserve; 622 struct TALER_Amount debited = TDB_amount ("5"); 623 624 memset (&reserve, 625 0, 626 sizeof (reserve)); 627 reserve.pub = reserve_pub; 628 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 629 TALER_EXCHANGEDB_get_reserve (pg, 630 &reserve)); 631 FAILIF (0 != TALER_amount_cmp (&reserve.balance, 632 &debited)); 633 } 634 return 0; 635 } 636 637 638 /** 639 * A reserve with a birthday refuses a withdraw that commits to too high 640 * an age. 641 * 642 * @param pg the database context 643 * @return 0 on success 644 */ 645 static int 646 check_age_restriction (struct TALER_EXCHANGEDB_PostgresContext *pg) 647 { 648 struct TALER_ReservePublicKeyP reserve_pub; 649 struct TALER_EXCHANGEDB_Withdraw wd; 650 struct WithdrawStatus st; 651 char *hex; 652 653 TDB_reserve_in (pg, 654 &account, 655 14, 656 "10", 657 &reserve_pub); 658 /* born 20000 days after the epoch, i.e. in 2024 */ 659 hex = TDB_hex (&reserve_pub, 660 sizeof (reserve_pub)); 661 FAILIF_C (GNUNET_OK != 662 TDB_exec (pg, 663 "UPDATE reserves" 664 " SET birthday=20000" 665 " WHERE reserve_pub=decode('%s','hex');", 666 hex), 667 GNUNET_free (hex)); 668 GNUNET_free (hex); 669 670 make_withdraw (14, 671 "5", 672 &reserve_pub, 673 false, 674 &wd); 675 /* no age commitment at all, from a reserve that has a birthday */ 676 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 677 run_withdraw (pg, 678 &wd, 679 &st), 680 free_withdraw (&wd)); 681 free_withdraw (&wd); 682 FAILIF (st.age_ok); 683 FAILIF (20000 != st.reserve_birthday); 684 FAILIF (1 != TDB_count (pg, 685 "FROM withdraw")); 686 return 0; 687 } 688 689 690 /** 691 * The iterators see the withdraw, and the KYC view attributes it to the 692 * account that funded the reserve. 693 * 694 * @param pg the database context 695 * @return 0 on success 696 */ 697 static int 698 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 699 { 700 struct TALER_HashBlindedPlanchetsP h; 701 struct WithdrawContext ctx; 702 struct AmountContext actx; 703 704 TDB_FILL (h, 705 11); 706 memset (&ctx, 707 0, 708 sizeof (ctx)); 709 ctx.planchets_h = &h; 710 FAILIF (1 != 711 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 712 0, 713 &withdraw_cb, 714 &ctx)); 715 FAILIF (1 != ctx.matched); 716 FAILIF (1 != ctx.num_denom_serials); 717 FAILIF (denom.serial != ctx.denom_serial); 718 FAILIF (! ctx.have_seed); 719 720 memset (&ctx, 721 0, 722 sizeof (ctx)); 723 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 724 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 725 1000, 726 &withdraw_cb, 727 &ctx)); 728 FAILIF (0 != ctx.total); 729 730 memset (&ctx, 731 0, 732 sizeof (ctx)); 733 ctx.stop_after = 1; 734 FAILIF (1 != 735 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 736 0, 737 &withdraw_cb, 738 &ctx)); 739 FAILIF (1 != ctx.total); 740 741 /* the KYC view groups by the account that funded the reserve */ 742 memset (&actx, 743 0, 744 sizeof (actx)); 745 actx.ret = GNUNET_OK; 746 FAILIF (1 != 747 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 748 pg, 749 &account.h_normalized, 750 GNUNET_TIME_UNIT_ZERO_ABS, 751 &amount_cb, 752 &actx)); 753 FAILIF (5 != actx.value_sum); 754 755 /* an account nobody withdrew against has nothing */ 756 { 757 struct TALER_NormalizedPaytoHashP other; 758 759 TDB_FILL (other, 760 77); 761 memset (&actx, 762 0, 763 sizeof (actx)); 764 actx.ret = GNUNET_OK; 765 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 766 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 767 pg, 768 &other, 769 GNUNET_TIME_UNIT_ZERO_ABS, 770 &amount_cb, 771 &actx)); 772 FAILIF (0 != actx.total); 773 } 774 775 /* a time limit in the future hides everything */ 776 memset (&actx, 777 0, 778 sizeof (actx)); 779 actx.ret = GNUNET_OK; 780 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 781 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 782 pg, 783 &account.h_normalized, 784 GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS), 785 &amount_cb, 786 &actx)); 787 FAILIF (0 != actx.total); 788 return 0; 789 } 790 791 792 /** 793 * The checks to run, in order. 794 */ 795 static const struct TDB_Test tests[] = { 796 { "withdraw-unknown-reserve", 797 &check_unknown_reserve }, 798 { "withdraw-insufficient-balance", 799 &check_insufficient_balance }, 800 { "withdraw-withdraw", 801 &check_withdraw }, 802 { "withdraw-nonce-reuse", 803 &check_nonce_reuse }, 804 { "withdraw-age-restriction", 805 &check_age_restriction }, 806 { "withdraw-iterate", 807 &check_iterate }, 808 { NULL, NULL } 809 }; 810 811 812 int 813 main (int argc, 814 char *const *argv) 815 { 816 int ret; 817 818 ret = TDB_main (argc, 819 argv, 820 "test-withdraw", 821 "Tests for the exchangedb `withdraw' table", 822 tests); 823 TDB_account_free (&account); 824 TDB_denom_free (&denom); 825 return ret; 826 } 827 828 829 /* end of test_withdraw.c */