exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */