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-10-29 19:05:17 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              :                                            &reserve_pub,
     390              :                                            &h,
     391              :                                            &got));
     392              :   }
     393            1 :   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            1 : 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            1 :   struct TALER_Amount expect = TDB_amount ("1");
     410              : 
     411            1 :   TDB_account (pg,
     412              :                10,
     413              :                &account);
     414            1 :   TDB_reserve_in (pg,
     415              :                   &account,
     416              :                   10,
     417              :                   "1",
     418              :                   &reserve_pub);
     419            1 :   make_withdraw (2,
     420              :                  "5",
     421              :                  &reserve_pub,
     422              :                  false,
     423              :                  &wd);
     424            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     425              :             run_withdraw (pg,
     426              :                           &wd,
     427              :                           &st),
     428              :             free_withdraw (&wd));
     429            1 :   free_withdraw (&wd);
     430            1 :   FAILIF (st.balance_ok);
     431            1 :   FAILIF (! st.age_ok);
     432            1 :   FAILIF (st.idempotent);
     433            1 :   FAILIF (0 != TALER_amount_cmp (&st.reserve_balance,
     434              :                                  &expect));
     435            1 :   FAILIF (0 != TDB_count (pg,
     436              :                           "FROM withdraw"));
     437            1 :   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            1 : 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            1 :   struct TALER_Amount expect_balance = TDB_amount ("5");
     456            1 :   struct TALER_Amount expect_left = TDB_amount ("5");
     457              : 
     458            1 :   TDB_reserve_in (pg,
     459              :                   &account,
     460              :                   11,
     461              :                   "10",
     462              :                   &reserve_pub);
     463            1 :   make_withdraw (11,
     464              :                  "5",
     465              :                  &reserve_pub,
     466              :                  true,
     467              :                  &wd);
     468            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     469              :             run_withdraw (pg,
     470              :                           &wd,
     471              :                           &st),
     472              :             free_withdraw (&wd));
     473            1 :   FAILIF_C (! st.balance_ok,
     474              :             free_withdraw (&wd));
     475            1 :   FAILIF_C (! st.age_ok,
     476              :             free_withdraw (&wd));
     477            1 :   FAILIF_C (st.idempotent,
     478              :             free_withdraw (&wd));
     479            1 :   FAILIF_C (st.nonce_reuse,
     480              :             free_withdraw (&wd));
     481            1 :   FAILIF_C (1 != TDB_count (pg,
     482              :                             "FROM withdraw"),
     483              :             free_withdraw (&wd));
     484              : 
     485              :   /* the reserve was debited */
     486            1 :   memset (&reserve,
     487              :           0,
     488              :           sizeof (reserve));
     489            1 :   reserve.pub = reserve_pub;
     490            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     491              :             TALER_EXCHANGEDB_get_reserve (pg,
     492              :                                           &reserve),
     493              :             free_withdraw (&wd));
     494            1 :   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            1 :   memset (&got,
     500              :           0,
     501              :           sizeof (got));
     502            1 :   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            1 :   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            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
     513              :                                 &reserve_pub),
     514              :             free_withdraw (&got); free_withdraw (&wd));
     515            1 :   FAILIF_C (0 == got.withdraw_id,
     516              :             free_withdraw (&got); free_withdraw (&wd));
     517            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
     518              :                                 &reserve_pub),
     519              :             free_withdraw (&got); free_withdraw (&wd));
     520            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig,
     521              :                                 &wd.reserve_sig),
     522              :             free_withdraw (&got); free_withdraw (&wd));
     523            1 :   FAILIF_C (1 != got.num_coins,
     524              :             free_withdraw (&got); free_withdraw (&wd));
     525            1 :   FAILIF_C (denom.serial != got.denom_serials[0],
     526              :             free_withdraw (&got); free_withdraw (&wd));
     527            1 :   FAILIF_C (got.no_blinding_seed,
     528              :             free_withdraw (&got); free_withdraw (&wd));
     529            1 :   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            1 :     TDB_FILL (other_pub,
     537              :               99);
     538            1 :     memset (&got,
     539              :             0,
     540              :             sizeof (got));
     541            1 :     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            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     551              :             run_withdraw (pg,
     552              :                           &wd,
     553              :                           &st),
     554              :             free_withdraw (&wd));
     555            1 :   FAILIF_C (! st.idempotent,
     556              :             free_withdraw (&wd));
     557            1 :   FAILIF_C (1 != TDB_count (pg,
     558              :                             "FROM withdraw"),
     559              :             free_withdraw (&wd));
     560            1 :   memset (&reserve,
     561              :           0,
     562              :           sizeof (reserve));
     563            1 :   reserve.pub = reserve_pub;
     564            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     565              :             TALER_EXCHANGEDB_get_reserve (pg,
     566              :                                           &reserve),
     567              :             free_withdraw (&wd));
     568            1 :   FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
     569              :                                    &expect_left),
     570              :             free_withdraw (&wd));
     571            1 :   free_withdraw (&wd);
     572            1 :   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            1 : 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            1 :   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            1 :   make_withdraw (13,
     597              :                  "5",
     598              :                  &reserve_pub,
     599              :                  true,
     600              :                  &wd);
     601            1 :   TDB_fill (&wd.blinding_seed,
     602              :             sizeof (wd.blinding_seed),
     603              :             11);
     604            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     605              :             run_withdraw (pg,
     606              :                           &wd,
     607              :                           &st),
     608              :             free_withdraw (&wd));
     609            1 :   FAILIF_C (! st.nonce_reuse,
     610              :             free_withdraw (&wd));
     611            1 :   FAILIF_C (st.idempotent,
     612              :             free_withdraw (&wd));
     613            1 :   free_withdraw (&wd);
     614              :   /* No withdraw row was written... */
     615            1 :   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            1 :     struct TALER_Amount debited = TDB_amount ("5");
     623              : 
     624            1 :     memset (&reserve,
     625              :             0,
     626              :             sizeof (reserve));
     627            1 :     reserve.pub = reserve_pub;
     628            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     629              :             TALER_EXCHANGEDB_get_reserve (pg,
     630              :                                           &reserve));
     631            1 :     FAILIF (0 != TALER_amount_cmp (&reserve.balance,
     632              :                                    &debited));
     633              :   }
     634            1 :   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            1 : 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            1 :   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            1 :   hex = TDB_hex (&reserve_pub,
     660              :                  sizeof (reserve_pub));
     661            1 :   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            1 :   GNUNET_free (hex);
     669              : 
     670            1 :   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            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     677              :             run_withdraw (pg,
     678              :                           &wd,
     679              :                           &st),
     680              :             free_withdraw (&wd));
     681            1 :   free_withdraw (&wd);
     682            1 :   FAILIF (st.age_ok);
     683            1 :   FAILIF (20000 != st.reserve_birthday);
     684            1 :   FAILIF (1 != TDB_count (pg,
     685              :                           "FROM withdraw"));
     686            1 :   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            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     699              : {
     700              :   struct TALER_HashBlindedPlanchetsP h;
     701              :   struct WithdrawContext ctx;
     702              :   struct AmountContext actx;
     703              : 
     704            1 :   TDB_FILL (h,
     705              :             11);
     706            1 :   memset (&ctx,
     707              :           0,
     708              :           sizeof (ctx));
     709            1 :   ctx.planchets_h = &h;
     710            1 :   FAILIF (1 !=
     711              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     712              :                                                                 0,
     713              :                                                                 &withdraw_cb,
     714              :                                                                 &ctx));
     715            1 :   FAILIF (1 != ctx.matched);
     716            1 :   FAILIF (1 != ctx.num_denom_serials);
     717            1 :   FAILIF (denom.serial != ctx.denom_serial);
     718            1 :   FAILIF (! ctx.have_seed);
     719              : 
     720            1 :   memset (&ctx,
     721              :           0,
     722              :           sizeof (ctx));
     723            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     724              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     725              :                                                                 1000,
     726              :                                                                 &withdraw_cb,
     727              :                                                                 &ctx));
     728            1 :   FAILIF (0 != ctx.total);
     729              : 
     730            1 :   memset (&ctx,
     731              :           0,
     732              :           sizeof (ctx));
     733            1 :   ctx.stop_after = 1;
     734            1 :   FAILIF (1 !=
     735              :           TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
     736              :                                                                 0,
     737              :                                                                 &withdraw_cb,
     738              :                                                                 &ctx));
     739            1 :   FAILIF (1 != ctx.total);
     740              : 
     741              :   /* the KYC view groups by the account that funded the reserve */
     742            1 :   memset (&actx,
     743              :           0,
     744              :           sizeof (actx));
     745            1 :   actx.ret = GNUNET_OK;
     746            1 :   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            1 :   FAILIF (5 != actx.value_sum);
     754              : 
     755              :   /* an account nobody withdrew against has nothing */
     756              :   {
     757              :     struct TALER_NormalizedPaytoHashP other;
     758              : 
     759            1 :     TDB_FILL (other,
     760              :               77);
     761            1 :     memset (&actx,
     762              :             0,
     763              :             sizeof (actx));
     764            1 :     actx.ret = GNUNET_OK;
     765            1 :     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            1 :     FAILIF (0 != actx.total);
     773              :   }
     774              : 
     775              :   /* a time limit in the future hides everything */
     776            1 :   memset (&actx,
     777              :           0,
     778              :           sizeof (actx));
     779            1 :   actx.ret = GNUNET_OK;
     780            1 :   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            1 :   FAILIF (0 != actx.total);
     788            1 :   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            1 : main (int argc,
     814              :       char *const *argv)
     815              : {
     816              :   int ret;
     817              : 
     818            1 :   ret = TDB_main (argc,
     819              :                   argv,
     820              :                   "test-withdraw",
     821              :                   "Tests for the exchangedb `withdraw' table",
     822              :                   tests);
     823            1 :   TDB_account_free (&account);
     824            1 :   TDB_denom_free (&denom);
     825            1 :   return ret;
     826              : }
     827              : 
     828              : 
     829              : /* end of test_withdraw.c */
        

Generated by: LCOV version 2.0-1