LCOV - code coverage report
Current view: top level - exchangedb - test_withdraw.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 169 169
Test Date: 2026-09-11 18:55:36 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_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            5 : 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            5 :   memset (wd,
      71              :           0,
      72              :           sizeof (*wd));
      73            5 :   wd->amount_with_fee = TDB_amount (amount);
      74            5 :   wd->age_proof_required = false;
      75            5 :   wd->reserve_pub = *reserve_pub;
      76            5 :   TDB_fill (&wd->planchets_h,
      77              :             sizeof (wd->planchets_h),
      78              :             seed);
      79            5 :   TDB_fill (&wd->reserve_sig,
      80              :             sizeof (wd->reserve_sig),
      81              :             seed);
      82            5 :   wd->num_coins = 1;
      83            5 :   wd->denom_serials = GNUNET_new (uint64_t);
      84            5 :   wd->denom_serials[0] = denom.serial;
      85            5 :   wd->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature);
      86            5 :   TDB_blinded_denom_sig (seed,
      87              :                          &wd->denom_sigs[0]);
      88            5 :   wd->no_blinding_seed = ! with_seed;
      89            5 :   if (with_seed)
      90            2 :     TDB_fill (&wd->blinding_seed,
      91              :               sizeof (wd->blinding_seed),
      92              :               seed);
      93            5 : }
      94              : 
      95              : 
      96              : /**
      97              :  * Release what make_withdraw() allocated.
      98              :  *
      99              :  * @param[in,out] wd request to clean up
     100              :  */
     101              : static void
     102            6 : free_withdraw (struct TALER_EXCHANGEDB_Withdraw *wd)
     103              : {
     104           12 :   for (size_t i = 0; i<wd->num_coins; i++)
     105            6 :     TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
     106            6 :   GNUNET_free (wd->denom_sigs);
     107            6 :   GNUNET_free (wd->denom_serials);
     108            6 : }
     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            6 : run_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
     173              :               const struct TALER_EXCHANGEDB_Withdraw *wd,
     174              :               struct WithdrawStatus *st)
     175              : {
     176            6 :   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     177              : 
     178            6 :   memset (st,
     179              :           0,
     180              :           sizeof (*st));
     181            6 :   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            2 : 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            2 :   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            2 :   ctx->total++;
     288            2 :   if ( (NULL != ctx->planchets_h) &&
     289            1 :        (0 == GNUNET_memcmp (h_planchets,
     290              :                             ctx->planchets_h)) )
     291              :   {
     292            1 :     ctx->matched++;
     293            1 :     ctx->amount = *amount_with_fee;
     294            1 :     ctx->num_denom_serials = num_denom_serials;
     295            1 :     if (0 < num_denom_serials)
     296            1 :       ctx->denom_serial = denom_serials[0];
     297            1 :     ctx->have_seed = (NULL != blinding_seed);
     298              :   }
     299            2 :   if ( (0 != ctx->stop_after) &&
     300            1 :        (ctx->total >= ctx->stop_after) )
     301            1 :     return GNUNET_SYSERR;
     302            1 :   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            1 : amount_cb (void *cls,
     338              :            const struct TALER_Amount *amount,
     339              :            struct GNUNET_TIME_Absolute date)
     340              : {
     341            1 :   struct AmountContext *ctx = cls;
     342              : 
     343              :   (void) date;
     344            1 :   ctx->total++;
     345            1 :   ctx->value_sum += amount->value;
     346            1 :   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            1 : 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            1 :   TDB_denom (pg,
     366              :              10,
     367              :              "5",
     368              :              "0.1",
     369              :              &denom);
     370            1 :   TDB_FILL (reserve_pub,
     371              :             1);
     372            1 :   make_withdraw (1,
     373              :                  "5",
     374              :                  &reserve_pub,
     375              :                  false,
     376              :                  &wd);
     377            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     378              :             run_withdraw (pg,
     379              :                           &wd,
     380              :                           &st),
     381              :             free_withdraw (&wd));
     382            1 :   free_withdraw (&wd);
     383            1 :   FAILIF (0 != TDB_count (pg,
     384              :                           "FROM withdraw"));
     385            1 :   TDB_FILL (h,
     386              :             1);
     387            1 :   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            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     396              :             TALER_EXCHANGEDB_get_withdraw (pg,
     397              :                                            &h,
     398              :                                            &got));
     399              :   }
     400            1 :   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            1 : 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            1 :   struct TALER_Amount expect = TDB_amount ("1");
     417              : 
     418            1 :   TDB_account (pg,
     419              :                10,
     420              :                &account);
     421            1 :   TDB_reserve_in (pg,
     422              :                   &account,
     423              :                   10,
     424              :                   "1",
     425              :                   &reserve_pub);
     426            1 :   make_withdraw (2,
     427              :                  "5",
     428              :                  &reserve_pub,
     429              :                  false,
     430              :                  &wd);
     431            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     432              :             run_withdraw (pg,
     433              :                           &wd,
     434              :                           &st),
     435              :             free_withdraw (&wd));
     436            1 :   free_withdraw (&wd);
     437            1 :   FAILIF (st.balance_ok);
     438            1 :   FAILIF (! st.age_ok);
     439            1 :   FAILIF (st.idempotent);
     440            1 :   FAILIF (0 != TALER_amount_cmp (&st.reserve_balance,
     441              :                                  &expect));
     442            1 :   FAILIF (0 != TDB_count (pg,
     443              :                           "FROM withdraw"));
     444            1 :   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            1 : 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            1 :   struct TALER_Amount expect_balance = TDB_amount ("5");
     463            1 :   struct TALER_Amount expect_left = TDB_amount ("5");
     464              :   struct TALER_ReservePublicKeyP got_pub;
     465            1 :   uint64_t withdraw_serial_id = 0;
     466              : 
     467            1 :   TDB_reserve_in (pg,
     468              :                   &account,
     469              :                   11,
     470              :                   "10",
     471              :                   &reserve_pub);
     472            1 :   make_withdraw (11,
     473              :                  "5",
     474              :                  &reserve_pub,
     475              :                  true,
     476              :                  &wd);
     477            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     478              :             run_withdraw (pg,
     479              :                           &wd,
     480              :                           &st),
     481              :             free_withdraw (&wd));
     482            1 :   FAILIF_C (! st.balance_ok,
     483              :             free_withdraw (&wd));
     484            1 :   FAILIF_C (! st.age_ok,
     485              :             free_withdraw (&wd));
     486            1 :   FAILIF_C (st.idempotent,
     487              :             free_withdraw (&wd));
     488            1 :   FAILIF_C (st.nonce_reuse,
     489              :             free_withdraw (&wd));
     490            1 :   FAILIF_C (1 != TDB_count (pg,
     491              :                             "FROM withdraw"),
     492              :             free_withdraw (&wd));
     493              : 
     494              :   /* the reserve was debited */
     495            1 :   memset (&reserve,
     496              :           0,
     497              :           sizeof (reserve));
     498            1 :   reserve.pub = reserve_pub;
     499            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     500              :             TALER_EXCHANGEDB_get_reserve (pg,
     501              :                                           &reserve),
     502              :             free_withdraw (&wd));
     503            1 :   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            1 :   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            1 :   FAILIF_C (0 != GNUNET_memcmp (&got_pub,
     515              :                                 &reserve_pub),
     516              :             free_withdraw (&wd));
     517            1 :   FAILIF_C (0 == withdraw_serial_id,
     518              :             free_withdraw (&wd));
     519              : 
     520            1 :   memset (&got,
     521              :           0,
     522              :           sizeof (got));
     523            1 :   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            1 :   FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee,
     529              :                                    &expect_balance),
     530              :             free_withdraw (&got); free_withdraw (&wd));
     531            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
     532              :                                 &reserve_pub),
     533              :             free_withdraw (&got); free_withdraw (&wd));
     534            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig,
     535              :                                 &wd.reserve_sig),
     536              :             free_withdraw (&got); free_withdraw (&wd));
     537            1 :   FAILIF_C (1 != got.num_coins,
     538              :             free_withdraw (&got); free_withdraw (&wd));
     539            1 :   FAILIF_C (denom.serial != got.denom_serials[0],
     540              :             free_withdraw (&got); free_withdraw (&wd));
     541            1 :   FAILIF_C (got.no_blinding_seed,
     542              :             free_withdraw (&got); free_withdraw (&wd));
     543            1 :   free_withdraw (&got);
     544              : 
     545              :   /* a replay of the same request is idempotent and does not debit again */
     546            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     547              :             run_withdraw (pg,
     548              :                           &wd,
     549              :                           &st),
     550              :             free_withdraw (&wd));
     551            1 :   FAILIF_C (! st.idempotent,
     552              :             free_withdraw (&wd));
     553            1 :   FAILIF_C (1 != TDB_count (pg,
     554              :                             "FROM withdraw"),
     555              :             free_withdraw (&wd));
     556            1 :   memset (&reserve,
     557              :           0,
     558              :           sizeof (reserve));
     559            1 :   reserve.pub = reserve_pub;
     560            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     561              :             TALER_EXCHANGEDB_get_reserve (pg,
     562              :                                           &reserve),
     563              :             free_withdraw (&wd));
     564            1 :   FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
     565              :                                    &expect_left),
     566              :             free_withdraw (&wd));
     567            1 :   free_withdraw (&wd);
     568            1 :   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            1 : 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            1 :   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            1 :   make_withdraw (13,
     593              :                  "5",
     594              :                  &reserve_pub,
     595              :                  true,
     596              :                  &wd);
     597            1 :   TDB_fill (&wd.blinding_seed,
     598              :             sizeof (wd.blinding_seed),
     599              :             11);
     600            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     601              :             run_withdraw (pg,
     602              :                           &wd,
     603              :                           &st),
     604              :             free_withdraw (&wd));
     605            1 :   FAILIF_C (! st.nonce_reuse,
     606              :             free_withdraw (&wd));
     607            1 :   FAILIF_C (st.idempotent,
     608              :             free_withdraw (&wd));
     609            1 :   free_withdraw (&wd);
     610              :   /* No withdraw row was written... */
     611            1 :   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            1 :     struct TALER_Amount debited = TDB_amount ("5");
     619              : 
     620            1 :     memset (&reserve,
     621              :             0,
     622              :             sizeof (reserve));
     623            1 :     reserve.pub = reserve_pub;
     624            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     625              :             TALER_EXCHANGEDB_get_reserve (pg,
     626              :                                           &reserve));
     627            1 :     FAILIF (0 != TALER_amount_cmp (&reserve.balance,
     628              :                                    &debited));
     629              :   }
     630            1 :   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            1 : 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            1 :   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            1 :   hex = TDB_hex (&reserve_pub,
     656              :                  sizeof (reserve_pub));
     657            1 :   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            1 :   GNUNET_free (hex);
     665              : 
     666            1 :   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            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     673              :             run_withdraw (pg,
     674              :                           &wd,
     675              :                           &st),
     676              :             free_withdraw (&wd));
     677            1 :   free_withdraw (&wd);
     678            1 :   FAILIF (st.age_ok);
     679            1 :   FAILIF (20000 != st.reserve_birthday);
     680            1 :   FAILIF (1 != TDB_count (pg,
     681              :                           "FROM withdraw"));
     682            1 :   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            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     695              : {
     696              :   struct TALER_HashBlindedPlanchetsP h;
     697              :   struct WithdrawContext ctx;
     698              :   struct AmountContext actx;
     699              : 
     700            1 :   TDB_FILL (h,
     701              :             11);
     702            1 :   memset (&ctx,
     703              :           0,
     704              :           sizeof (ctx));
     705            1 :   ctx.planchets_h = &h;
     706            1 :   FAILIF (1 !=
     707              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     708              :                                                                 0,
     709              :                                                                 &withdraw_cb,
     710              :                                                                 &ctx));
     711            1 :   FAILIF (1 != ctx.matched);
     712            1 :   FAILIF (1 != ctx.num_denom_serials);
     713            1 :   FAILIF (denom.serial != ctx.denom_serial);
     714            1 :   FAILIF (! ctx.have_seed);
     715              : 
     716            1 :   memset (&ctx,
     717              :           0,
     718              :           sizeof (ctx));
     719            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     720              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     721              :                                                                 1000,
     722              :                                                                 &withdraw_cb,
     723              :                                                                 &ctx));
     724            1 :   FAILIF (0 != ctx.total);
     725              : 
     726            1 :   memset (&ctx,
     727              :           0,
     728              :           sizeof (ctx));
     729            1 :   ctx.stop_after = 1;
     730            1 :   FAILIF (1 !=
     731              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     732              :                                                                 0,
     733              :                                                                 &withdraw_cb,
     734              :                                                                 &ctx));
     735            1 :   FAILIF (1 != ctx.total);
     736              : 
     737              :   /* the KYC view groups by the account that funded the reserve */
     738            1 :   memset (&actx,
     739              :           0,
     740              :           sizeof (actx));
     741            1 :   actx.ret = GNUNET_OK;
     742            1 :   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            1 :   FAILIF (5 != actx.value_sum);
     750              : 
     751              :   /* an account nobody withdrew against has nothing */
     752              :   {
     753              :     struct TALER_NormalizedPaytoHashP other;
     754              : 
     755            1 :     TDB_FILL (other,
     756              :               77);
     757            1 :     memset (&actx,
     758              :             0,
     759              :             sizeof (actx));
     760            1 :     actx.ret = GNUNET_OK;
     761            1 :     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            1 :     FAILIF (0 != actx.total);
     769              :   }
     770              : 
     771              :   /* a time limit in the future hides everything */
     772            1 :   memset (&actx,
     773              :           0,
     774              :           sizeof (actx));
     775            1 :   actx.ret = GNUNET_OK;
     776            1 :   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            1 :   FAILIF (0 != actx.total);
     784            1 :   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            1 : main (int argc,
     810              :       char *const *argv)
     811              : {
     812              :   int ret;
     813              : 
     814            1 :   ret = TDB_main (argc,
     815              :                   argv,
     816              :                   "test-withdraw",
     817              :                   "Tests for the exchangedb `withdraw' table",
     818              :                   tests);
     819            1 :   TDB_account_free (&account);
     820            1 :   TDB_denom_free (&denom);
     821            1 :   return ret;
     822              : }
     823              : 
     824              : 
     825              : /* end of test_withdraw.c */
        

Generated by: LCOV version 2.0-1