LCOV - code coverage report
Current view: top level - exchangedb - test_coin_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 200 200
Test Date: 2026-09-11 18:55:36 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_coin_history.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `coin_history`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_get_coin_transactions().
      23              :  *
      24              :  * Nothing writes to `coin_history` directly: every table that can spend or
      25              :  * credit a coin has an INSERT trigger that appends a row naming itself and
      26              :  * its own serial.  get_coin_transactions() walks those rows and looks each
      27              :  * one up in the table it names.  So the interesting part of this test is
      28              :  * spending one coin in as many different ways as possible and then checking
      29              :  * that every one of them comes back with the right type and amount.
      30              :  */
      31              : #include "test_common.h"
      32              : #include "exchange-database/do_purse_deposit.h"
      33              : #include "exchange-database/rollback.h"
      34              : #include "exchange-database/start.h"
      35              : #include "exchange-database/do_recoup.h"
      36              : #include "exchange-database/do_refresh.h"
      37              : #include "exchange-database/do_refund.h"
      38              : #include "exchange-database/get_coin_transactions.h"
      39              : #include "exchange-database/insert_reserve_open_deposit.h"
      40              : #include "exchange-database/update_to_refresh_revealed.h"
      41              : 
      42              : 
      43              : /**
      44              :  * Account the checks fund their reserves from.
      45              :  */
      46              : static struct TDB_Account account;
      47              : 
      48              : 
      49              : /**
      50              :  * Denomination the checks use.
      51              :  */
      52              : static struct TDB_Denom denom;
      53              : 
      54              : 
      55              : /**
      56              :  * A different denomination used among the refresh outputs.
      57              :  */
      58              : static struct TDB_Denom fresh_denom;
      59              : 
      60              : 
      61              : /**
      62              :  * The coin whose history we build up.
      63              :  */
      64              : static struct TALER_CoinPublicInfo coin;
      65              : 
      66              : 
      67              : /**
      68              :  * Row of @e coin in `known_coins`.
      69              :  */
      70              : static uint64_t known_coin_id;
      71              : 
      72              : 
      73              : /**
      74              :  * Reserve @e coin was withdrawn from.
      75              :  */
      76              : static struct TALER_ReservePublicKeyP reserve_pub;
      77              : 
      78              : 
      79              : /**
      80              :  * Row of the withdraw that created @e coin.
      81              :  */
      82              : static uint64_t withdraw_id;
      83              : 
      84              : 
      85              : /**
      86              :  * The deposit we later refund.
      87              :  */
      88              : static struct TDB_Deposit deposit;
      89              : 
      90              : 
      91              : /**
      92              :  * ETag of the history right after the deposit was refunded.
      93              :  */
      94              : static uint64_t etag_after_refund;
      95              : 
      96              : 
      97              : /**
      98              :  * Count the entries of @a tl and remember which types occurred.
      99              :  *
     100              :  * @param tl transaction list to walk
     101              :  * @param[out] types set to the bitmask of the types seen
     102              :  * @return number of entries in @a tl
     103              :  */
     104              : static unsigned int
     105            5 : summarize (const struct TALER_EXCHANGEDB_TransactionList *tl,
     106              :            unsigned int *types)
     107              : {
     108            5 :   unsigned int cnt = 0;
     109              : 
     110            5 :   *types = 0;
     111            5 :   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     112           25 :        NULL != pos;
     113           20 :        pos = pos->next)
     114              :   {
     115           20 :     cnt++;
     116           20 :     *types |= 1U << pos->type;
     117              :   }
     118            5 :   return cnt;
     119              : }
     120              : 
     121              : 
     122              : /**
     123              :  * Ask for the full history of @e coin.
     124              :  *
     125              :  * @param pg the database context
     126              :  * @param start_off offset to start from
     127              :  * @param etag_in ETag the caller already has
     128              :  * @param[out] etag_out set to the current ETag
     129              :  * @param[out] tlp set to the history
     130              :  * @return transaction status
     131              :  */
     132              : static enum GNUNET_DB_QueryStatus
     133            8 : history (struct TALER_EXCHANGEDB_PostgresContext *pg,
     134              :          uint64_t start_off,
     135              :          uint64_t etag_in,
     136              :          uint64_t *etag_out,
     137              :          struct TALER_EXCHANGEDB_TransactionList **tlp)
     138              : {
     139              :   struct TALER_Amount balance;
     140              :   struct TALER_DenominationHashP h_denom_pub;
     141              : 
     142            8 :   return TALER_EXCHANGEDB_get_coin_transactions (pg,
     143              :                                                  true,
     144              :                                                  &coin.coin_pub,
     145              :                                                  start_off,
     146              :                                                  etag_in,
     147              :                                                  etag_out,
     148              :                                                  &balance,
     149              :                                                  &h_denom_pub,
     150              :                                                  tlp);
     151              : }
     152              : 
     153              : 
     154              : /**
     155              :  * A coin the exchange never saw has no history.
     156              :  *
     157              :  * @param pg the database context
     158              :  * @return 0 on success
     159              :  */
     160              : static int
     161            1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     162              : {
     163            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     164              :   struct TALER_CoinSpendPublicKeyP unknown;
     165              :   struct TALER_Amount balance;
     166              :   struct TALER_DenominationHashP h_denom_pub;
     167            1 :   uint64_t etag = 42;
     168              : 
     169            1 :   TDB_account (pg,
     170              :                10,
     171              :                &account);
     172            1 :   TDB_denom (pg,
     173              :              10,
     174              :              "5",
     175              :              "0.1",
     176              :              &denom);
     177            1 :   TDB_FILL (unknown,
     178              :             99);
     179            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     180              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     181              :                                                   true,
     182              :                                                   &unknown,
     183              :                                                   0,
     184              :                                                   0,
     185              :                                                   &etag,
     186              :                                                   &balance,
     187              :                                                   &h_denom_pub,
     188              :                                                   &tl));
     189            1 :   FAILIF (NULL != tl);
     190            1 :   return 0;
     191              : }
     192              : 
     193              : 
     194              : /**
     195              :  * A coin that was withdrawn but never spent has no history either: the
     196              :  * `withdraw` table has no coin history trigger.
     197              :  *
     198              :  * @param pg the database context
     199              :  * @return 0 on success
     200              :  */
     201              : static int
     202            1 : check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     203              : {
     204            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     205            1 :   uint64_t etag = 42;
     206              : 
     207            1 :   TDB_reserve_in (pg,
     208              :                   &account,
     209              :                   11,
     210              :                   "10",
     211              :                   &reserve_pub);
     212            1 :   withdraw_id = TDB_withdraw (pg,
     213              :                               &denom,
     214              :                               &reserve_pub,
     215              :                               11,
     216              :                               "5");
     217            1 :   TDB_coin (pg,
     218              :             &denom,
     219              :             20,
     220              :             &coin,
     221              :             &known_coin_id);
     222            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     223              :           history (pg,
     224              :                    0,
     225              :                    0,
     226              :                    &etag,
     227              :                    &tl));
     228            1 :   FAILIF (NULL != tl);
     229            1 :   FAILIF (0 != TDB_count (pg,
     230              :                           "FROM coin_history"));
     231            1 :   return 0;
     232              : }
     233              : 
     234              : 
     235              : /**
     236              :  * A deposit and its refund show up as two entries.
     237              :  *
     238              :  * @param pg the database context
     239              :  * @return 0 on success
     240              :  */
     241              : static int
     242            1 : check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
     243              : {
     244            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     245              :   struct TALER_EXCHANGEDB_Refund refund;
     246            1 :   struct TALER_Amount deposit_fee = TDB_amount ("0.1");
     247              :   struct TALER_Amount balance;
     248            1 :   struct TALER_Amount expect = TDB_amount ("4.5");
     249              :   struct TALER_DenominationHashP h_denom_pub;
     250              :   unsigned int types;
     251            1 :   uint64_t etag = 0;
     252              :   bool not_found;
     253              :   bool refund_ok;
     254              :   bool gone;
     255              :   bool conflict;
     256              : 
     257            1 :   TDB_deposit (pg,
     258              :                &account,
     259              :                &coin,
     260              :                30,
     261              :                "1",
     262              :                "0.1",
     263              :                GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     264              :                GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     265              :                &deposit);
     266            1 :   FAILIF (1 != TDB_count (pg,
     267              :                           "FROM coin_history"));
     268              : 
     269            1 :   memset (&refund,
     270              :           0,
     271              :           sizeof (refund));
     272            1 :   refund.coin = coin;
     273            1 :   refund.details.merchant_pub = deposit.merchant_pub;
     274            1 :   TDB_FILL (refund.details.merchant_sig,
     275              :             31);
     276            1 :   refund.details.h_contract_terms = deposit.h_contract_terms;
     277            1 :   refund.details.rtransaction_id = 1;
     278            1 :   refund.details.refund_amount = TDB_amount ("0.5");
     279            1 :   refund.details.refund_fee = TDB_amount ("0");
     280            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     281              :           TALER_EXCHANGEDB_do_refund (pg,
     282              :                                       &refund,
     283              :                                       &deposit_fee,
     284              :                                       0,
     285              :                                       &not_found,
     286              :                                       &refund_ok,
     287              :                                       &gone,
     288              :                                       &conflict));
     289            1 :   FAILIF (not_found);
     290            1 :   FAILIF (! refund_ok);
     291            1 :   FAILIF (2 != TDB_count (pg,
     292              :                           "FROM coin_history"));
     293              : 
     294            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     295              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     296              :                                                   true,
     297              :                                                   &coin.coin_pub,
     298              :                                                   0,
     299              :                                                   0,
     300              :                                                   &etag,
     301              :                                                   &balance,
     302              :                                                   &h_denom_pub,
     303              :                                                   &tl));
     304            1 :   FAILIF_C (2 != summarize (tl,
     305              :                             &types),
     306              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     307            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)),
     308              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     309            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
     310              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     311              :   /* 5 - 1 + 0.5 */
     312            1 :   FAILIF_C (0 != TALER_amount_cmp (&balance,
     313              :                                    &expect),
     314              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     315            1 :   FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
     316              :                                 &denom.h_denom_pub),
     317              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     318            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     319            1 :   FAILIF (0 == etag);
     320            1 :   etag_after_refund = etag;
     321            1 :   return 0;
     322              : }
     323              : 
     324              : 
     325              : /**
     326              :  * A caller that is already up to date gets no list back, and the
     327              :  * transaction the lookup opened is not left dangling.
     328              :  *
     329              :  * @param pg the database context
     330              :  * @return 0 on success
     331              :  */
     332              : static int
     333            1 : check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg)
     334              : {
     335            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     336            1 :   uint64_t etag = 0;
     337              : 
     338            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     339              :           history (pg,
     340              :                    0,
     341              :                    etag_after_refund,
     342              :                    &etag,
     343              :                    &tl));
     344            1 :   FAILIF (NULL != tl);
     345            1 :   FAILIF (etag != etag_after_refund);
     346              :   /* the lookup must not leave a transaction open (it starts one of its
     347              :      own when begin_transaction is true) */
     348            1 :   FAILIF (NULL != pg->transaction_name);
     349              : 
     350              :   /* an ETag that is not the current one still returns the history */
     351            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     352              :           history (pg,
     353              :                    0,
     354              :                    etag_after_refund - 1,
     355              :                    &etag,
     356              :                    &tl));
     357            1 :   FAILIF (NULL == tl);
     358            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     359            1 :   FAILIF (NULL != pg->transaction_name);
     360            1 :   return 0;
     361              : }
     362              : 
     363              : 
     364              : /**
     365              :  * The offset skips everything up to and including it.
     366              :  *
     367              :  * @param pg the database context
     368              :  * @return 0 on success
     369              :  */
     370              : static int
     371            1 : check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg)
     372              : {
     373            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     374              :   unsigned int types;
     375            1 :   uint64_t etag = 0;
     376              : 
     377              :   /* everything after the deposit: only the refund is left */
     378            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     379              :           history (pg,
     380              :                    etag_after_refund - 1,
     381              :                    0,
     382              :                    &etag,
     383              :                    &tl));
     384            1 :   FAILIF_C (1 != summarize (tl,
     385              :                             &types),
     386              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     387            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
     388              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     389            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     390              : 
     391              :   /* everything after the last entry: nothing, but the ETag is still
     392              :      reported */
     393            1 :   tl = (void *) 1;
     394            1 :   etag = 0;
     395            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     396              :           history (pg,
     397              :                    etag_after_refund,
     398              :                    0,
     399              :                    &etag,
     400              :                    &tl));
     401            1 :   FAILIF (NULL != tl);
     402            1 :   FAILIF (etag != etag_after_refund);
     403            1 :   return 0;
     404              : }
     405              : 
     406              : 
     407              : /**
     408              :  * Spending the coin into a purse, on a reserve and in a melt adds one
     409              :  * entry each.
     410              :  *
     411              :  * @param pg the database context
     412              :  * @return 0 on success
     413              :  */
     414              : static int
     415            1 : check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg)
     416              : {
     417            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     418              :   struct TDB_Purse purse;
     419              :   struct TALER_CoinSpendSignatureP coin_sig;
     420              :   struct TALER_ReserveSignatureP reserve_sig;
     421            1 :   struct TALER_Amount one = TDB_amount ("1");
     422            1 :   struct TALER_Amount zero = TDB_amount ("0");
     423              :   unsigned int types;
     424            1 :   uint64_t etag = 0;
     425              :   bool balance_ok;
     426              :   bool too_late;
     427              :   bool conflict;
     428              :   bool insufficient_funds;
     429              : 
     430              :   /* into a purse */
     431            1 :   TDB_purse (pg,
     432              :              40,
     433              :              "1",
     434              :              GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     435              :              &purse);
     436            1 :   TDB_FILL (coin_sig,
     437              :             41);
     438            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     439              :           TALER_EXCHANGEDB_do_purse_deposit (pg,
     440              :                                              &purse.purse_pub,
     441              :                                              &coin.coin_pub,
     442              :                                              &one,
     443              :                                              &coin_sig,
     444              :                                              &one,
     445              :                                              &balance_ok,
     446              :                                              &too_late,
     447              :                                              &conflict));
     448            1 :   FAILIF (! balance_ok);
     449            1 :   FAILIF (conflict);
     450              : 
     451              :   /* to keep a reserve open */
     452            1 :   TDB_FILL (coin_sig,
     453              :             42);
     454            1 :   TDB_FILL (reserve_sig,
     455              :             42);
     456            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     457              :           TALER_EXCHANGEDB_insert_reserve_open_deposit (pg,
     458              :                                                         &coin,
     459              :                                                         &coin_sig,
     460              :                                                         known_coin_id,
     461              :                                                         &one,
     462              :                                                         &reserve_sig,
     463              :                                                         &reserve_pub,
     464              :                                                         &insufficient_funds));
     465            1 :   FAILIF (insufficient_funds);
     466              : 
     467              :   /* and into a melt */
     468              :   {
     469              :     struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     470            1 :     struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     471              :     struct TALER_Amount coin_balance;
     472              :     bool found;
     473            1 :     bool zombie_required = false;
     474              :     bool nonce_reuse;
     475              :     bool melt_balance_ok;
     476              :     uint32_t noreveal_index;
     477              :     enum GNUNET_DB_QueryStatus qs;
     478              : 
     479            1 :     memset (&rf,
     480              :             0,
     481              :             sizeof (rf));
     482            1 :     rf.coin.coin_pub = coin.coin_pub;
     483            1 :     rf.coin.denom_pub_hash = coin.denom_pub_hash;
     484            1 :     rf.coin.no_age_commitment = coin.no_age_commitment;
     485            1 :     TDB_FILL (rf.coin_sig,
     486              :               43);
     487            1 :     TDB_FILL (rf.rc,
     488              :               43);
     489            1 :     TDB_FILL (rf.refresh_seed,
     490              :               43);
     491            1 :     TDB_FILL (rf.planchets_h,
     492              :               43);
     493            1 :     TDB_FILL (rf.selected_h,
     494              :               44);
     495            1 :     rf.amount_with_fee = one;
     496            1 :     TDB_denom (pg,
     497              :                11,
     498              :                "0.1",
     499              :                "0.01",
     500              :                &fresh_denom);
     501            1 :     rf.num_coins = 3;
     502            1 :     rf.denom_serials = GNUNET_new_array (rf.num_coins,
     503              :                                          uint64_t);
     504              :     /* Deliberately neither sorted nor distinct. */
     505            1 :     rf.denom_serials[0] = fresh_denom.serial;
     506            1 :     rf.denom_serials[1] = denom.serial;
     507            1 :     rf.denom_serials[2] = fresh_denom.serial;
     508            1 :     rf.denom_sigs = GNUNET_new_array (
     509              :       rf.num_coins,
     510              :       struct TALER_BlindedDenominationSignature);
     511            4 :     for (unsigned int i = 0; i < rf.num_coins; i++)
     512            3 :       TDB_blinded_denom_sig (43 + i,
     513            3 :                              &rf.denom_sigs[i]);
     514            1 :     rf.noreveal_index = 1;
     515            1 :     rf.is_v27_refresh = true;
     516            1 :     rf.no_blinding_seed = true;
     517            1 :     qs = TALER_EXCHANGEDB_do_refresh (pg,
     518              :                                       &rf,
     519              :                                       &now,
     520              :                                       &found,
     521              :                                       &noreveal_index,
     522              :                                       &zombie_required,
     523              :                                       &nonce_reuse,
     524              :                                       &melt_balance_ok,
     525              :                                       &coin_balance);
     526            4 :     for (unsigned int i = 0; i < rf.num_coins; i++)
     527            3 :       TALER_blinded_denom_sig_free (&rf.denom_sigs[i]);
     528            1 :     GNUNET_free (rf.denom_sigs);
     529            1 :     GNUNET_free (rf.denom_serials);
     530            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
     531            1 :     FAILIF (! melt_balance_ok);
     532              : 
     533              :     /* The original output list is available before and after reveal. */
     534            3 :     for (unsigned int revealed = 0; revealed < 2; revealed++)
     535              :     {
     536            2 :       bool found_melt = false;
     537              : 
     538            2 :       if (revealed)
     539            1 :         FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     540              :                 TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
     541              :                                                               &rf.rc));
     542            2 :       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     543              :               history (pg,
     544              :                        0,
     545              :                        0,
     546              :                        &etag,
     547              :                        &tl));
     548            2 :       for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     549           12 :            NULL != pos;
     550           10 :            pos = pos->next)
     551              :       {
     552              :         const struct TALER_EXCHANGEDB_MeltListEntry *melt;
     553              : 
     554           10 :         if (TALER_EXCHANGEDB_TT_MELT != pos->type)
     555            8 :           continue;
     556            2 :         found_melt = true;
     557            2 :         melt = pos->details.melt;
     558            2 :         FAILIF_C (3 != melt->num_coins,
     559              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     560            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0],
     561              :                                       &fresh_denom.h_denom_pub),
     562              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     563            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1],
     564              :                                       &denom.h_denom_pub),
     565              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     566            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2],
     567              :                                       &fresh_denom.h_denom_pub),
     568              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     569              :       }
     570            2 :       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     571            2 :       tl = NULL;
     572            2 :       FAILIF (! found_melt);
     573              :     }
     574              :   }
     575              : 
     576            1 :   FAILIF (5 != TDB_count (pg,
     577              :                           "FROM coin_history"));
     578            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     579              :           history (pg,
     580              :                    0,
     581              :                    0,
     582              :                    &etag,
     583              :                    &tl));
     584            1 :   FAILIF_C (5 != summarize (tl,
     585              :                             &types),
     586              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     587            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)),
     588              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     589            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)),
     590              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     591            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)),
     592              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     593              : 
     594              :   /* the ETag is the highest coin history row of the list */
     595              :   {
     596            1 :     uint64_t max = 0;
     597              : 
     598            1 :     for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     599            6 :          NULL != pos;
     600            5 :          pos = pos->next)
     601            5 :       max = GNUNET_MAX (max,
     602              :                         pos->coin_history_id);
     603            1 :     FAILIF_C (etag != max,
     604              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     605              :   }
     606              : 
     607              :   /* the spends add up to what the coin no longer has */
     608              :   {
     609              :     struct TALER_Amount total;
     610            1 :     struct TALER_Amount expect = TDB_amount ("3.5");
     611              : 
     612            1 :     FAILIF_C (GNUNET_OK !=
     613              :               TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
     614              :                                                                   &zero,
     615              :                                                                   &total),
     616              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     617            1 :     FAILIF_C (0 != TALER_amount_cmp (&total,
     618              :                                      &expect),
     619              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     620              :   }
     621            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     622            1 :   return 0;
     623              : }
     624              : 
     625              : 
     626              : /**
     627              :  * Recouping the coin adds the last entry and empties it.
     628              :  *
     629              :  * @param pg the database context
     630              :  * @return 0 on success
     631              :  */
     632              : static int
     633            1 : check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     634              : {
     635            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     636              :   struct TALER_CoinSpendSignatureP coin_sig;
     637              :   union GNUNET_CRYPTO_BlindingSecretP coin_bks;
     638              :   struct GNUNET_TIME_Timestamp recoup_timestamp;
     639              :   struct TALER_Amount balance;
     640            1 :   struct TALER_Amount zero = TDB_amount ("0");
     641              :   struct TALER_DenominationHashP h_denom_pub;
     642              :   unsigned int types;
     643            1 :   uint64_t etag = 0;
     644              :   bool recoup_ok;
     645              :   bool internal_failure;
     646              : 
     647            1 :   TDB_FILL (coin_sig,
     648              :             50);
     649            1 :   TDB_FILL (coin_bks,
     650              :             50);
     651              :   /* in/out: the caller picks the time, the callee only overwrites it if
     652              :      the coin was recouped before */
     653            1 :   recoup_timestamp = GNUNET_TIME_timestamp_get ();
     654            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     655              :           TALER_EXCHANGEDB_do_recoup (pg,
     656              :                                       &reserve_pub,
     657              :                                       withdraw_id,
     658              :                                       &coin_bks,
     659              :                                       &coin.coin_pub,
     660              :                                       known_coin_id,
     661              :                                       &coin_sig,
     662              :                                       &recoup_timestamp,
     663              :                                       &recoup_ok,
     664              :                                       &internal_failure));
     665            1 :   FAILIF (internal_failure);
     666            1 :   FAILIF (! recoup_ok);
     667            1 :   FAILIF (6 != TDB_count (pg,
     668              :                           "FROM coin_history"));
     669              : 
     670            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     671              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     672              :                                                   true,
     673              :                                                   &coin.coin_pub,
     674              :                                                   0,
     675              :                                                   0,
     676              :                                                   &etag,
     677              :                                                   &balance,
     678              :                                                   &h_denom_pub,
     679              :                                                   &tl));
     680            1 :   FAILIF_C (6 != summarize (tl,
     681              :                             &types),
     682              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     683            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)),
     684              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     685            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     686            1 :   FAILIF (0 != TALER_amount_cmp (&balance,
     687              :                                  &zero));
     688            1 :   return 0;
     689              : }
     690              : 
     691              : 
     692              : /**
     693              :  * With begin_transaction false the caller's transaction is used and left
     694              :  * open.
     695              :  *
     696              :  * @param pg the database context
     697              :  * @return 0 on success
     698              :  */
     699              : static int
     700            1 : check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg)
     701              : {
     702            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     703              :   struct TALER_Amount balance;
     704              :   struct TALER_DenominationHashP h_denom_pub;
     705              :   unsigned int types;
     706            1 :   uint64_t etag = 0;
     707              : 
     708            1 :   FAILIF (GNUNET_OK !=
     709              :           TALER_EXCHANGEDB_start (pg,
     710              :                                   "coin-history"));
     711            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     712              :             TALER_EXCHANGEDB_get_coin_transactions (pg,
     713              :                                                     false,
     714              :                                                     &coin.coin_pub,
     715              :                                                     0,
     716              :                                                     0,
     717              :                                                     &etag,
     718              :                                                     &balance,
     719              :                                                     &h_denom_pub,
     720              :                                                     &tl),
     721              :             TALER_EXCHANGEDB_rollback (pg));
     722            1 :   FAILIF_C (6 != summarize (tl,
     723              :                             &types),
     724              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     725              :             TALER_EXCHANGEDB_rollback (pg));
     726            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     727            1 :   FAILIF_C (NULL == pg->transaction_name,
     728              :             TALER_EXCHANGEDB_rollback (pg));
     729            1 :   TALER_EXCHANGEDB_rollback (pg);
     730            1 :   return 0;
     731              : }
     732              : 
     733              : 
     734              : /**
     735              :  * The checks to run, in order.
     736              :  */
     737              : static const struct TDB_Test tests[] = {
     738              :   { "coin-history-unknown-coin",
     739              :     &check_unknown_coin },
     740              :   { "coin-history-fresh-coin",
     741              :     &check_fresh_coin },
     742              :   { "coin-history-deposit-and-refund",
     743              :     &check_deposit_and_refund },
     744              :   { "coin-history-etag",
     745              :     &check_etag },
     746              :   { "coin-history-offset",
     747              :     &check_offset },
     748              :   { "coin-history-other-spends",
     749              :     &check_other_spends },
     750              :   { "coin-history-recoup",
     751              :     &check_recoup },
     752              :   { "coin-history-in-transaction",
     753              :     &check_in_transaction },
     754              :   { NULL, NULL }
     755              : };
     756              : 
     757              : 
     758              : int
     759            1 : main (int argc,
     760              :       char *const *argv)
     761              : {
     762              :   int ret;
     763              : 
     764            1 :   ret = TDB_main (argc,
     765              :                   argv,
     766              :                   "test-coin-history",
     767              :                   "Tests for the exchangedb `coin_history' table",
     768              :                   tests);
     769            1 :   TDB_coin_free (&coin);
     770            1 :   TDB_denom_free (&denom);
     771            1 :   TDB_denom_free (&fresh_denom);
     772            1 :   TDB_account_free (&account);
     773            1 :   return ret;
     774              : }
     775              : 
     776              : 
     777              : /* end of test_coin_history.c */
        

Generated by: LCOV version 2.0-1