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

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file exchangedb/test_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_recoup_refresh.h"
      37              : #include "exchange-database/do_refresh.h"
      38              : #include "exchange-database/do_refund.h"
      39              : #include "exchange-database/get_coin_transactions.h"
      40              : #include "exchange-database/insert_reserve_open_deposit.h"
      41              : #include "exchange-database/update_to_refresh_revealed.h"
      42              : 
      43              : 
      44              : /**
      45              :  * Account the checks fund their reserves from.
      46              :  */
      47              : static struct TDB_Account account;
      48              : 
      49              : 
      50              : /**
      51              :  * Denomination the checks use.
      52              :  */
      53              : static struct TDB_Denom denom;
      54              : 
      55              : 
      56              : /**
      57              :  * A different denomination used among the refresh outputs.
      58              :  */
      59              : static struct TDB_Denom fresh_denom;
      60              : 
      61              : 
      62              : /**
      63              :  * The coin whose history we build up.
      64              :  */
      65              : static struct TALER_CoinPublicInfo coin;
      66              : 
      67              : 
      68              : /**
      69              :  * Row of @e coin in `known_coins`.
      70              :  */
      71              : static uint64_t known_coin_id;
      72              : 
      73              : 
      74              : /**
      75              :  * Reserve @e coin was withdrawn from.
      76              :  */
      77              : static struct TALER_ReservePublicKeyP reserve_pub;
      78              : 
      79              : 
      80              : /**
      81              :  * Row of the withdraw that created @e coin.
      82              :  */
      83              : static uint64_t withdraw_id;
      84              : 
      85              : 
      86              : /**
      87              :  * The deposit we later refund.
      88              :  */
      89              : static struct TDB_Deposit deposit;
      90              : 
      91              : 
      92              : /**
      93              :  * ETag of the history right after the deposit was refunded.
      94              :  */
      95              : static uint64_t etag_after_refund;
      96              : 
      97              : 
      98              : /**
      99              :  * Count the entries of @a tl and remember which types occurred.
     100              :  *
     101              :  * @param tl transaction list to walk
     102              :  * @param[out] types set to the bitmask of the types seen
     103              :  * @return number of entries in @a tl
     104              :  */
     105              : static unsigned int
     106            7 : summarize (const struct TALER_EXCHANGEDB_TransactionList *tl,
     107              :            unsigned int *types)
     108              : {
     109            7 :   unsigned int cnt = 0;
     110              : 
     111            7 :   *types = 0;
     112            7 :   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     113           35 :        NULL != pos;
     114           28 :        pos = pos->next)
     115              :   {
     116           28 :     cnt++;
     117           28 :     *types |= 1U << pos->type;
     118              :   }
     119            7 :   return cnt;
     120              : }
     121              : 
     122              : 
     123              : /**
     124              :  * Ask for the full history of @e coin.
     125              :  *
     126              :  * @param pg the database context
     127              :  * @param start_off offset to start from
     128              :  * @param etag_in ETag the caller already has
     129              :  * @param[out] etag_out set to the current ETag
     130              :  * @param[out] tlp set to the history
     131              :  * @return transaction status
     132              :  */
     133              : static enum GNUNET_DB_QueryStatus
     134            8 : history (struct TALER_EXCHANGEDB_PostgresContext *pg,
     135              :          uint64_t start_off,
     136              :          uint64_t etag_in,
     137              :          uint64_t *etag_out,
     138              :          struct TALER_EXCHANGEDB_TransactionList **tlp)
     139              : {
     140              :   struct TALER_Amount balance;
     141              :   struct TALER_DenominationHashP h_denom_pub;
     142              : 
     143            8 :   return TALER_EXCHANGEDB_get_coin_transactions (pg,
     144              :                                                  true,
     145              :                                                  &coin.coin_pub,
     146              :                                                  start_off,
     147              :                                                  etag_in,
     148              :                                                  etag_out,
     149              :                                                  &balance,
     150              :                                                  &h_denom_pub,
     151              :                                                  tlp);
     152              : }
     153              : 
     154              : 
     155              : /**
     156              :  * A coin the exchange never saw has no history.
     157              :  *
     158              :  * @param pg the database context
     159              :  * @return 0 on success
     160              :  */
     161              : static int
     162            1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     163              : {
     164            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     165              :   struct TALER_CoinSpendPublicKeyP unknown;
     166              :   struct TALER_Amount balance;
     167              :   struct TALER_DenominationHashP h_denom_pub;
     168            1 :   uint64_t etag = 42;
     169              : 
     170            1 :   TDB_account (pg,
     171              :                10,
     172              :                &account);
     173            1 :   TDB_denom (pg,
     174              :              10,
     175              :              "5",
     176              :              "0.1",
     177              :              &denom);
     178            1 :   TDB_FILL (unknown,
     179              :             99);
     180            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     181              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     182              :                                                   true,
     183              :                                                   &unknown,
     184              :                                                   0,
     185              :                                                   0,
     186              :                                                   &etag,
     187              :                                                   &balance,
     188              :                                                   &h_denom_pub,
     189              :                                                   &tl));
     190            1 :   FAILIF (NULL != tl);
     191            1 :   return 0;
     192              : }
     193              : 
     194              : 
     195              : /**
     196              :  * A coin that was withdrawn but never spent has no history either: the
     197              :  * `withdraw` table has no coin history trigger.
     198              :  *
     199              :  * @param pg the database context
     200              :  * @return 0 on success
     201              :  */
     202              : static int
     203            1 : check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
     204              : {
     205            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     206            1 :   uint64_t etag = 42;
     207              : 
     208            1 :   TDB_reserve_in (pg,
     209              :                   &account,
     210              :                   11,
     211              :                   "10",
     212              :                   &reserve_pub);
     213            1 :   withdraw_id = TDB_withdraw (pg,
     214              :                               &denom,
     215              :                               &reserve_pub,
     216              :                               11,
     217              :                               "5");
     218            1 :   TDB_coin (pg,
     219              :             &denom,
     220              :             20,
     221              :             &coin,
     222              :             &known_coin_id);
     223            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     224              :           history (pg,
     225              :                    0,
     226              :                    0,
     227              :                    &etag,
     228              :                    &tl));
     229            1 :   FAILIF (NULL != tl);
     230            1 :   FAILIF (0 != TDB_count (pg,
     231              :                           "FROM coin_history"));
     232            1 :   return 0;
     233              : }
     234              : 
     235              : 
     236              : /**
     237              :  * A deposit and its refund show up as two entries.
     238              :  *
     239              :  * @param pg the database context
     240              :  * @return 0 on success
     241              :  */
     242              : static int
     243            1 : check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
     244              : {
     245            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     246              :   struct TALER_EXCHANGEDB_Refund refund;
     247            1 :   struct TALER_Amount deposit_fee = TDB_amount ("0.1");
     248              :   struct TALER_Amount balance;
     249            1 :   struct TALER_Amount expect = TDB_amount ("4.5");
     250              :   struct TALER_DenominationHashP h_denom_pub;
     251              :   unsigned int types;
     252            1 :   uint64_t etag = 0;
     253              :   bool not_found;
     254              :   bool refund_ok;
     255              :   bool gone;
     256              :   bool conflict;
     257              : 
     258            1 :   TDB_deposit (pg,
     259              :                &account,
     260              :                &coin,
     261              :                30,
     262              :                "1",
     263              :                "0.1",
     264              :                GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     265              :                GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     266              :                &deposit);
     267            1 :   FAILIF (1 != TDB_count (pg,
     268              :                           "FROM coin_history"));
     269              : 
     270            1 :   memset (&refund,
     271              :           0,
     272              :           sizeof (refund));
     273            1 :   refund.coin = coin;
     274            1 :   refund.details.merchant_pub = deposit.merchant_pub;
     275            1 :   TDB_FILL (refund.details.merchant_sig,
     276              :             31);
     277            1 :   refund.details.h_contract_terms = deposit.h_contract_terms;
     278            1 :   refund.details.rtransaction_id = 1;
     279            1 :   refund.details.refund_amount = TDB_amount ("0.5");
     280            1 :   refund.details.refund_fee = TDB_amount ("0");
     281            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     282              :           TALER_EXCHANGEDB_do_refund (pg,
     283              :                                       &refund,
     284              :                                       &deposit_fee,
     285              :                                       0,
     286              :                                       &not_found,
     287              :                                       &refund_ok,
     288              :                                       &gone,
     289              :                                       &conflict));
     290            1 :   FAILIF (not_found);
     291            1 :   FAILIF (! refund_ok);
     292            1 :   FAILIF (2 != TDB_count (pg,
     293              :                           "FROM coin_history"));
     294              : 
     295            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     296              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     297              :                                                   true,
     298              :                                                   &coin.coin_pub,
     299              :                                                   0,
     300              :                                                   0,
     301              :                                                   &etag,
     302              :                                                   &balance,
     303              :                                                   &h_denom_pub,
     304              :                                                   &tl));
     305            1 :   FAILIF_C (2 != summarize (tl,
     306              :                             &types),
     307              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     308            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)),
     309              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     310            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
     311              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     312              :   /* 5 - 1 + 0.5 */
     313            1 :   FAILIF_C (0 != TALER_amount_cmp (&balance,
     314              :                                    &expect),
     315              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     316            1 :   FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
     317              :                                 &denom.h_denom_pub),
     318              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     319            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     320            1 :   FAILIF (0 == etag);
     321            1 :   etag_after_refund = etag;
     322            1 :   return 0;
     323              : }
     324              : 
     325              : 
     326              : /**
     327              :  * A caller that is already up to date gets no list back, and the
     328              :  * transaction the lookup opened is not left dangling.
     329              :  *
     330              :  * @param pg the database context
     331              :  * @return 0 on success
     332              :  */
     333              : static int
     334            1 : check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg)
     335              : {
     336            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
     337            1 :   uint64_t etag = 0;
     338              : 
     339            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     340              :           history (pg,
     341              :                    0,
     342              :                    etag_after_refund,
     343              :                    &etag,
     344              :                    &tl));
     345            1 :   FAILIF (NULL != tl);
     346            1 :   FAILIF (etag != etag_after_refund);
     347              :   /* the lookup must not leave a transaction open (it starts one of its
     348              :      own when begin_transaction is true) */
     349            1 :   FAILIF (NULL != pg->transaction_name);
     350              : 
     351              :   /* an ETag that is not the current one still returns the history */
     352            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     353              :           history (pg,
     354              :                    0,
     355              :                    etag_after_refund - 1,
     356              :                    &etag,
     357              :                    &tl));
     358            1 :   FAILIF (NULL == tl);
     359            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     360            1 :   FAILIF (NULL != pg->transaction_name);
     361            1 :   return 0;
     362              : }
     363              : 
     364              : 
     365              : /**
     366              :  * The offset skips everything up to and including it.
     367              :  *
     368              :  * @param pg the database context
     369              :  * @return 0 on success
     370              :  */
     371              : static int
     372            1 : check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg)
     373              : {
     374            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     375              :   unsigned int types;
     376            1 :   uint64_t etag = 0;
     377              : 
     378              :   /* everything after the deposit: only the refund is left */
     379            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     380              :           history (pg,
     381              :                    etag_after_refund - 1,
     382              :                    0,
     383              :                    &etag,
     384              :                    &tl));
     385            1 :   FAILIF_C (1 != summarize (tl,
     386              :                             &types),
     387              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     388            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
     389              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     390            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     391              : 
     392              :   /* everything after the last entry: nothing, but the ETag is still
     393              :      reported */
     394            1 :   tl = (void *) 1;
     395            1 :   etag = 0;
     396            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     397              :           history (pg,
     398              :                    etag_after_refund,
     399              :                    0,
     400              :                    &etag,
     401              :                    &tl));
     402            1 :   FAILIF (NULL != tl);
     403            1 :   FAILIF (etag != etag_after_refund);
     404            1 :   return 0;
     405              : }
     406              : 
     407              : 
     408              : /**
     409              :  * Spending the coin into a purse, on a reserve and in a melt adds one
     410              :  * entry each.
     411              :  *
     412              :  * @param pg the database context
     413              :  * @return 0 on success
     414              :  */
     415              : static int
     416            1 : check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg)
     417              : {
     418            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     419              :   struct TDB_Purse purse;
     420              :   struct TALER_CoinSpendSignatureP coin_sig;
     421              :   struct TALER_ReserveSignatureP reserve_sig;
     422            1 :   struct TALER_Amount one = TDB_amount ("1");
     423            1 :   struct TALER_Amount zero = TDB_amount ("0");
     424              :   unsigned int types;
     425            1 :   uint64_t etag = 0;
     426              :   bool balance_ok;
     427              :   bool too_late;
     428              :   bool conflict;
     429              :   bool insufficient_funds;
     430              : 
     431              :   /* into a purse */
     432            1 :   TDB_purse (pg,
     433              :              40,
     434              :              "1",
     435              :              GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     436              :              &purse);
     437            1 :   TDB_FILL (coin_sig,
     438              :             41);
     439            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     440              :           TALER_EXCHANGEDB_do_purse_deposit (pg,
     441              :                                              &purse.purse_pub,
     442              :                                              &coin.coin_pub,
     443              :                                              &one,
     444              :                                              &coin_sig,
     445              :                                              &one,
     446              :                                              &balance_ok,
     447              :                                              &too_late,
     448              :                                              &conflict));
     449            1 :   FAILIF (! balance_ok);
     450            1 :   FAILIF (conflict);
     451              : 
     452              :   /* to keep a reserve open */
     453            1 :   TDB_FILL (coin_sig,
     454              :             42);
     455            1 :   TDB_FILL (reserve_sig,
     456              :             42);
     457            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     458              :           TALER_EXCHANGEDB_insert_reserve_open_deposit (pg,
     459              :                                                         &coin,
     460              :                                                         &coin_sig,
     461              :                                                         known_coin_id,
     462              :                                                         &one,
     463              :                                                         &reserve_sig,
     464              :                                                         &reserve_pub,
     465              :                                                         &insufficient_funds));
     466            1 :   FAILIF (insufficient_funds);
     467              : 
     468              :   /* and into a melt */
     469              :   {
     470              :     struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
     471            1 :     struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     472              :     struct TALER_Amount coin_balance;
     473              :     bool found;
     474            1 :     bool zombie_required = false;
     475              :     bool nonce_reuse;
     476              :     bool melt_balance_ok;
     477              :     uint32_t noreveal_index;
     478              :     enum GNUNET_DB_QueryStatus qs;
     479              : 
     480            1 :     memset (&rf,
     481              :             0,
     482              :             sizeof (rf));
     483            1 :     rf.coin.coin_pub = coin.coin_pub;
     484            1 :     rf.coin.denom_pub_hash = coin.denom_pub_hash;
     485            1 :     rf.coin.no_age_commitment = coin.no_age_commitment;
     486            1 :     TDB_FILL (rf.coin_sig,
     487              :               43);
     488            1 :     TDB_FILL (rf.rc,
     489              :               43);
     490            1 :     TDB_FILL (rf.refresh_seed,
     491              :               43);
     492            1 :     TDB_FILL (rf.planchets_h,
     493              :               43);
     494            1 :     TDB_FILL (rf.selected_h,
     495              :               44);
     496            1 :     rf.amount_with_fee = one;
     497            1 :     TDB_denom (pg,
     498              :                11,
     499              :                "0.1",
     500              :                "0.01",
     501              :                &fresh_denom);
     502            1 :     rf.num_coins = 3;
     503            1 :     rf.denom_serials = GNUNET_new_array (rf.num_coins,
     504              :                                          uint64_t);
     505              :     /* Deliberately neither sorted nor distinct. */
     506            1 :     rf.denom_serials[0] = fresh_denom.serial;
     507            1 :     rf.denom_serials[1] = denom.serial;
     508            1 :     rf.denom_serials[2] = fresh_denom.serial;
     509            1 :     rf.denom_sigs = GNUNET_new_array (
     510              :       rf.num_coins,
     511              :       struct TALER_BlindedDenominationSignature);
     512            4 :     for (unsigned int i = 0; i < rf.num_coins; i++)
     513            3 :       TDB_blinded_denom_sig (43 + i,
     514            3 :                              &rf.denom_sigs[i]);
     515            1 :     rf.noreveal_index = 1;
     516            1 :     rf.is_v27_refresh = true;
     517            1 :     rf.no_blinding_seed = true;
     518            1 :     qs = TALER_EXCHANGEDB_do_refresh (pg,
     519              :                                       &rf,
     520              :                                       &now,
     521              :                                       &found,
     522              :                                       &noreveal_index,
     523              :                                       &zombie_required,
     524              :                                       &nonce_reuse,
     525              :                                       &melt_balance_ok,
     526              :                                       &coin_balance);
     527            4 :     for (unsigned int i = 0; i < rf.num_coins; i++)
     528            3 :       TALER_blinded_denom_sig_free (&rf.denom_sigs[i]);
     529            1 :     GNUNET_free (rf.denom_sigs);
     530            1 :     GNUNET_free (rf.denom_serials);
     531            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
     532            1 :     FAILIF (! melt_balance_ok);
     533              : 
     534              :     /* The original output list is available before and after reveal. */
     535            3 :     for (unsigned int revealed = 0; revealed < 2; revealed++)
     536              :     {
     537            2 :       bool found_melt = false;
     538              : 
     539            2 :       if (revealed)
     540            1 :         FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     541              :                 TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
     542              :                                                              &rf.rc));
     543            2 :       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     544              :               history (pg,
     545              :                        0,
     546              :                        0,
     547              :                        &etag,
     548              :                        &tl));
     549            2 :       for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     550           12 :            NULL != pos;
     551           10 :            pos = pos->next)
     552              :       {
     553              :         const struct TALER_EXCHANGEDB_MeltListEntry *melt;
     554              : 
     555           10 :         if (TALER_EXCHANGEDB_TT_MELT != pos->type)
     556            8 :           continue;
     557            2 :         found_melt = true;
     558            2 :         melt = pos->details.melt;
     559            2 :         FAILIF_C (3 != melt->num_coins,
     560              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     561            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0],
     562              :                                       &fresh_denom.h_denom_pub),
     563              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     564            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1],
     565              :                                       &denom.h_denom_pub),
     566              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     567            2 :         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2],
     568              :                                       &fresh_denom.h_denom_pub),
     569              :                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     570              :       }
     571            2 :       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     572            2 :       tl = NULL;
     573            2 :       FAILIF (! found_melt);
     574              :     }
     575              :   }
     576              : 
     577            1 :   FAILIF (5 != TDB_count (pg,
     578              :                           "FROM coin_history"));
     579            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     580              :           history (pg,
     581              :                    0,
     582              :                    0,
     583              :                    &etag,
     584              :                    &tl));
     585            1 :   FAILIF_C (5 != summarize (tl,
     586              :                             &types),
     587              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     588            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)),
     589              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     590            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)),
     591              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     592            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)),
     593              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     594              : 
     595              :   /* the ETag is the highest coin history row of the list */
     596              :   {
     597            1 :     uint64_t max = 0;
     598              : 
     599            1 :     for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     600            6 :          NULL != pos;
     601            5 :          pos = pos->next)
     602            5 :       max = GNUNET_MAX (max,
     603              :                         pos->coin_history_id);
     604            1 :     FAILIF_C (etag != max,
     605              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     606              :   }
     607              : 
     608              :   /* the spends add up to what the coin no longer has */
     609              :   {
     610              :     struct TALER_Amount total;
     611            1 :     struct TALER_Amount expect = TDB_amount ("3.5");
     612              : 
     613            1 :     FAILIF_C (GNUNET_OK !=
     614              :               TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
     615              :                                                                   &zero,
     616              :                                                                   &total),
     617              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     618            1 :     FAILIF_C (0 != TALER_amount_cmp (&total,
     619              :                                      &expect),
     620              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     621              :   }
     622            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     623            1 :   return 0;
     624              : }
     625              : 
     626              : 
     627              : /**
     628              :  * Recouping the coin adds the last entry and empties it.
     629              :  *
     630              :  * @param pg the database context
     631              :  * @return 0 on success
     632              :  */
     633              : static int
     634            1 : check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     635              : {
     636            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     637              :   struct TALER_CoinSpendSignatureP coin_sig;
     638              :   union GNUNET_CRYPTO_BlindingSecretP coin_bks;
     639              :   struct GNUNET_TIME_Timestamp recoup_timestamp;
     640              :   struct TALER_Amount recoup_amount;
     641              :   struct TALER_Amount balance;
     642            1 :   struct TALER_Amount zero = TDB_amount ("0");
     643              :   struct TALER_DenominationHashP h_denom_pub;
     644              :   unsigned int types;
     645            1 :   uint64_t etag = 0;
     646              :   bool recoup_ok;
     647              :   bool internal_failure;
     648              : 
     649            1 :   TDB_FILL (coin_sig,
     650              :             50);
     651            1 :   TDB_FILL (coin_bks,
     652              :             50);
     653              :   /* in/out: the caller picks the time, the callee only overwrites it if
     654              :      the coin was recouped before */
     655            1 :   recoup_timestamp = GNUNET_TIME_timestamp_get ();
     656            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     657              :           TALER_EXCHANGEDB_do_recoup (pg,
     658              :                                       &reserve_pub,
     659              :                                       withdraw_id,
     660              :                                       3,
     661              :                                       &coin_bks,
     662              :                                       &coin.coin_pub,
     663              :                                       known_coin_id,
     664              :                                       &coin_sig,
     665              :                                       &recoup_timestamp,
     666              :                                       &recoup_amount,
     667              :                                       &recoup_ok,
     668              :                                       &internal_failure));
     669            1 :   FAILIF (internal_failure);
     670            1 :   FAILIF (! recoup_ok);
     671            1 :   FAILIF (6 != TDB_count (pg,
     672              :                           "FROM coin_history"));
     673              : 
     674            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     675              :           TALER_EXCHANGEDB_get_coin_transactions (pg,
     676              :                                                   true,
     677              :                                                   &coin.coin_pub,
     678              :                                                   0,
     679              :                                                   0,
     680              :                                                   &etag,
     681              :                                                   &balance,
     682              :                                                   &h_denom_pub,
     683              :                                                   &tl));
     684            1 :   FAILIF_C (6 != summarize (tl,
     685              :                             &types),
     686              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     687            1 :   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)),
     688              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     689              :   /* the entry names the withdraw operation (by its commitment, seeded
     690              :      like the withdraw in check_fresh_coin()) and the coin's position */
     691              :   {
     692            1 :     const struct TALER_EXCHANGEDB_RecoupListEntry *recoup = NULL;
     693              :     struct TALER_HashBlindedPlanchetsP planchets_h;
     694              : 
     695            1 :     TDB_FILL (planchets_h,
     696              :               11);
     697            1 :     for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     698            7 :          NULL != pos;
     699            6 :          pos = pos->next)
     700            6 :       if (TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW == pos->type)
     701            1 :         recoup = pos->details.recoup;
     702            1 :     FAILIF_C (NULL == recoup,
     703              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     704            1 :     FAILIF_C (3 != recoup->coin_index,
     705              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     706            1 :     FAILIF_C (0 != GNUNET_memcmp (&recoup->planchets_h,
     707              :                                   &planchets_h),
     708              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     709            1 :     FAILIF_C (0 != GNUNET_memcmp (&recoup->reserve_pub,
     710              :                                   &reserve_pub),
     711              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     712            1 :     FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_sig,
     713              :                                   &coin_sig),
     714              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     715            1 :     FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_blind,
     716              :                                   &coin_bks),
     717              :               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
     718              :   }
     719            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     720            1 :   FAILIF (0 != TALER_amount_cmp (&balance,
     721              :                                  &zero));
     722            1 :   return 0;
     723              : }
     724              : 
     725              : 
     726              : /**
     727              :  * With begin_transaction false the caller's transaction is used and left
     728              :  * open.
     729              :  *
     730              :  * @param pg the database context
     731              :  * @return 0 on success
     732              :  */
     733              : static int
     734            1 : check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg)
     735              : {
     736            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     737              :   struct TALER_Amount balance;
     738              :   struct TALER_DenominationHashP h_denom_pub;
     739              :   unsigned int types;
     740            1 :   uint64_t etag = 0;
     741              : 
     742            1 :   FAILIF (GNUNET_OK !=
     743              :           TALER_EXCHANGEDB_start (pg,
     744              :                                   "coin-history"));
     745            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     746              :             TALER_EXCHANGEDB_get_coin_transactions (pg,
     747              :                                                     false,
     748              :                                                     &coin.coin_pub,
     749              :                                                     0,
     750              :                                                     0,
     751              :                                                     &etag,
     752              :                                                     &balance,
     753              :                                                     &h_denom_pub,
     754              :                                                     &tl),
     755              :             TALER_EXCHANGEDB_rollback (pg));
     756            1 :   FAILIF_C (6 != summarize (tl,
     757              :                             &types),
     758              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     759              :             TALER_EXCHANGEDB_rollback (pg));
     760            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     761            1 :   FAILIF_C (NULL == pg->transaction_name,
     762              :             TALER_EXCHANGEDB_rollback (pg));
     763            1 :   TALER_EXCHANGEDB_rollback (pg);
     764            1 :   return 0;
     765              : }
     766              : 
     767              : 
     768              : /**
     769              :  * Recouping a coin that was refreshed from @e coin credits @e coin and adds
     770              :  * a RECOUP-REFRESH-RECEIVER entry to its history that names the refresh
     771              :  * operation (by its commitment, seeded like the melt in
     772              :  * check_other_spends()), the recouped coin and its position in the batch.
     773              :  * The recouped coin's own history gets the matching RECOUP-REFRESH entry.
     774              :  *
     775              :  * @param pg the database context
     776              :  * @return 0 on success
     777              :  */
     778              : static int
     779            1 : check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
     780              : {
     781            1 :   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
     782            1 :   const struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr = NULL;
     783              :   struct TALER_CoinPublicInfo fresh_coin;
     784              :   struct TALER_RefreshCommitmentP rc;
     785              :   struct TALER_CoinSpendSignatureP coin_sig;
     786              :   union GNUNET_CRYPTO_BlindingSecretP coin_bks;
     787              :   struct GNUNET_TIME_Timestamp recoup_timestamp;
     788              :   struct TALER_Amount recoup_amount;
     789              :   struct TALER_Amount balance;
     790            1 :   struct TALER_Amount zero = TDB_amount ("0");
     791            1 :   struct TALER_Amount credited = TDB_amount ("0.1");
     792              :   struct TALER_DenominationHashP h_denom_pub;
     793              :   uint64_t fresh_known_coin_id;
     794              :   uint64_t refresh_id;
     795            1 :   uint64_t etag = 0;
     796              :   unsigned int types;
     797              :   bool recoup_ok;
     798              :   bool internal_failure;
     799              : 
     800            1 :   TDB_FILL (rc,
     801              :             43);
     802              :   /* row of the melt made in check_other_spends() */
     803              :   {
     804            1 :     struct GNUNET_PQ_QueryParam params[] = {
     805            1 :       GNUNET_PQ_query_param_auto_from_type (&rc),
     806              :       GNUNET_PQ_query_param_end
     807              :     };
     808            1 :     struct GNUNET_PQ_ResultSpec rs[] = {
     809            1 :       GNUNET_PQ_result_spec_uint64 ("refresh_id",
     810              :                                     &refresh_id),
     811              :       GNUNET_PQ_result_spec_end
     812              :     };
     813              : 
     814            1 :     FAILIF (GNUNET_OK !=
     815              :             GNUNET_PQ_prepare_anon (pg->conn,
     816              :                                     "SELECT refresh_id"
     817              :                                     " FROM refresh"
     818              :                                     " WHERE rc=$1;"));
     819            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     820              :             GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
     821              :                                                       "",
     822              :                                                       params,
     823              :                                                       rs));
     824              :   }
     825            1 :   TDB_coin (pg,
     826              :             &fresh_denom,
     827              :             60,
     828              :             &fresh_coin,
     829              :             &fresh_known_coin_id);
     830            1 :   TDB_FILL (coin_sig,
     831              :             60);
     832            1 :   TDB_FILL (coin_bks,
     833              :             60);
     834            1 :   recoup_timestamp = GNUNET_TIME_timestamp_get ();
     835            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     836              :             TALER_EXCHANGEDB_do_recoup_refresh (pg,
     837              :                                                 &coin.coin_pub,
     838              :                                                 refresh_id,
     839              :                                                 2,
     840              :                                                 &coin_bks,
     841              :                                                 &fresh_coin.coin_pub,
     842              :                                                 fresh_known_coin_id,
     843              :                                                 &coin_sig,
     844              :                                                 &recoup_timestamp,
     845              :                                                 &recoup_amount,
     846              :                                                 &recoup_ok,
     847              :                                                 &internal_failure),
     848              :             TDB_coin_free (&fresh_coin));
     849            1 :   FAILIF_C (internal_failure || ! recoup_ok,
     850              :             TDB_coin_free (&fresh_coin));
     851            1 :   FAILIF_C (0 != TALER_amount_cmp (&recoup_amount,
     852              :                                    &credited),
     853              :             TDB_coin_free (&fresh_coin));
     854              : 
     855              :   /* the old coin is credited and sees the receiver entry */
     856            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     857              :             TALER_EXCHANGEDB_get_coin_transactions (pg,
     858              :                                                     true,
     859              :                                                     &coin.coin_pub,
     860              :                                                     0,
     861              :                                                     0,
     862              :                                                     &etag,
     863              :                                                     &balance,
     864              :                                                     &h_denom_pub,
     865              :                                                     &tl),
     866              :             TDB_coin_free (&fresh_coin));
     867            1 :   FAILIF_C (7 != summarize (tl,
     868              :                             &types),
     869              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     870              :             TDB_coin_free (&fresh_coin));
     871            1 :   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     872            8 :        NULL != pos;
     873            7 :        pos = pos->next)
     874            7 :     if (TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER == pos->type)
     875            1 :       rr = pos->details.old_coin_recoup;
     876            1 :   FAILIF_C (NULL == rr,
     877              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     878              :             TDB_coin_free (&fresh_coin));
     879            1 :   FAILIF_C (2 != rr->coin_index,
     880              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     881              :             TDB_coin_free (&fresh_coin));
     882            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
     883              :                                 &rc),
     884              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     885              :             TDB_coin_free (&fresh_coin));
     886            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->coin.coin_pub,
     887              :                                 &fresh_coin.coin_pub),
     888              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     889              :             TDB_coin_free (&fresh_coin));
     890            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub,
     891              :                                 &coin.coin_pub),
     892              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     893              :             TDB_coin_free (&fresh_coin));
     894            1 :   FAILIF_C (0 != TALER_amount_cmp (&rr->value,
     895              :                                    &credited),
     896              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     897              :             TDB_coin_free (&fresh_coin));
     898            1 :   FAILIF_C (0 != TALER_amount_cmp (&balance,
     899              :                                    &credited),
     900              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     901              :             TDB_coin_free (&fresh_coin));
     902            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     903            1 :   tl = NULL;
     904              : 
     905              :   /* the recouped coin is emptied and sees the debit entry */
     906            1 :   etag = 0;
     907            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     908              :             TALER_EXCHANGEDB_get_coin_transactions (pg,
     909              :                                                     true,
     910              :                                                     &fresh_coin.coin_pub,
     911              :                                                     0,
     912              :                                                     0,
     913              :                                                     &etag,
     914              :                                                     &balance,
     915              :                                                     &h_denom_pub,
     916              :                                                     &tl),
     917              :             TDB_coin_free (&fresh_coin));
     918            1 :   FAILIF_C (1 != summarize (tl,
     919              :                             &types),
     920              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     921              :             TDB_coin_free (&fresh_coin));
     922            1 :   FAILIF_C (TALER_EXCHANGEDB_TT_RECOUP_REFRESH != tl->type,
     923              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     924              :             TDB_coin_free (&fresh_coin));
     925            1 :   rr = tl->details.recoup_refresh;
     926            1 :   FAILIF_C (2 != rr->coin_index,
     927              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     928              :             TDB_coin_free (&fresh_coin));
     929            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
     930              :                                 &rc),
     931              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     932              :             TDB_coin_free (&fresh_coin));
     933            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub,
     934              :                                 &coin.coin_pub),
     935              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     936              :             TDB_coin_free (&fresh_coin));
     937            1 :   FAILIF_C (0 != GNUNET_memcmp (&rr->coin_sig,
     938              :                                 &coin_sig),
     939              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     940              :             TDB_coin_free (&fresh_coin));
     941            1 :   FAILIF_C (0 != TALER_amount_cmp (&balance,
     942              :                                    &zero),
     943              :             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     944              :             TDB_coin_free (&fresh_coin));
     945            1 :   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
     946            1 :   TDB_coin_free (&fresh_coin);
     947            1 :   return 0;
     948              : }
     949              : 
     950              : 
     951              : /**
     952              :  * The checks to run, in order.
     953              :  */
     954              : static const struct TDB_Test tests[] = {
     955              :   { "coin-history-unknown-coin",
     956              :     &check_unknown_coin },
     957              :   { "coin-history-fresh-coin",
     958              :     &check_fresh_coin },
     959              :   { "coin-history-deposit-and-refund",
     960              :     &check_deposit_and_refund },
     961              :   { "coin-history-etag",
     962              :     &check_etag },
     963              :   { "coin-history-offset",
     964              :     &check_offset },
     965              :   { "coin-history-other-spends",
     966              :     &check_other_spends },
     967              :   { "coin-history-recoup",
     968              :     &check_recoup },
     969              :   { "coin-history-in-transaction",
     970              :     &check_in_transaction },
     971              :   { "coin-history-recoup-refresh",
     972              :     &check_recoup_refresh },
     973              :   { NULL, NULL }
     974              : };
     975              : 
     976              : 
     977              : int
     978            1 : main (int argc,
     979              :       char *const *argv)
     980              : {
     981              :   int ret;
     982              : 
     983            1 :   ret = TDB_main (argc,
     984              :                   argv,
     985              :                   "test-coin-history",
     986              :                   "Tests for the exchangedb `coin_history' table",
     987              :                   tests);
     988            1 :   TDB_coin_free (&coin);
     989            1 :   TDB_denom_free (&denom);
     990            1 :   TDB_denom_free (&fresh_denom);
     991            1 :   TDB_account_free (&account);
     992            1 :   return ret;
     993              : }
     994              : 
     995              : 
     996              : /* end of test_coin_history.c */
        

Generated by: LCOV version 2.0-1