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