LCOV - code coverage report
Current view: top level - exchangedb - test_withdraw.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 166 166
Test Date: 2026-10-10 21:00:14 Functions: 100.0 % 12 12

            Line data    Source code
       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            5 : 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            5 :   memset (wd,
      69              :           0,
      70              :           sizeof (*wd));
      71            5 :   wd->amount_with_fee = TDB_amount (amount);
      72            5 :   wd->age_proof_required = false;
      73            5 :   wd->reserve_pub = *reserve_pub;
      74            5 :   TDB_fill (&wd->planchets_h,
      75              :             sizeof (wd->planchets_h),
      76              :             seed);
      77            5 :   TDB_fill (&wd->reserve_sig,
      78              :             sizeof (wd->reserve_sig),
      79              :             seed);
      80            5 :   wd->num_coins = 1;
      81            5 :   wd->denom_serials = GNUNET_new (uint64_t);
      82            5 :   wd->denom_serials[0] = denom.serial;
      83            5 :   wd->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature);
      84            5 :   TDB_blinded_denom_sig (seed,
      85              :                          &wd->denom_sigs[0]);
      86            5 :   wd->no_blinding_seed = ! with_seed;
      87            5 :   if (with_seed)
      88            2 :     TDB_fill (&wd->blinding_seed,
      89              :               sizeof (wd->blinding_seed),
      90              :               seed);
      91            5 : }
      92              : 
      93              : 
      94              : /**
      95              :  * Release what make_withdraw() allocated.
      96              :  *
      97              :  * @param[in,out] wd request to clean up
      98              :  */
      99              : static void
     100            6 : free_withdraw (struct TALER_EXCHANGEDB_Withdraw *wd)
     101              : {
     102           12 :   for (size_t i = 0; i<wd->num_coins; i++)
     103            6 :     TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
     104            6 :   GNUNET_free (wd->denom_sigs);
     105            6 :   GNUNET_free (wd->denom_serials);
     106            6 : }
     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            6 : run_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
     171              :               const struct TALER_EXCHANGEDB_Withdraw *wd,
     172              :               struct WithdrawStatus *st)
     173              : {
     174            6 :   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     175              : 
     176            6 :   memset (st,
     177              :           0,
     178              :           sizeof (*st));
     179            6 :   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            2 : 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            2 :   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            2 :   ctx->total++;
     286            2 :   if ( (NULL != ctx->planchets_h) &&
     287            1 :        (0 == GNUNET_memcmp (h_planchets,
     288              :                             ctx->planchets_h)) )
     289              :   {
     290            1 :     ctx->matched++;
     291            1 :     ctx->amount = *amount_with_fee;
     292            1 :     ctx->num_denom_serials = num_denom_serials;
     293            1 :     if (0 < num_denom_serials)
     294            1 :       ctx->denom_serial = denom_serials[0];
     295            1 :     ctx->have_seed = (NULL != blinding_seed);
     296              :   }
     297            2 :   if ( (0 != ctx->stop_after) &&
     298            1 :        (ctx->total >= ctx->stop_after) )
     299            1 :     return GNUNET_SYSERR;
     300            1 :   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            1 : amount_cb (void *cls,
     336              :            const struct TALER_Amount *amount,
     337              :            struct GNUNET_TIME_Absolute date)
     338              : {
     339            1 :   struct AmountContext *ctx = cls;
     340              : 
     341              :   (void) date;
     342            1 :   ctx->total++;
     343            1 :   ctx->value_sum += amount->value;
     344            1 :   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            1 : 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            1 :   TDB_denom (pg,
     363              :              10,
     364              :              "5",
     365              :              "0.1",
     366              :              &denom);
     367            1 :   TDB_FILL (reserve_pub,
     368              :             1);
     369            1 :   make_withdraw (1,
     370              :                  "5",
     371              :                  &reserve_pub,
     372              :                  false,
     373              :                  &wd);
     374            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     375              :             run_withdraw (pg,
     376              :                           &wd,
     377              :                           &st),
     378              :             free_withdraw (&wd));
     379            1 :   free_withdraw (&wd);
     380            1 :   FAILIF (0 != TDB_count (pg,
     381              :                           "FROM withdraw"));
     382            1 :   TDB_FILL (h,
     383              :             1);
     384              :   {
     385              :     struct TALER_EXCHANGEDB_Withdraw got;
     386              : 
     387            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     388              :             TALER_EXCHANGEDB_get_withdraw (pg,
     389              :                                            &h,
     390              :                                            &got));
     391              :   }
     392            1 :   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            1 : 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            1 :   struct TALER_Amount expect = TDB_amount ("1");
     409              : 
     410            1 :   TDB_account (pg,
     411              :                10,
     412              :                &account);
     413            1 :   TDB_reserve_in (pg,
     414              :                   &account,
     415              :                   10,
     416              :                   "1",
     417              :                   &reserve_pub);
     418            1 :   make_withdraw (2,
     419              :                  "5",
     420              :                  &reserve_pub,
     421              :                  false,
     422              :                  &wd);
     423            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     424              :             run_withdraw (pg,
     425              :                           &wd,
     426              :                           &st),
     427              :             free_withdraw (&wd));
     428            1 :   free_withdraw (&wd);
     429            1 :   FAILIF (st.balance_ok);
     430            1 :   FAILIF (! st.age_ok);
     431            1 :   FAILIF (st.idempotent);
     432            1 :   FAILIF (0 != TALER_amount_cmp (&st.reserve_balance,
     433              :                                  &expect));
     434            1 :   FAILIF (0 != TDB_count (pg,
     435              :                           "FROM withdraw"));
     436            1 :   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            1 : 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            1 :   struct TALER_Amount expect_balance = TDB_amount ("5");
     455            1 :   struct TALER_Amount expect_left = TDB_amount ("5");
     456              : 
     457            1 :   TDB_reserve_in (pg,
     458              :                   &account,
     459              :                   11,
     460              :                   "10",
     461              :                   &reserve_pub);
     462            1 :   make_withdraw (11,
     463              :                  "5",
     464              :                  &reserve_pub,
     465              :                  true,
     466              :                  &wd);
     467            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     468              :             run_withdraw (pg,
     469              :                           &wd,
     470              :                           &st),
     471              :             free_withdraw (&wd));
     472            1 :   FAILIF_C (! st.balance_ok,
     473              :             free_withdraw (&wd));
     474            1 :   FAILIF_C (! st.age_ok,
     475              :             free_withdraw (&wd));
     476            1 :   FAILIF_C (st.idempotent,
     477              :             free_withdraw (&wd));
     478            1 :   FAILIF_C (st.nonce_reuse,
     479              :             free_withdraw (&wd));
     480            1 :   FAILIF_C (1 != TDB_count (pg,
     481              :                             "FROM withdraw"),
     482              :             free_withdraw (&wd));
     483              : 
     484              :   /* the reserve was debited */
     485            1 :   memset (&reserve,
     486              :           0,
     487              :           sizeof (reserve));
     488            1 :   reserve.pub = reserve_pub;
     489            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     490              :             TALER_EXCHANGEDB_get_reserve (pg,
     491              :                                           &reserve),
     492              :             free_withdraw (&wd));
     493            1 :   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            1 :   memset (&got,
     499              :           0,
     500              :           sizeof (got));
     501            1 :   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            1 :   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            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
     511              :                                 &reserve_pub),
     512              :             free_withdraw (&got); free_withdraw (&wd));
     513            1 :   FAILIF_C (0 == got.withdraw_id,
     514              :             free_withdraw (&got); free_withdraw (&wd));
     515            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
     516              :                                 &reserve_pub),
     517              :             free_withdraw (&got); free_withdraw (&wd));
     518            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig,
     519              :                                 &wd.reserve_sig),
     520              :             free_withdraw (&got); free_withdraw (&wd));
     521            1 :   FAILIF_C (1 != got.num_coins,
     522              :             free_withdraw (&got); free_withdraw (&wd));
     523            1 :   FAILIF_C (denom.serial != got.denom_serials[0],
     524              :             free_withdraw (&got); free_withdraw (&wd));
     525            1 :   FAILIF_C (got.no_blinding_seed,
     526              :             free_withdraw (&got); free_withdraw (&wd));
     527            1 :   free_withdraw (&got);
     528              : 
     529              :   /* a replay of the same request is idempotent and does not debit again */
     530            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     531              :             run_withdraw (pg,
     532              :                           &wd,
     533              :                           &st),
     534              :             free_withdraw (&wd));
     535            1 :   FAILIF_C (! st.idempotent,
     536              :             free_withdraw (&wd));
     537            1 :   FAILIF_C (1 != TDB_count (pg,
     538              :                             "FROM withdraw"),
     539              :             free_withdraw (&wd));
     540            1 :   memset (&reserve,
     541              :           0,
     542              :           sizeof (reserve));
     543            1 :   reserve.pub = reserve_pub;
     544            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     545              :             TALER_EXCHANGEDB_get_reserve (pg,
     546              :                                           &reserve),
     547              :             free_withdraw (&wd));
     548            1 :   FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
     549              :                                    &expect_left),
     550              :             free_withdraw (&wd));
     551            1 :   free_withdraw (&wd);
     552            1 :   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            1 : 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            1 :   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            1 :   make_withdraw (13,
     577              :                  "5",
     578              :                  &reserve_pub,
     579              :                  true,
     580              :                  &wd);
     581            1 :   TDB_fill (&wd.blinding_seed,
     582              :             sizeof (wd.blinding_seed),
     583              :             11);
     584            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     585              :             run_withdraw (pg,
     586              :                           &wd,
     587              :                           &st),
     588              :             free_withdraw (&wd));
     589            1 :   FAILIF_C (! st.nonce_reuse,
     590              :             free_withdraw (&wd));
     591            1 :   FAILIF_C (st.idempotent,
     592              :             free_withdraw (&wd));
     593            1 :   free_withdraw (&wd);
     594              :   /* No withdraw row was written... */
     595            1 :   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            1 :     struct TALER_Amount debited = TDB_amount ("5");
     603              : 
     604            1 :     memset (&reserve,
     605              :             0,
     606              :             sizeof (reserve));
     607            1 :     reserve.pub = reserve_pub;
     608            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     609              :             TALER_EXCHANGEDB_get_reserve (pg,
     610              :                                           &reserve));
     611            1 :     FAILIF (0 != TALER_amount_cmp (&reserve.balance,
     612              :                                    &debited));
     613              :   }
     614            1 :   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            1 : 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            1 :   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            1 :   hex = TDB_hex (&reserve_pub,
     640              :                  sizeof (reserve_pub));
     641            1 :   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            1 :   GNUNET_free (hex);
     649              : 
     650            1 :   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            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     657              :             run_withdraw (pg,
     658              :                           &wd,
     659              :                           &st),
     660              :             free_withdraw (&wd));
     661            1 :   free_withdraw (&wd);
     662            1 :   FAILIF (st.age_ok);
     663            1 :   FAILIF (20000 != st.reserve_birthday);
     664            1 :   FAILIF (1 != TDB_count (pg,
     665              :                           "FROM withdraw"));
     666            1 :   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            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     679              : {
     680              :   struct TALER_HashBlindedPlanchetsP h;
     681              :   struct WithdrawContext ctx;
     682              :   struct AmountContext actx;
     683              : 
     684            1 :   TDB_FILL (h,
     685              :             11);
     686            1 :   memset (&ctx,
     687              :           0,
     688              :           sizeof (ctx));
     689            1 :   ctx.planchets_h = &h;
     690            1 :   FAILIF (1 !=
     691              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     692              :                                                                 0,
     693              :                                                                 &withdraw_cb,
     694              :                                                                 &ctx));
     695            1 :   FAILIF (1 != ctx.matched);
     696            1 :   FAILIF (1 != ctx.num_denom_serials);
     697            1 :   FAILIF (denom.serial != ctx.denom_serial);
     698            1 :   FAILIF (! ctx.have_seed);
     699              : 
     700            1 :   memset (&ctx,
     701              :           0,
     702              :           sizeof (ctx));
     703            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     704              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     705              :                                                                 1000,
     706              :                                                                 &withdraw_cb,
     707              :                                                                 &ctx));
     708            1 :   FAILIF (0 != ctx.total);
     709              : 
     710            1 :   memset (&ctx,
     711              :           0,
     712              :           sizeof (ctx));
     713            1 :   ctx.stop_after = 1;
     714            1 :   FAILIF (1 !=
     715              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     716              :                                                                 0,
     717              :                                                                 &withdraw_cb,
     718              :                                                                 &ctx));
     719            1 :   FAILIF (1 != ctx.total);
     720              : 
     721              :   /* the KYC view groups by the account that funded the reserve */
     722            1 :   memset (&actx,
     723              :           0,
     724              :           sizeof (actx));
     725            1 :   actx.ret = GNUNET_OK;
     726            1 :   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            1 :   FAILIF (5 != actx.value_sum);
     734              : 
     735              :   /* an account nobody withdrew against has nothing */
     736              :   {
     737              :     struct TALER_NormalizedPaytoHashP other;
     738              : 
     739            1 :     TDB_FILL (other,
     740              :               77);
     741            1 :     memset (&actx,
     742              :             0,
     743              :             sizeof (actx));
     744            1 :     actx.ret = GNUNET_OK;
     745            1 :     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            1 :     FAILIF (0 != actx.total);
     753              :   }
     754              : 
     755              :   /* a time limit in the future hides everything */
     756            1 :   memset (&actx,
     757              :           0,
     758              :           sizeof (actx));
     759            1 :   actx.ret = GNUNET_OK;
     760            1 :   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            1 :   FAILIF (0 != actx.total);
     768            1 :   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            1 : main (int argc,
     794              :       char *const *argv)
     795              : {
     796              :   int ret;
     797              : 
     798            1 :   ret = TDB_main (argc,
     799              :                   argv,
     800              :                   "test-withdraw",
     801              :                   "Tests for the exchangedb `withdraw' table",
     802              :                   tests);
     803            1 :   TDB_account_free (&account);
     804            1 :   TDB_denom_free (&denom);
     805            1 :   return ret;
     806              : }
     807              : 
     808              : 
     809              : /* end of test_withdraw.c */
        

Generated by: LCOV version 2.0-1