LCOV - code coverage report
Current view: top level - exchangedb - test_refresh.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 171 171
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 13 13

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

Generated by: LCOV version 2.0-1