test_withdraw.c (22857B)
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 &h, 390 &got)); 391 } 392 return 0; 393 } 394 395 396 /** 397 * A reserve without enough money keeps it. 398 * 399 * @param pg the database context 400 * @return 0 on success 401 */ 402 static int 403 check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg) 404 { 405 struct TALER_ReservePublicKeyP reserve_pub; 406 struct TALER_EXCHANGEDB_Withdraw wd; 407 struct WithdrawStatus st; 408 struct TALER_Amount expect = TDB_amount ("1"); 409 410 TDB_account (pg, 411 10, 412 &account); 413 TDB_reserve_in (pg, 414 &account, 415 10, 416 "1", 417 &reserve_pub); 418 make_withdraw (2, 419 "5", 420 &reserve_pub, 421 false, 422 &wd); 423 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 424 run_withdraw (pg, 425 &wd, 426 &st), 427 free_withdraw (&wd)); 428 free_withdraw (&wd); 429 FAILIF (st.balance_ok); 430 FAILIF (! st.age_ok); 431 FAILIF (st.idempotent); 432 FAILIF (0 != TALER_amount_cmp (&st.reserve_balance, 433 &expect)); 434 FAILIF (0 != TDB_count (pg, 435 "FROM withdraw")); 436 return 0; 437 } 438 439 440 /** 441 * A funded reserve is drained and the withdraw is recorded. 442 * 443 * @param pg the database context 444 * @return 0 on success 445 */ 446 static int 447 check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg) 448 { 449 struct TALER_ReservePublicKeyP reserve_pub; 450 struct TALER_EXCHANGEDB_Withdraw wd; 451 struct TALER_EXCHANGEDB_Withdraw got; 452 struct TALER_EXCHANGEDB_Reserve reserve; 453 struct WithdrawStatus st; 454 struct TALER_Amount expect_balance = TDB_amount ("5"); 455 struct TALER_Amount expect_left = TDB_amount ("5"); 456 457 TDB_reserve_in (pg, 458 &account, 459 11, 460 "10", 461 &reserve_pub); 462 make_withdraw (11, 463 "5", 464 &reserve_pub, 465 true, 466 &wd); 467 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 468 run_withdraw (pg, 469 &wd, 470 &st), 471 free_withdraw (&wd)); 472 FAILIF_C (! st.balance_ok, 473 free_withdraw (&wd)); 474 FAILIF_C (! st.age_ok, 475 free_withdraw (&wd)); 476 FAILIF_C (st.idempotent, 477 free_withdraw (&wd)); 478 FAILIF_C (st.nonce_reuse, 479 free_withdraw (&wd)); 480 FAILIF_C (1 != TDB_count (pg, 481 "FROM withdraw"), 482 free_withdraw (&wd)); 483 484 /* the reserve was debited */ 485 memset (&reserve, 486 0, 487 sizeof (reserve)); 488 reserve.pub = reserve_pub; 489 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 490 TALER_EXCHANGEDB_get_reserve (pg, 491 &reserve), 492 free_withdraw (&wd)); 493 FAILIF_C (0 != TALER_amount_cmp (&reserve.balance, 494 &expect_left), 495 free_withdraw (&wd)); 496 497 /* the request can be looked up by its planchet hash */ 498 memset (&got, 499 0, 500 sizeof (got)); 501 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 502 TALER_EXCHANGEDB_get_withdraw (pg, 503 &wd.planchets_h, 504 &got), 505 free_withdraw (&wd)); 506 FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee, 507 &expect_balance), 508 free_withdraw (&got); free_withdraw (&wd)); 509 /* ... and names its reserve and its row */ 510 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub, 511 &reserve_pub), 512 free_withdraw (&got); free_withdraw (&wd)); 513 FAILIF_C (0 == got.withdraw_id, 514 free_withdraw (&got); free_withdraw (&wd)); 515 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub, 516 &reserve_pub), 517 free_withdraw (&got); free_withdraw (&wd)); 518 FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig, 519 &wd.reserve_sig), 520 free_withdraw (&got); free_withdraw (&wd)); 521 FAILIF_C (1 != got.num_coins, 522 free_withdraw (&got); free_withdraw (&wd)); 523 FAILIF_C (denom.serial != got.denom_serials[0], 524 free_withdraw (&got); free_withdraw (&wd)); 525 FAILIF_C (got.no_blinding_seed, 526 free_withdraw (&got); free_withdraw (&wd)); 527 free_withdraw (&got); 528 529 /* a replay of the same request is idempotent and does not debit again */ 530 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 531 run_withdraw (pg, 532 &wd, 533 &st), 534 free_withdraw (&wd)); 535 FAILIF_C (! st.idempotent, 536 free_withdraw (&wd)); 537 FAILIF_C (1 != TDB_count (pg, 538 "FROM withdraw"), 539 free_withdraw (&wd)); 540 memset (&reserve, 541 0, 542 sizeof (reserve)); 543 reserve.pub = reserve_pub; 544 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 545 TALER_EXCHANGEDB_get_reserve (pg, 546 &reserve), 547 free_withdraw (&wd)); 548 FAILIF_C (0 != TALER_amount_cmp (&reserve.balance, 549 &expect_left), 550 free_withdraw (&wd)); 551 free_withdraw (&wd); 552 return 0; 553 } 554 555 556 /** 557 * Reusing a blinding seed for a different withdraw is refused, and the 558 * reserve is left debited -- the caller has to roll back. 559 * 560 * @param pg the database context 561 * @return 0 on success 562 */ 563 static int 564 check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg) 565 { 566 struct TALER_ReservePublicKeyP reserve_pub; 567 struct TALER_EXCHANGEDB_Withdraw wd; 568 struct WithdrawStatus st; 569 570 TDB_reserve_in (pg, 571 &account, 572 12, 573 "10", 574 &reserve_pub); 575 /* a fresh planchet hash, but the blinding seed of check_withdraw() */ 576 make_withdraw (13, 577 "5", 578 &reserve_pub, 579 true, 580 &wd); 581 TDB_fill (&wd.blinding_seed, 582 sizeof (wd.blinding_seed), 583 11); 584 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 585 run_withdraw (pg, 586 &wd, 587 &st), 588 free_withdraw (&wd)); 589 FAILIF_C (! st.nonce_reuse, 590 free_withdraw (&wd)); 591 FAILIF_C (st.idempotent, 592 free_withdraw (&wd)); 593 free_withdraw (&wd); 594 /* No withdraw row was written... */ 595 FAILIF (1 != TDB_count (pg, 596 "FROM withdraw")); 597 /* ...but the reserve was debited before the seed was checked, so the 598 caller has to roll the transaction back. This is what the check is 599 really about: the function is not safe to call outside a transaction. */ 600 { 601 struct TALER_EXCHANGEDB_Reserve reserve; 602 struct TALER_Amount debited = TDB_amount ("5"); 603 604 memset (&reserve, 605 0, 606 sizeof (reserve)); 607 reserve.pub = reserve_pub; 608 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 609 TALER_EXCHANGEDB_get_reserve (pg, 610 &reserve)); 611 FAILIF (0 != TALER_amount_cmp (&reserve.balance, 612 &debited)); 613 } 614 return 0; 615 } 616 617 618 /** 619 * A reserve with a birthday refuses a withdraw that commits to too high 620 * an age. 621 * 622 * @param pg the database context 623 * @return 0 on success 624 */ 625 static int 626 check_age_restriction (struct TALER_EXCHANGEDB_PostgresContext *pg) 627 { 628 struct TALER_ReservePublicKeyP reserve_pub; 629 struct TALER_EXCHANGEDB_Withdraw wd; 630 struct WithdrawStatus st; 631 char *hex; 632 633 TDB_reserve_in (pg, 634 &account, 635 14, 636 "10", 637 &reserve_pub); 638 /* born 20000 days after the epoch, i.e. in 2024 */ 639 hex = TDB_hex (&reserve_pub, 640 sizeof (reserve_pub)); 641 FAILIF_C (GNUNET_OK != 642 TDB_exec (pg, 643 "UPDATE reserves" 644 " SET birthday=20000" 645 " WHERE reserve_pub=decode('%s','hex');", 646 hex), 647 GNUNET_free (hex)); 648 GNUNET_free (hex); 649 650 make_withdraw (14, 651 "5", 652 &reserve_pub, 653 false, 654 &wd); 655 /* no age commitment at all, from a reserve that has a birthday */ 656 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 657 run_withdraw (pg, 658 &wd, 659 &st), 660 free_withdraw (&wd)); 661 free_withdraw (&wd); 662 FAILIF (st.age_ok); 663 FAILIF (20000 != st.reserve_birthday); 664 FAILIF (1 != TDB_count (pg, 665 "FROM withdraw")); 666 return 0; 667 } 668 669 670 /** 671 * The iterators see the withdraw, and the KYC view attributes it to the 672 * account that funded the reserve. 673 * 674 * @param pg the database context 675 * @return 0 on success 676 */ 677 static int 678 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 679 { 680 struct TALER_HashBlindedPlanchetsP h; 681 struct WithdrawContext ctx; 682 struct AmountContext actx; 683 684 TDB_FILL (h, 685 11); 686 memset (&ctx, 687 0, 688 sizeof (ctx)); 689 ctx.planchets_h = &h; 690 FAILIF (1 != 691 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 692 0, 693 &withdraw_cb, 694 &ctx)); 695 FAILIF (1 != ctx.matched); 696 FAILIF (1 != ctx.num_denom_serials); 697 FAILIF (denom.serial != ctx.denom_serial); 698 FAILIF (! ctx.have_seed); 699 700 memset (&ctx, 701 0, 702 sizeof (ctx)); 703 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 704 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 705 1000, 706 &withdraw_cb, 707 &ctx)); 708 FAILIF (0 != ctx.total); 709 710 memset (&ctx, 711 0, 712 sizeof (ctx)); 713 ctx.stop_after = 1; 714 FAILIF (1 != 715 TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg, 716 0, 717 &withdraw_cb, 718 &ctx)); 719 FAILIF (1 != ctx.total); 720 721 /* the KYC view groups by the account that funded the reserve */ 722 memset (&actx, 723 0, 724 sizeof (actx)); 725 actx.ret = GNUNET_OK; 726 FAILIF (1 != 727 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 728 pg, 729 &account.h_normalized, 730 GNUNET_TIME_UNIT_ZERO_ABS, 731 &amount_cb, 732 &actx)); 733 FAILIF (5 != actx.value_sum); 734 735 /* an account nobody withdrew against has nothing */ 736 { 737 struct TALER_NormalizedPaytoHashP other; 738 739 TDB_FILL (other, 740 77); 741 memset (&actx, 742 0, 743 sizeof (actx)); 744 actx.ret = GNUNET_OK; 745 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 746 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 747 pg, 748 &other, 749 GNUNET_TIME_UNIT_ZERO_ABS, 750 &amount_cb, 751 &actx)); 752 FAILIF (0 != actx.total); 753 } 754 755 /* a time limit in the future hides everything */ 756 memset (&actx, 757 0, 758 sizeof (actx)); 759 actx.ret = GNUNET_OK; 760 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 761 TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check ( 762 pg, 763 &account.h_normalized, 764 GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS), 765 &amount_cb, 766 &actx)); 767 FAILIF (0 != actx.total); 768 return 0; 769 } 770 771 772 /** 773 * The checks to run, in order. 774 */ 775 static const struct TDB_Test tests[] = { 776 { "withdraw-unknown-reserve", 777 &check_unknown_reserve }, 778 { "withdraw-insufficient-balance", 779 &check_insufficient_balance }, 780 { "withdraw-withdraw", 781 &check_withdraw }, 782 { "withdraw-nonce-reuse", 783 &check_nonce_reuse }, 784 { "withdraw-age-restriction", 785 &check_age_restriction }, 786 { "withdraw-iterate", 787 &check_iterate }, 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-withdraw", 801 "Tests for the exchangedb `withdraw' table", 802 tests); 803 TDB_account_free (&account); 804 TDB_denom_free (&denom); 805 return ret; 806 } 807 808 809 /* end of test_withdraw.c */