LCOV - code coverage report
Current view: top level - exchangedb - test_refresh.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 167 167
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_refresh.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `refresh`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_refresh(), #TALER_EXCHANGEDB_get_refresh(),
      23              :  * #TALER_EXCHANGEDB_update_to_refresh_revealed(),
      24              :  * #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id().
      25              :  *
      26              :  * `refresh` references `known_coins`, which TDB_coin() creates.  The
      27              :  * do_refresh() answers checked here are: unknown coin, insufficient coin
      28              :  * balance, the zombie requirement, blinding-seed reuse and an idempotent
      29              :  * replay.
      30              :  */
      31              : #include "test_common.h"
      32              : #include "exchange-database/do_refresh.h"
      33              : #include "exchange-database/get_known_coin.h"
      34              : #include "exchange-database/get_refresh.h"
      35              : #include "exchange-database/iterate_refreshes_above_serial_id.h"
      36              : #include "exchange-database/update_to_refresh_revealed.h"
      37              : 
      38              : 
      39              : /**
      40              :  * Denomination the checks melt.
      41              :  */
      42              : static struct TDB_Denom denom;
      43              : 
      44              : 
      45              : /**
      46              :  * Fill in a refresh (melt) request for one fresh coin.
      47              :  *
      48              :  * @param seed seed for the commitment, signatures and seeds
      49              :  * @param amount amount to melt, e.g. "1"
      50              :  * @param coin coin being melted
      51              :  * @param with_seed true to pass a blinding seed and a CS R value
      52              :  * @param[out] rf set to the request; release with free_refresh()
      53              :  */
      54              : static void
      55            6 : make_refresh (uint32_t seed,
      56              :               const char *amount,
      57              :               const struct TALER_CoinPublicInfo *coin,
      58              :               bool with_seed,
      59              :               struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf)
      60              : {
      61            6 :   memset (rf,
      62              :           0,
      63              :           sizeof (*rf));
      64            6 :   rf->coin.coin_pub = coin->coin_pub;
      65            6 :   rf->coin.denom_pub_hash = coin->denom_pub_hash;
      66            6 :   rf->coin.no_age_commitment = coin->no_age_commitment;
      67            6 :   TDB_fill (&rf->coin_sig,
      68              :             sizeof (rf->coin_sig),
      69              :             seed);
      70            6 :   TDB_fill (&rf->rc,
      71              :             sizeof (rf->rc),
      72              :             seed);
      73            6 :   TDB_fill (&rf->refresh_seed,
      74              :             sizeof (rf->refresh_seed),
      75              :             seed);
      76            6 :   TDB_fill (&rf->planchets_h,
      77              :             sizeof (rf->planchets_h),
      78              :             seed);
      79            6 :   TDB_fill (&rf->selected_h,
      80              :             sizeof (rf->selected_h),
      81              :             seed + 1);
      82            6 :   rf->amount_with_fee = TDB_amount (amount);
      83            6 :   rf->num_coins = 1;
      84            6 :   rf->denom_serials = GNUNET_new (uint64_t);
      85            6 :   rf->denom_serials[0] = denom.serial;
      86            6 :   rf->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature);
      87            6 :   TDB_blinded_denom_sig (seed,
      88              :                          &rf->denom_sigs[0]);
      89            6 :   rf->noreveal_index = 1;
      90              :   /* v27 refresh: the client provides no transfer public keys */
      91            6 :   rf->is_v27_refresh = true;
      92            6 :   rf->no_blinding_seed = ! with_seed;
      93            6 :   if (with_seed)
      94              :   {
      95            2 :     TDB_fill (&rf->blinding_seed,
      96              :               sizeof (rf->blinding_seed),
      97              :               seed);
      98            2 :     rf->num_cs_r_values = 1;
      99            2 :     rf->cs_r_values = GNUNET_new (struct GNUNET_CRYPTO_CSPublicRPairP);
     100            2 :     TDB_fill (rf->cs_r_values,
     101              :               sizeof (*rf->cs_r_values),
     102              :               seed);
     103            2 :     rf->cs_r_choices = 0;
     104              :   }
     105            6 : }
     106              : 
     107              : 
     108              : /**
     109              :  * Release what make_refresh() allocated.
     110              :  *
     111              :  * @param[in,out] rf request to clean up
     112              :  */
     113              : static void
     114            8 : free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf)
     115              : {
     116           16 :   for (size_t i = 0; i<rf->num_coins; i++)
     117            8 :     TALER_blinded_denom_sig_free (&rf->denom_sigs[i]);
     118            8 :   GNUNET_free (rf->denom_sigs);
     119            8 :   GNUNET_free (rf->denom_serials);
     120            8 :   GNUNET_free (rf->cs_r_values);
     121            8 :   GNUNET_free (rf->transfer_pubs);
     122            8 :   GNUNET_free (rf->denom_pub_hashes);
     123            8 : }
     124              : 
     125              : 
     126              : /**
     127              :  * Outcome flags of a refresh request.
     128              :  */
     129              : struct RefreshStatus
     130              : {
     131              :   /**
     132              :    * Was there already a row for this commitment?
     133              :    */
     134              :   bool found;
     135              : 
     136              :   /**
     137              :    * Did the melt have to be of a zombie coin?
     138              :    */
     139              :   bool zombie_required;
     140              : 
     141              :   /**
     142              :    * Was the blinding seed used before?
     143              :    */
     144              :   bool nonce_reuse;
     145              : 
     146              :   /**
     147              :    * Was the coin balance sufficient?
     148              :    */
     149              :   bool balance_ok;
     150              : 
     151              :   /**
     152              :    * Balance the coin had.
     153              :    */
     154              :   struct TALER_Amount coin_balance;
     155              : 
     156              :   /**
     157              :    * Index the exchange chose not to reveal.
     158              :    */
     159              :   uint32_t noreveal_index;
     160              : };
     161              : 
     162              : 
     163              : /**
     164              :  * Perform a refresh request.
     165              :  *
     166              :  * @param pg the database context
     167              :  * @param rf the request
     168              :  * @param zombie_required whether the coin must be a zombie
     169              :  * @param[out] st set to the outcome
     170              :  * @return transaction status
     171              :  */
     172              : static enum GNUNET_DB_QueryStatus
     173            7 : run_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg,
     174              :              struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
     175              :              bool zombie_required,
     176              :              struct RefreshStatus *st)
     177              : {
     178            7 :   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     179              : 
     180            7 :   memset (st,
     181              :           0,
     182              :           sizeof (*st));
     183            7 :   st->zombie_required = zombie_required;
     184            7 :   return TALER_EXCHANGEDB_do_refresh (pg,
     185              :                                       rf,
     186              :                                       &now,
     187              :                                       &st->found,
     188              :                                       &st->noreveal_index,
     189              :                                       &st->zombie_required,
     190              :                                       &st->nonce_reuse,
     191              :                                       &st->balance_ok,
     192              :                                       &st->coin_balance);
     193              : }
     194              : 
     195              : 
     196              : /**
     197              :  * Melting a coin that is not known does nothing.
     198              :  *
     199              :  * @param pg the database context
     200              :  * @return 0 on success
     201              :  */
     202              : static int
     203            1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     204              : {
     205              :   struct TALER_CoinPublicInfo coin;
     206              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     207              :   struct RefreshStatus st;
     208              : 
     209            1 :   TDB_denom (pg,
     210              :              10,
     211              :              "5",
     212              :              "0.1",
     213              :              &denom);
     214            1 :   memset (&coin,
     215              :           0,
     216              :           sizeof (coin));
     217            1 :   TDB_FILL (coin.coin_pub,
     218              :             1);
     219            1 :   coin.denom_pub_hash = denom.h_denom_pub;
     220            1 :   coin.no_age_commitment = true;
     221            1 :   make_refresh (1,
     222              :                 "1",
     223              :                 &coin,
     224              :                 false,
     225              :                 &rf);
     226            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     227              :             run_refresh (pg,
     228              :                          &rf,
     229              :                          false,
     230              :                          &st),
     231              :             free_refresh (&rf));
     232            1 :   free_refresh (&rf);
     233            1 :   FAILIF (0 != TDB_count (pg,
     234              :                           "FROM refresh"));
     235            1 :   return 0;
     236              : }
     237              : 
     238              : 
     239              : /**
     240              :  * Melting more than the coin is worth does nothing.
     241              :  *
     242              :  * @param pg the database context
     243              :  * @return 0 on success
     244              :  */
     245              : static int
     246            1 : check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
     247              : {
     248              :   struct TALER_CoinPublicInfo coin;
     249              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     250              :   struct RefreshStatus st;
     251            1 :   struct TALER_Amount expect = TDB_amount ("5");
     252              : 
     253            1 :   TDB_coin (pg,
     254              :             &denom,
     255              :             20,
     256              :             &coin,
     257              :             NULL);
     258            1 :   make_refresh (2,
     259              :                 "6",
     260              :                 &coin,
     261              :                 false,
     262              :                 &rf);
     263            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     264              :             run_refresh (pg,
     265              :                          &rf,
     266              :                          false,
     267              :                          &st),
     268              :             free_refresh (&rf); TDB_coin_free (&coin));
     269            1 :   free_refresh (&rf);
     270            1 :   FAILIF_C (st.balance_ok,
     271              :             TDB_coin_free (&coin));
     272            1 :   FAILIF_C (0 != TALER_amount_cmp (&st.coin_balance,
     273              :                                    &expect),
     274              :             TDB_coin_free (&coin));
     275              :   /* the row was written before the balance was checked, so the caller has
     276              :      to roll back -- here the check simply notes that it is there */
     277            1 :   FAILIF_C (1 != TDB_count (pg,
     278              :                             "FROM refresh"),
     279              :             TDB_coin_free (&coin));
     280            1 :   FAILIF_C (GNUNET_OK !=
     281              :             TDB_exec (pg,
     282              :                       "DELETE FROM refresh;"),
     283              :             TDB_coin_free (&coin));
     284            1 :   TDB_coin_free (&coin);
     285            1 :   return 0;
     286              : }
     287              : 
     288              : 
     289              : /**
     290              :  * A melt within the coin's balance is recorded and debits the coin.
     291              :  *
     292              :  * @param pg the database context
     293              :  * @return 0 on success
     294              :  */
     295              : static int
     296            1 : check_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
     297              : {
     298              :   struct TALER_CoinPublicInfo coin;
     299              :   struct TALER_CoinPublicInfo got_coin;
     300              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     301              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got;
     302              :   struct RefreshStatus st;
     303            1 :   struct TALER_Amount expect_amount = TDB_amount ("1");
     304              : 
     305            1 :   TDB_coin (pg,
     306              :             &denom,
     307              :             20,
     308              :             &coin,
     309              :             NULL);
     310            1 :   make_refresh (3,
     311              :                 "1",
     312              :                 &coin,
     313              :                 true,
     314              :                 &rf);
     315            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     316              :             run_refresh (pg,
     317              :                          &rf,
     318              :                          false,
     319              :                          &st),
     320              :             free_refresh (&rf); TDB_coin_free (&coin));
     321            1 :   FAILIF_C (! st.balance_ok,
     322              :             free_refresh (&rf); TDB_coin_free (&coin));
     323            1 :   FAILIF_C (st.found,
     324              :             free_refresh (&rf); TDB_coin_free (&coin));
     325            1 :   FAILIF_C (st.nonce_reuse,
     326              :             free_refresh (&rf); TDB_coin_free (&coin));
     327            1 :   FAILIF_C (1 != TDB_count (pg,
     328              :                             "FROM refresh"),
     329              :             free_refresh (&rf); TDB_coin_free (&coin));
     330              : 
     331              :   /* the melt is on file with everything it was created with */
     332            1 :   memset (&got,
     333              :           0,
     334              :           sizeof (got));
     335            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     336              :             TALER_EXCHANGEDB_get_refresh (pg,
     337              :                                           &rf.rc,
     338              :                                           &got),
     339              :             free_refresh (&rf); TDB_coin_free (&coin));
     340            1 :   FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee,
     341              :                                    &expect_amount),
     342              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     343            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.coin.coin_pub,
     344              :                                 &coin.coin_pub),
     345              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     346            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.coin_sig,
     347              :                                 &rf.coin_sig),
     348              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     349            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.refresh_seed,
     350              :                                 &rf.refresh_seed),
     351              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     352            1 :   FAILIF_C (1 != got.num_coins,
     353              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     354            1 :   FAILIF_C (denom.serial != got.denom_serials[0],
     355              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     356            1 :   FAILIF_C (rf.noreveal_index != got.noreveal_index,
     357              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     358            1 :   FAILIF_C (got.revealed,
     359              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     360            1 :   FAILIF_C (got.no_blinding_seed,
     361              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     362            1 :   FAILIF_C (! got.is_v27_refresh,
     363              :             free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
     364            1 :   free_refresh (&got);
     365              : 
     366              :   /* the coin was debited */
     367            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     368              :             TALER_EXCHANGEDB_get_known_coin (pg,
     369              :                                              &coin.coin_pub,
     370              :                                              &got_coin),
     371              :             free_refresh (&rf); TDB_coin_free (&coin));
     372            1 :   TALER_denom_sig_free (&got_coin.denom_sig);
     373              :   {
     374            1 :     char *hex = TDB_hex (&coin.coin_pub,
     375              :                          sizeof (coin.coin_pub));
     376              : 
     377            1 :     FAILIF_C (1 != TDB_count (pg,
     378              :                               "FROM known_coins"
     379              :                               " WHERE coin_pub=decode('%s','hex')"
     380              :                               "   AND remaining=ROW(4,0)::taler_amount",
     381              :                               hex),
     382              :               GNUNET_free (hex);
     383              :               free_refresh (&rf); TDB_coin_free (&coin));
     384            1 :     GNUNET_free (hex);
     385              :   }
     386              : 
     387              :   /* a replay is idempotent and reports the index we chose */
     388            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     389              :             run_refresh (pg,
     390              :                          &rf,
     391              :                          false,
     392              :                          &st),
     393              :             free_refresh (&rf); TDB_coin_free (&coin));
     394            1 :   FAILIF_C (! st.found,
     395              :             free_refresh (&rf); TDB_coin_free (&coin));
     396            1 :   FAILIF_C (rf.noreveal_index != st.noreveal_index,
     397              :             free_refresh (&rf); TDB_coin_free (&coin));
     398            1 :   FAILIF_C (1 != TDB_count (pg,
     399              :                             "FROM refresh"),
     400              :             free_refresh (&rf); TDB_coin_free (&coin));
     401            1 :   free_refresh (&rf);
     402            1 :   TDB_coin_free (&coin);
     403            1 :   return 0;
     404              : }
     405              : 
     406              : 
     407              : /**
     408              :  * Reusing a blinding seed is refused.
     409              :  *
     410              :  * @param pg the database context
     411              :  * @return 0 on success
     412              :  */
     413              : static int
     414            1 : check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg)
     415              : {
     416              :   struct TALER_CoinPublicInfo coin;
     417              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     418              :   struct RefreshStatus st;
     419              : 
     420            1 :   TDB_coin (pg,
     421              :             &denom,
     422              :             20,
     423              :             &coin,
     424              :             NULL);
     425              :   /* a fresh commitment, but the blinding seed of check_refresh() */
     426            1 :   make_refresh (4,
     427              :                 "1",
     428              :                 &coin,
     429              :                 true,
     430              :                 &rf);
     431            1 :   TDB_fill (&rf.blinding_seed,
     432              :             sizeof (rf.blinding_seed),
     433              :             3);
     434            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     435              :             run_refresh (pg,
     436              :                          &rf,
     437              :                          false,
     438              :                          &st),
     439              :             free_refresh (&rf); TDB_coin_free (&coin));
     440            1 :   FAILIF_C (! st.nonce_reuse,
     441              :             free_refresh (&rf); TDB_coin_free (&coin));
     442            1 :   FAILIF_C (st.found,
     443              :             free_refresh (&rf); TDB_coin_free (&coin));
     444            1 :   free_refresh (&rf);
     445            1 :   TDB_coin_free (&coin);
     446            1 :   FAILIF (1 != TDB_count (pg,
     447              :                           "FROM refresh"));
     448            1 :   return 0;
     449              : }
     450              : 
     451              : 
     452              : /**
     453              :  * A melt that insists on a zombie coin is refused when the coin never was
     454              :  * one.
     455              :  *
     456              :  * @param pg the database context
     457              :  * @return 0 on success
     458              :  */
     459              : static int
     460            1 : check_zombie_required (struct TALER_EXCHANGEDB_PostgresContext *pg)
     461              : {
     462              :   struct TALER_CoinPublicInfo coin;
     463              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     464              :   struct RefreshStatus st;
     465              : 
     466            1 :   TDB_coin (pg,
     467              :             &denom,
     468              :             21,
     469              :             &coin,
     470              :             NULL);
     471            1 :   make_refresh (5,
     472              :                 "1",
     473              :                 &coin,
     474              :                 false,
     475              :                 &rf);
     476            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     477              :             run_refresh (pg,
     478              :                          &rf,
     479              :                          true,
     480              :                          &st),
     481              :             free_refresh (&rf); TDB_coin_free (&coin));
     482            1 :   FAILIF_C (! st.zombie_required,
     483              :             free_refresh (&rf); TDB_coin_free (&coin));
     484            1 :   FAILIF_C (st.balance_ok,
     485              :             free_refresh (&rf); TDB_coin_free (&coin));
     486            1 :   free_refresh (&rf);
     487            1 :   TDB_coin_free (&coin);
     488              :   /* the row went in before the zombie check, so the caller must roll back */
     489            1 :   FAILIF (2 != TDB_count (pg,
     490              :                           "FROM refresh"));
     491            1 :   FAILIF (GNUNET_OK !=
     492              :           TDB_exec (pg,
     493              :                     "DELETE FROM refresh WHERE NOT revealed"
     494              :                     " AND rc <> (SELECT rc FROM refresh"
     495              :                     "             ORDER BY refresh_id ASC LIMIT 1);"));
     496            1 :   return 0;
     497              : }
     498              : 
     499              : 
     500              : /**
     501              :  * A melt can be marked as revealed, and marking one that does not exist
     502              :  * does nothing.
     503              :  *
     504              :  * @param pg the database context
     505              :  * @return 0 on success
     506              :  */
     507              : static int
     508            1 : check_revealed (struct TALER_EXCHANGEDB_PostgresContext *pg)
     509              : {
     510              :   struct TALER_RefreshCommitmentP rc;
     511              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got;
     512              : 
     513            1 :   TDB_FILL (rc,
     514              :             99);
     515            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     516              :           TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
     517              :                                                        &rc));
     518            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     519              :           TALER_EXCHANGEDB_get_refresh (pg,
     520              :                                         &rc,
     521              :                                         &got));
     522              : 
     523            1 :   TDB_FILL (rc,
     524              :             3);
     525            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     526              :           TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
     527              :                                                        &rc));
     528            1 :   memset (&got,
     529              :           0,
     530              :           sizeof (got));
     531            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     532              :           TALER_EXCHANGEDB_get_refresh (pg,
     533              :                                         &rc,
     534              :                                         &got));
     535            1 :   FAILIF_C (! got.revealed,
     536              :             free_refresh (&got));
     537            1 :   free_refresh (&got);
     538              :   /* marking it again is still reported as a row touched */
     539            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     540              :           TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
     541              :                                                        &rc));
     542            1 :   return 0;
     543              : }
     544              : 
     545              : 
     546              : /**
     547              :  * Closure for #refreshes_cb().
     548              :  */
     549              : struct RefreshesContext
     550              : {
     551              :   /**
     552              :    * How many melts did the callback see?
     553              :    */
     554              :   unsigned int total;
     555              : 
     556              :   /**
     557              :    * Stop after this many melts; 0 for no limit.
     558              :    */
     559              :   unsigned int stop_after;
     560              : 
     561              :   /**
     562              :    * Row of the last melt.
     563              :    */
     564              :   uint64_t rowid;
     565              : 
     566              :   /**
     567              :    * Amount of the last melt.
     568              :    */
     569              :   struct TALER_Amount amount;
     570              : 
     571              :   /**
     572              :    * Number of new denominations of the last melt.
     573              :    */
     574              :   size_t num_nds;
     575              : };
     576              : 
     577              : 
     578              : /**
     579              :  * Callback for #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id().
     580              :  *
     581              :  * @param cls a `struct RefreshesContext *`
     582              :  * @param rowid row of the melt
     583              :  * @param old_denom_pub denomination of the melted coin
     584              :  * @param coin_pub the melted coin
     585              :  * @param coin_sig signature authorising the melt
     586              :  * @param h_age_commitment age commitment of the coin, NULL if none
     587              :  * @param amount_with_fee how much was melted
     588              :  * @param num_nds length of @a new_denom_serials
     589              :  * @param new_denom_serials denominations of the fresh coins
     590              :  * @param rc commitment of the melt
     591              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     592              :  */
     593              : static enum GNUNET_GenericReturnValue
     594            3 : refreshes_cb (void *cls,
     595              :               uint64_t rowid,
     596              :               const struct TALER_DenominationPublicKey *old_denom_pub,
     597              :               const struct TALER_CoinSpendPublicKeyP *coin_pub,
     598              :               const struct TALER_CoinSpendSignatureP *coin_sig,
     599              :               const struct TALER_AgeCommitmentHashP *h_age_commitment,
     600              :               const struct TALER_Amount *amount_with_fee,
     601              :               size_t num_nds,
     602              :               uint64_t new_denom_serials[static num_nds],
     603              :               const struct TALER_RefreshCommitmentP *rc)
     604            3 : {
     605            3 :   struct RefreshesContext *ctx = cls;
     606              : 
     607              :   (void) old_denom_pub;
     608              :   (void) coin_pub;
     609              :   (void) coin_sig;
     610              :   (void) h_age_commitment;
     611              :   (void) new_denom_serials;
     612              :   (void) rc;
     613            3 :   ctx->total++;
     614            3 :   ctx->rowid = rowid;
     615            3 :   ctx->amount = *amount_with_fee;
     616            3 :   ctx->num_nds = num_nds;
     617            3 :   if ( (0 != ctx->stop_after) &&
     618            1 :        (ctx->total >= ctx->stop_after) )
     619            1 :     return GNUNET_SYSERR;
     620            2 :   return GNUNET_OK;
     621              : }
     622              : 
     623              : 
     624              : /**
     625              :  * The auditor's view walks the melts by serial and stops when asked to.
     626              :  *
     627              :  * @param pg the database context
     628              :  * @return 0 on success
     629              :  */
     630              : static int
     631            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     632              : {
     633              :   struct TALER_CoinPublicInfo coin;
     634              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     635              :   struct RefreshStatus st;
     636              :   struct RefreshesContext ctx;
     637              :   uint64_t rows;
     638              : 
     639              :   /* one more melt, so that "stop after the first" is distinguishable
     640              :      from "saw everything" */
     641            1 :   TDB_coin (pg,
     642              :             &denom,
     643              :             42,
     644              :             &coin,
     645              :             NULL);
     646            1 :   make_refresh (42,
     647              :                 "1",
     648              :                 &coin,
     649              :                 false,
     650              :                 &rf);
     651            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     652              :             run_refresh (pg,
     653              :                          &rf,
     654              :                          false,
     655              :                          &st),
     656              :             free_refresh (&rf); TDB_coin_free (&coin));
     657            1 :   free_refresh (&rf);
     658            1 :   TDB_coin_free (&coin);
     659            1 :   FAILIF (! st.balance_ok);
     660            1 :   rows = TDB_count (pg,
     661              :                     "FROM refresh");
     662            1 :   FAILIF (2 != rows);
     663            1 :   memset (&ctx,
     664              :           0,
     665              :           sizeof (ctx));
     666            1 :   FAILIF (0 >=
     667              :           TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
     668              :             pg,
     669              :             0,
     670              :             &refreshes_cb,
     671              :             &ctx));
     672            1 :   FAILIF (rows != ctx.total);
     673              :   /* every melt here asked for exactly one fresh coin */
     674            1 :   FAILIF (1 != ctx.num_nds);
     675              : 
     676              :   /* everything above the last row is nothing */
     677            1 :   memset (&ctx,
     678              :           0,
     679              :           sizeof (ctx));
     680            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     681              :           TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
     682              :             pg,
     683              :             UINT32_MAX,
     684              :             &refreshes_cb,
     685              :             &ctx));
     686            1 :   FAILIF (0 != ctx.total);
     687              : 
     688              :   /* a callback that says stop is not called again */
     689            1 :   memset (&ctx,
     690              :           0,
     691              :           sizeof (ctx));
     692            1 :   ctx.stop_after = 1;
     693            1 :   FAILIF (0 >=
     694              :           TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
     695              :             pg,
     696              :             0,
     697              :             &refreshes_cb,
     698              :             &ctx));
     699            1 :   FAILIF (1 != ctx.total);
     700            1 :   return 0;
     701              : }
     702              : 
     703              : 
     704              : /**
     705              :  * The checks to run, in order.
     706              :  */
     707              : static const struct TDB_Test tests[] = {
     708              :   { "refresh-unknown-coin",
     709              :     &check_unknown_coin },
     710              :   { "refresh-insufficient-balance",
     711              :     &check_insufficient_balance },
     712              :   { "refresh-refresh",
     713              :     &check_refresh },
     714              :   { "refresh-nonce-reuse",
     715              :     &check_nonce_reuse },
     716              :   { "refresh-zombie-required",
     717              :     &check_zombie_required },
     718              :   { "refresh-revealed",
     719              :     &check_revealed },
     720              :   { "refresh-iterate",
     721              :     &check_iterate },
     722              :   { NULL, NULL }
     723              : };
     724              : 
     725              : 
     726              : int
     727            1 : main (int argc,
     728              :       char *const *argv)
     729              : {
     730              :   int ret;
     731              : 
     732            1 :   ret = TDB_main (argc,
     733              :                   argv,
     734              :                   "test-refresh",
     735              :                   "Tests for the exchangedb `refresh' table",
     736              :                   tests);
     737            1 :   TDB_denom_free (&denom);
     738            1 :   return ret;
     739              : }
     740              : 
     741              : 
     742              : /* end of test_refresh.c */
        

Generated by: LCOV version 2.0-1