LCOV - code coverage report
Current view: top level - exchangedb - test_recoup.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.1 % 157 154
Test Date: 2026-10-10 21:00:14 Functions: 100.0 % 10 10

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

Generated by: LCOV version 2.0-1