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

            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_recoup_refresh.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `recoup_refresh`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_recoup_refresh() and
      23              :  * #TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id().
      24              :  *
      25              :  * `recoup_refresh` references `known_coins` and `refresh`, so each check
      26              :  * builds a melt first.  do_recoup_refresh() moves the fresh coin's whole
      27              :  * remaining balance back to the old coin.
      28              :  */
      29              : #include "test_common.h"
      30              : #include "exchange-database/do_recoup_refresh.h"
      31              : #include "exchange-database/do_refresh.h"
      32              : #include "exchange-database/iterate_recoup_refreshes_above_serial_id.h"
      33              : #include "exchange-database/iterate_records_by_table.h"
      34              : #include "exchange-database/insert_records_by_table.h"
      35              : 
      36              : 
      37              : /**
      38              :  * Account the checks fund their reserves from.
      39              :  */
      40              : static struct TDB_Account account;
      41              : 
      42              : 
      43              : /**
      44              :  * Denomination the checks use.
      45              :  */
      46              : static struct TDB_Denom denom;
      47              : 
      48              : 
      49              : /**
      50              :  * Melt @a coin and return the row of the melt.
      51              :  *
      52              :  * @param pg the database context
      53              :  * @param coin coin to melt
      54              :  * @param seed seed for the commitment and signatures
      55              :  * @param amount how much to melt, e.g. "1"
      56              :  * @return row of the melt in `refresh`
      57              :  */
      58              : static uint64_t
      59            6 : melt (struct TALER_EXCHANGEDB_PostgresContext *pg,
      60              :       const struct TALER_CoinPublicInfo *coin,
      61              :       uint32_t seed,
      62              :       const char *amount)
      63              : {
      64              :   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
      65              :   struct TALER_BlindedDenominationSignature denom_sig;
      66            6 :   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
      67              :   struct TALER_Amount coin_balance;
      68            6 :   uint64_t denom_serial = denom.serial;
      69              :   uint64_t refresh_id;
      70              :   uint32_t noreveal_index;
      71              :   bool found;
      72            6 :   bool zombie_required = false;
      73              :   bool nonce_reuse;
      74              :   bool balance_ok;
      75              : 
      76            6 :   memset (&rf,
      77              :           0,
      78              :           sizeof (rf));
      79            6 :   rf.coin.coin_pub = coin->coin_pub;
      80            6 :   rf.coin.denom_pub_hash = coin->denom_pub_hash;
      81            6 :   rf.coin.no_age_commitment = coin->no_age_commitment;
      82            6 :   TDB_fill (&rf.coin_sig,
      83              :             sizeof (rf.coin_sig),
      84              :             seed);
      85            6 :   TDB_fill (&rf.rc,
      86              :             sizeof (rf.rc),
      87              :             seed);
      88            6 :   TDB_fill (&rf.refresh_seed,
      89              :             sizeof (rf.refresh_seed),
      90              :             seed);
      91            6 :   TDB_fill (&rf.planchets_h,
      92              :             sizeof (rf.planchets_h),
      93              :             seed);
      94            6 :   TDB_fill (&rf.selected_h,
      95              :             sizeof (rf.selected_h),
      96              :             seed + 1);
      97            6 :   rf.amount_with_fee = TDB_amount (amount);
      98            6 :   rf.num_coins = 1;
      99            6 :   rf.denom_serials = &denom_serial;
     100            6 :   TDB_blinded_denom_sig (seed,
     101              :                          &denom_sig);
     102            6 :   rf.denom_sigs = &denom_sig;
     103            6 :   rf.noreveal_index = 0;
     104            6 :   rf.is_v27_refresh = true;
     105            6 :   rf.no_blinding_seed = true;
     106            6 :   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     107              :                  TALER_EXCHANGEDB_do_refresh (pg,
     108              :                                               &rf,
     109              :                                               &now,
     110              :                                               &found,
     111              :                                               &noreveal_index,
     112              :                                               &zombie_required,
     113              :                                               &nonce_reuse,
     114              :                                               &balance_ok,
     115              :                                               &coin_balance));
     116            6 :   GNUNET_assert (balance_ok);
     117            6 :   TALER_blinded_denom_sig_free (&denom_sig);
     118              :   {
     119            6 :     struct GNUNET_PQ_QueryParam params[] = {
     120            6 :       GNUNET_PQ_query_param_auto_from_type (&rf.rc),
     121              :       GNUNET_PQ_query_param_end
     122              :     };
     123            6 :     struct GNUNET_PQ_ResultSpec rs[] = {
     124            6 :       GNUNET_PQ_result_spec_uint64 ("refresh_id",
     125              :                                     &refresh_id),
     126              :       GNUNET_PQ_result_spec_end
     127              :     };
     128              : 
     129            6 :     GNUNET_assert (GNUNET_OK ==
     130              :                    GNUNET_PQ_prepare_anon (pg->conn,
     131              :                                            "SELECT refresh_id"
     132              :                                            " FROM refresh"
     133              :                                            " WHERE rc=$1;"));
     134            6 :     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     135              :                    GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
     136              :                                                              "",
     137              :                                                              params,
     138              :                                                              rs));
     139              :   }
     140            6 :   return refresh_id;
     141              : }
     142              : 
     143              : 
     144              : /**
     145              :  * Callback for
     146              :  * #TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id().
     147              :  *
     148              :  * @param cls a pointer to an `unsigned int` counter
     149              :  * @param rowid row of the recoup
     150              :  * @param timestamp when it happened
     151              :  * @param amount how much went back
     152              :  * @param old_coin_pub the old coin
     153              :  * @param old_denom_pub_hash denomination of the old coin
     154              :  * @param coin the fresh coin
     155              :  * @param denom_pub denomination of @a coin
     156              :  * @param coin_sig signature over the request
     157              :  * @param coin_blind blinding secret of the fresh coin
     158              :  * @return #GNUNET_OK
     159              :  */
     160              : static enum GNUNET_GenericReturnValue
     161            1 : recoup_refresh_cb (void *cls,
     162              :                    uint64_t rowid,
     163              :                    struct GNUNET_TIME_Timestamp timestamp,
     164              :                    const struct TALER_Amount *amount,
     165              :                    const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
     166              :                    const struct TALER_DenominationHashP *old_denom_pub_hash,
     167              :                    const struct TALER_CoinPublicInfo *coin,
     168              :                    const struct TALER_DenominationPublicKey *denom_pub,
     169              :                    const struct TALER_CoinSpendSignatureP *coin_sig,
     170              :                    const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
     171              : {
     172            1 :   unsigned int *total = cls;
     173              : 
     174              :   (void) rowid;
     175              :   (void) timestamp;
     176              :   (void) amount;
     177              :   (void) old_coin_pub;
     178              :   (void) old_denom_pub_hash;
     179              :   (void) coin;
     180              :   (void) denom_pub;
     181              :   (void) coin_sig;
     182              :   (void) coin_blind;
     183            1 :   (*total)++;
     184            1 :   return GNUNET_OK;
     185              : }
     186              : 
     187              : 
     188              : /**
     189              :  * Outcome of a recoup-refresh request.
     190              :  */
     191              : struct RecoupStatus
     192              : {
     193              :   /**
     194              :    * Was the recoup accepted?
     195              :    */
     196              :   bool recoup_ok;
     197              : 
     198              :   /**
     199              :    * Did something go wrong inside the database?
     200              :    */
     201              :   bool internal_failure;
     202              : 
     203              :   /**
     204              :    * When the recoup happened.
     205              :    */
     206              :   struct GNUNET_TIME_Timestamp recoup_timestamp;
     207              : 
     208              :   /**
     209              :    * Amount the recoup credited.
     210              :    */
     211              :   struct TALER_Amount recoup_amount;
     212              : };
     213              : 
     214              : 
     215              : /**
     216              :  * Recoup a fresh coin back onto the coin it was refreshed from.
     217              :  *
     218              :  * @param pg the database context
     219              :  * @param old_coin_pub coin to credit
     220              :  * @param refresh_id melt that justifies the recoup
     221              :  * @param coin_pub fresh coin to drain
     222              :  * @param known_coin_id row of @a coin_pub
     223              :  * @param seed seed for the blinding secret and coin signature
     224              :  * @param[out] st set to the outcome
     225              :  * @return transaction status
     226              :  */
     227              : static enum GNUNET_DB_QueryStatus
     228            8 : run_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg,
     229              :             const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
     230              :             uint64_t refresh_id,
     231              :             const struct TALER_CoinSpendPublicKeyP *coin_pub,
     232              :             uint64_t known_coin_id,
     233              :             uint32_t seed,
     234              :             struct RecoupStatus *st)
     235              : {
     236              :   union GNUNET_CRYPTO_BlindingSecretP coin_bks;
     237              :   struct TALER_CoinSpendSignatureP coin_sig;
     238              : 
     239            8 :   TDB_fill (&coin_bks,
     240              :             sizeof (coin_bks),
     241              :             seed);
     242            8 :   TDB_fill (&coin_sig,
     243              :             sizeof (coin_sig),
     244              :             seed);
     245            8 :   memset (st,
     246              :           0,
     247              :           sizeof (*st));
     248            8 :   st->recoup_timestamp = GNUNET_TIME_timestamp_get ();
     249            8 :   return TALER_EXCHANGEDB_do_recoup_refresh (pg,
     250              :                                              old_coin_pub,
     251              :                                              refresh_id,
     252              :                                              0,
     253              :                                              &coin_bks,
     254              :                                              coin_pub,
     255              :                                              known_coin_id,
     256              :                                              &coin_sig,
     257              :                                              &st->recoup_timestamp,
     258              :                                              &st->recoup_amount,
     259              :                                              &st->recoup_ok,
     260              :                                              &st->internal_failure);
     261              : }
     262              : 
     263              : 
     264              : /**
     265              :  * Recouping a coin the exchange does not know is an internal failure.
     266              :  *
     267              :  * @param pg the database context
     268              :  * @return 0 on success
     269              :  */
     270              : static int
     271            1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     272              : {
     273              :   struct TALER_ReservePublicKeyP reserve_pub;
     274              :   struct TALER_CoinPublicInfo old_coin;
     275              :   struct TALER_CoinSpendPublicKeyP coin_pub;
     276              :   struct RecoupStatus st;
     277              :   uint64_t refresh_id;
     278              : 
     279            1 :   TDB_denom (pg,
     280              :              10,
     281              :              "5",
     282              :              "0.1",
     283              :              &denom);
     284            1 :   TDB_account (pg,
     285              :                10,
     286              :                &account);
     287            1 :   TDB_reserve_in (pg,
     288              :                   &account,
     289              :                   10,
     290              :                   "10",
     291              :                   &reserve_pub);
     292            1 :   TDB_coin (pg,
     293              :             &denom,
     294              :             20,
     295              :             &old_coin,
     296              :             NULL);
     297            1 :   refresh_id = melt (pg,
     298              :                      &old_coin,
     299              :                      1,
     300              :                      "1");
     301            1 :   TDB_FILL (coin_pub,
     302              :             99);
     303            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     304              :             run_recoup (pg,
     305              :                         &old_coin.coin_pub,
     306              :                         refresh_id,
     307              :                         &coin_pub,
     308              :                         1,
     309              :                         99,
     310              :                         &st),
     311              :             TDB_coin_free (&old_coin));
     312            1 :   TDB_coin_free (&old_coin);
     313            1 :   FAILIF (! st.internal_failure);
     314            1 :   FAILIF (st.recoup_ok);
     315            1 :   FAILIF (0 != TDB_count (pg,
     316              :                           "FROM recoup_refresh"));
     317            1 :   return 0;
     318              : }
     319              : 
     320              : 
     321              : /**
     322              :  * A fresh coin with nothing left on it and no earlier recoup is refused.
     323              :  *
     324              :  * @param pg the database context
     325              :  * @return 0 on success
     326              :  */
     327              : static int
     328            1 : check_empty_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     329              : {
     330              :   struct TALER_CoinPublicInfo old_coin;
     331              :   struct TALER_CoinPublicInfo fresh;
     332              :   struct RecoupStatus st;
     333              :   uint64_t known_coin_id;
     334              :   uint64_t refresh_id;
     335              :   char *hex;
     336              : 
     337            1 :   TDB_coin (pg,
     338              :             &denom,
     339              :             21,
     340              :             &old_coin,
     341              :             NULL);
     342            1 :   refresh_id = melt (pg,
     343              :                      &old_coin,
     344              :                      2,
     345              :                      "1");
     346            1 :   TDB_coin (pg,
     347              :             &denom,
     348              :             22,
     349              :             &fresh,
     350              :             &known_coin_id);
     351            1 :   hex = TDB_hex (&fresh.coin_pub,
     352              :                  sizeof (fresh.coin_pub));
     353            1 :   FAILIF_C (GNUNET_OK !=
     354              :             TDB_exec (pg,
     355              :                       "UPDATE known_coins"
     356              :                       " SET remaining=ROW(0,0)::taler_amount"
     357              :                       " WHERE coin_pub=decode('%s','hex');",
     358              :                       hex),
     359              :             GNUNET_free (hex);
     360              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     361            1 :   GNUNET_free (hex);
     362            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     363              :             run_recoup (pg,
     364              :                         &old_coin.coin_pub,
     365              :                         refresh_id,
     366              :                         &fresh.coin_pub,
     367              :                         known_coin_id,
     368              :                         22,
     369              :                         &st),
     370              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     371            1 :   TDB_coin_free (&fresh);
     372            1 :   TDB_coin_free (&old_coin);
     373            1 :   FAILIF (st.internal_failure);
     374            1 :   FAILIF (st.recoup_ok);
     375            1 :   FAILIF (0 != TDB_count (pg,
     376              :                           "FROM recoup_refresh"));
     377            1 :   return 0;
     378              : }
     379              : 
     380              : 
     381              : /**
     382              :  * A funded fresh coin is drained back onto the old coin.
     383              :  *
     384              :  * @param pg the database context
     385              :  * @return 0 on success
     386              :  */
     387              : static int
     388            1 : check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
     389              : {
     390              :   struct TALER_CoinPublicInfo old_coin;
     391              :   struct TALER_CoinPublicInfo fresh;
     392              :   struct RecoupStatus st;
     393              :   uint64_t known_coin_id;
     394              :   uint64_t refresh_id;
     395              :   char *hex;
     396              : 
     397            1 :   TDB_coin (pg,
     398              :             &denom,
     399              :             23,
     400              :             &old_coin,
     401              :             NULL);
     402            1 :   refresh_id = melt (pg,
     403              :                      &old_coin,
     404              :                      3,
     405              :                      "1");
     406            1 :   TDB_coin (pg,
     407              :             &denom,
     408              :             24,
     409              :             &fresh,
     410              :             &known_coin_id);
     411            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     412              :             run_recoup (pg,
     413              :                         &old_coin.coin_pub,
     414              :                         refresh_id,
     415              :                         &fresh.coin_pub,
     416              :                         known_coin_id,
     417              :                         24,
     418              :                         &st),
     419              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     420            1 :   FAILIF_C (st.internal_failure,
     421              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     422            1 :   FAILIF_C (! st.recoup_ok,
     423              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     424            1 :   FAILIF_C (1 != TDB_count (pg,
     425              :                             "FROM recoup_refresh"),
     426              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     427              : 
     428              :   /* the fresh coin is empty */
     429            1 :   hex = TDB_hex (&fresh.coin_pub,
     430              :                  sizeof (fresh.coin_pub));
     431            1 :   FAILIF_C (1 != TDB_count (pg,
     432              :                             "FROM known_coins"
     433              :                             " WHERE coin_pub=decode('%s','hex')"
     434              :                             "   AND remaining=ROW(0,0)::taler_amount",
     435              :                             hex),
     436              :             GNUNET_free (hex);
     437              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     438            1 :   GNUNET_free (hex);
     439              :   /* the old coin got the EUR:5 back on top of the EUR:4 it had left */
     440            1 :   hex = TDB_hex (&old_coin.coin_pub,
     441              :                  sizeof (old_coin.coin_pub));
     442            1 :   FAILIF_C (1 != TDB_count (pg,
     443              :                             "FROM known_coins"
     444              :                             " WHERE coin_pub=decode('%s','hex')"
     445              :                             "   AND remaining=ROW(9,0)::taler_amount",
     446              :                             hex),
     447              :             GNUNET_free (hex);
     448              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     449            1 :   GNUNET_free (hex);
     450              : 
     451              :   /* recouping the same coin again finds the earlier recoup */
     452            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     453              :             run_recoup (pg,
     454              :                         &old_coin.coin_pub,
     455              :                         refresh_id,
     456              :                         &fresh.coin_pub,
     457              :                         known_coin_id,
     458              :                         24,
     459              :                         &st),
     460              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     461            1 :   FAILIF_C (! st.recoup_ok,
     462              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     463            1 :   FAILIF_C (1 != TDB_count (pg,
     464              :                             "FROM recoup_refresh"),
     465              :             TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     466              : 
     467              :   /* ...but a recoup of the same coin for another refresh operation
     468              :      is not the same recoup: the coin has nothing left for it */
     469              :   {
     470              :     uint64_t other_refresh_id;
     471              : 
     472            1 :     other_refresh_id = melt (pg,
     473              :                              &old_coin,
     474              :                              5,
     475              :                              "1");
     476            1 :     FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     477              :               run_recoup (pg,
     478              :                           &old_coin.coin_pub,
     479              :                           other_refresh_id,
     480              :                           &fresh.coin_pub,
     481              :                           known_coin_id,
     482              :                           24,
     483              :                           &st),
     484              :               TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     485            1 :     FAILIF_C (st.recoup_ok,
     486              :               TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     487            1 :     FAILIF_C (st.internal_failure,
     488              :               TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     489            1 :     FAILIF_C (1 != TDB_count (pg,
     490              :                               "FROM recoup_refresh"),
     491              :               TDB_coin_free (&fresh); TDB_coin_free (&old_coin));
     492              :   }
     493            1 :   TDB_coin_free (&fresh);
     494            1 :   TDB_coin_free (&old_coin);
     495            1 :   return 0;
     496              : }
     497              : 
     498              : 
     499              : /**
     500              :  * Closure for #replication_cb().
     501              :  */
     502              : struct ReplicationContext
     503              : {
     504              :   /**
     505              :    * Coin index every record must carry.
     506              :    */
     507              :   uint32_t expected_coin_index;
     508              : 
     509              :   /**
     510              :    * Records seen.
     511              :    */
     512              :   unsigned int seen;
     513              : 
     514              :   /**
     515              :    * Set if a record carried another coin index.
     516              :    */
     517              :   bool mismatch;
     518              : };
     519              : 
     520              : 
     521              : /**
     522              :  * Counts the replication records of the recoup table and checks
     523              :  * their coin index.
     524              :  *
     525              :  * @param cls a `struct ReplicationContext *`
     526              :  * @param td the record
     527              :  * @return #GNUNET_OK to continue
     528              :  */
     529              : static int
     530            2 : replication_cb (void *cls,
     531              :                 const struct TALER_EXCHANGEDB_TableData *td)
     532              : {
     533            2 :   struct ReplicationContext *rc = cls;
     534              : 
     535            2 :   if (TALER_EXCHANGEDB_RT_RECOUP_REFRESH != td->table)
     536              :   {
     537            0 :     rc->mismatch = true;
     538            0 :     return GNUNET_OK;
     539              :   }
     540            2 :   rc->seen++;
     541            2 :   if (rc->expected_coin_index != td->details.recoup_refresh.coin_index)
     542            0 :     rc->mismatch = true;
     543            2 :   return GNUNET_OK;
     544              : }
     545              : 
     546              : 
     547              : /**
     548              :  * Replication carries the coin index of a recoup-refresh.
     549              :  *
     550              :  * @param pg the database context
     551              :  * @return 0 on success
     552              :  */
     553              : static int
     554            1 : check_replication (struct TALER_EXCHANGEDB_PostgresContext *pg)
     555              : {
     556            1 :   struct ReplicationContext rc = {
     557              :     .expected_coin_index = 0
     558              :   };
     559            2 :   struct TALER_EXCHANGEDB_TableData td = {
     560              :     .table = TALER_EXCHANGEDB_RT_RECOUP_REFRESH,
     561              :     .serial = 77,
     562              :     .details.recoup_refresh.coin_index = 7,
     563            1 :     .details.recoup_refresh.amount = TDB_amount ("1"),
     564            1 :     .details.recoup_refresh.recoup_timestamp = GNUNET_TIME_timestamp_get ()
     565              :   };
     566              :   struct TALER_CoinPublicInfo old_coin;
     567              :   struct TALER_CoinPublicInfo fresh;
     568              : 
     569              :   /* the row made by check_recoup_refresh() is reported with its index 0 */
     570            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     571              :           TALER_EXCHANGEDB_iterate_records_by_table (
     572              :             pg,
     573              :             TALER_EXCHANGEDB_RT_RECOUP_REFRESH,
     574              :             0,
     575              :             &replication_cb,
     576              :             &rc));
     577            1 :   FAILIF (1 != rc.seen);
     578            1 :   FAILIF (rc.mismatch);
     579              :   /* a replicated row keeps its index */
     580            1 :   TDB_coin (pg,
     581              :             &denom,
     582              :             25,
     583              :             &old_coin,
     584              :             NULL);
     585            1 :   TDB_coin (pg,
     586              :             &denom,
     587              :             26,
     588              :             &fresh,
     589              :             &td.details.recoup_refresh.known_coin_id);
     590            1 :   td.details.recoup_refresh.coin_pub = fresh.coin_pub;
     591            1 :   td.details.recoup_refresh.refresh_id = melt (pg,
     592              :                                                &old_coin,
     593              :                                                6,
     594              :                                                "1");
     595            1 :   TDB_coin_free (&old_coin);
     596            1 :   TDB_coin_free (&fresh);
     597            1 :   TDB_FILL (td.details.recoup_refresh.coin_sig,
     598              :             77);
     599            1 :   TDB_FILL (td.details.recoup_refresh.coin_blind,
     600              :             77);
     601            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     602              :           TALER_EXCHANGEDB_insert_records_by_table (pg,
     603              :                                                     &td));
     604            1 :   FAILIF (1 != TDB_count (pg,
     605              :                           "FROM recoup_refresh WHERE coin_index=7"));
     606            1 :   rc.seen = 0;
     607            1 :   rc.expected_coin_index = 7;
     608            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     609              :           TALER_EXCHANGEDB_iterate_records_by_table (
     610              :             pg,
     611              :             TALER_EXCHANGEDB_RT_RECOUP_REFRESH,
     612              :             76,
     613              :             &replication_cb,
     614              :             &rc));
     615            1 :   FAILIF (1 != rc.seen);
     616            1 :   FAILIF (rc.mismatch);
     617            1 :   return 0;
     618              : }
     619              : 
     620              : 
     621              : /**
     622              :  * The iterator must not report a recoup-refresh that is not there.
     623              :  *
     624              :  * This is all it can be checked for today: its statement joins
     625              :  * `refresh_revealed_coins` and `refresh_commitments`, tables the schema no
     626              :  * longer has, so it cannot return a row at all (EDB-16).  The assertion
     627              :  * below holds both now and once that is repaired.
     628              :  *
     629              :  * @param pg the database context
     630              :  * @return 0 on success
     631              :  */
     632              : static int
     633            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     634              : {
     635            1 :   unsigned int total = 0;
     636              : 
     637              :   /* the one recoup made in check_recoup_refresh() */
     638            1 :   FAILIF (1 !=
     639              :           TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id (
     640              :             pg,
     641              :             0,
     642              :             &recoup_refresh_cb,
     643              :             &total));
     644            1 :   FAILIF (1 != total);
     645            1 :   total = 0;
     646            1 :   FAILIF (0 <
     647              :           TALER_EXCHANGEDB_iterate_recoup_refreshes_above_serial_id (
     648              :             pg,
     649              :             1000,
     650              :             &recoup_refresh_cb,
     651              :             &total));
     652            1 :   FAILIF (0 != total);
     653            1 :   return 0;
     654              : }
     655              : 
     656              : 
     657              : /**
     658              :  * A recouped coin that was credited again (a refund does that) can be
     659              :  * recouped a second time.  Replaying the request afterwards must report
     660              :  * the latest recoup, not an arbitrary earlier one.
     661              :  *
     662              :  * @param pg the database context
     663              :  * @return 0 on success
     664              :  */
     665              : static int
     666            1 : check_replay_latest (struct TALER_EXCHANGEDB_PostgresContext *pg)
     667              : {
     668              :   struct TALER_CoinPublicInfo old_coin;
     669              :   struct TALER_CoinPublicInfo fresh;
     670              :   struct RecoupStatus st;
     671            1 :   struct TALER_Amount expect_first = TDB_amount ("5");
     672            1 :   struct TALER_Amount expect_second = TDB_amount ("2");
     673              :   uint64_t known_coin_id;
     674              :   uint64_t refresh_id;
     675              :   char *hex;
     676            1 :   int ret = 1;
     677              : 
     678            1 :   TDB_coin (pg,
     679              :             &denom,
     680              :             27,
     681              :             &old_coin,
     682              :             NULL);
     683            1 :   refresh_id = melt (pg,
     684              :                      &old_coin,
     685              :                      7,
     686              :                      "1");
     687            1 :   TDB_coin (pg,
     688              :             &denom,
     689              :             28,
     690              :             &fresh,
     691              :             &known_coin_id);
     692            1 :   hex = TDB_hex (&fresh.coin_pub,
     693              :                  sizeof (fresh.coin_pub));
     694              : 
     695              :   /* first recoup drains the fresh coin's EUR:5 */
     696            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     697              :             run_recoup (pg,
     698              :                         &old_coin.coin_pub,
     699              :                         refresh_id,
     700              :                         &fresh.coin_pub,
     701              :                         known_coin_id,
     702              :                         28,
     703              :                         &st),
     704              :             goto cleanup);
     705            1 :   FAILIF_C ( (! st.recoup_ok) ||
     706              :              (st.internal_failure) ||
     707              :              (0 != TALER_amount_cmp (&st.recoup_amount,
     708              :                                      &expect_first)),
     709              :              goto cleanup);
     710              : 
     711              :   /* the fresh coin gets EUR:2 back, as a refund would do */
     712            1 :   FAILIF_C (GNUNET_OK !=
     713              :             TDB_exec (pg,
     714              :                       "UPDATE known_coins"
     715              :                       " SET remaining=ROW(2,0)::taler_amount"
     716              :                       " WHERE coin_pub=decode('%s','hex');",
     717              :                       hex),
     718              :             goto cleanup);
     719              : 
     720              :   /* second recoup for the same refresh drains the EUR:2 */
     721            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     722              :             run_recoup (pg,
     723              :                         &old_coin.coin_pub,
     724              :                         refresh_id,
     725              :                         &fresh.coin_pub,
     726              :                         known_coin_id,
     727              :                         28,
     728              :                         &st),
     729              :             goto cleanup);
     730            1 :   FAILIF_C ( (! st.recoup_ok) ||
     731              :              (st.internal_failure) ||
     732              :              (0 != TALER_amount_cmp (&st.recoup_amount,
     733              :                                      &expect_second)),
     734              :              goto cleanup);
     735            1 :   FAILIF_C (2 != TDB_count (pg,
     736              :                             "FROM recoup_refresh"
     737              :                             " WHERE coin_pub=decode('%s','hex')",
     738              :                             hex),
     739              :             goto cleanup);
     740              : 
     741              :   /* replaying the request reports the latest recoup... */
     742            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     743              :             run_recoup (pg,
     744              :                         &old_coin.coin_pub,
     745              :                         refresh_id,
     746              :                         &fresh.coin_pub,
     747              :                         known_coin_id,
     748              :                         28,
     749              :                         &st),
     750              :             goto cleanup);
     751            1 :   FAILIF_C ( (! st.recoup_ok) ||
     752              :              (st.internal_failure),
     753              :              goto cleanup);
     754            1 :   FAILIF_C (0 != TALER_amount_cmp (&st.recoup_amount,
     755              :                                    &expect_second),
     756              :             goto cleanup);
     757              :   /* ...and does not touch the tables again */
     758            1 :   FAILIF_C (2 != TDB_count (pg,
     759              :                             "FROM recoup_refresh"
     760              :                             " WHERE coin_pub=decode('%s','hex')",
     761              :                             hex),
     762              :             goto cleanup);
     763            1 :   ret = 0;
     764            1 : cleanup:
     765            1 :   GNUNET_free (hex);
     766            1 :   TDB_coin_free (&fresh);
     767            1 :   TDB_coin_free (&old_coin);
     768            1 :   return ret;
     769              : }
     770              : 
     771              : 
     772              : /**
     773              :  * The checks to run, in order.
     774              :  */
     775              : static const struct TDB_Test tests[] = {
     776              :   { "recoup-refresh-unknown-coin",
     777              :     &check_unknown_coin },
     778              :   { "recoup-refresh-empty-coin",
     779              :     &check_empty_coin },
     780              :   { "recoup-refresh-recoup",
     781              :     &check_recoup_refresh },
     782              :   { "recoup-refresh-iterate",
     783              :     &check_iterate },
     784              :   { "recoup-refresh-replication",
     785              :     &check_replication },
     786              :   { "recoup-refresh-replay-latest",
     787              :     &check_replay_latest },
     788              :   { NULL, NULL }
     789              : };
     790              : 
     791              : 
     792              : int
     793            1 : main (int argc,
     794              :       char *const *argv)
     795              : {
     796              :   int ret;
     797              : 
     798            1 :   ret = TDB_main (argc,
     799              :                   argv,
     800              :                   "test-recoup-refresh",
     801              :                   "Tests for the exchangedb `recoup_refresh' table",
     802              :                   tests);
     803            1 :   TDB_account_free (&account);
     804            1 :   TDB_denom_free (&denom);
     805            1 :   return ret;
     806              : }
     807              : 
     808              : 
     809              : /* end of test_recoup_refresh.c */
        

Generated by: LCOV version 2.0-1