exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

test_coin_history.c (34838B)


      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 summarize (const struct TALER_EXCHANGEDB_TransactionList *tl,
    107            unsigned int *types)
    108 {
    109   unsigned int cnt = 0;
    110 
    111   *types = 0;
    112   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
    113        NULL != pos;
    114        pos = pos->next)
    115   {
    116     cnt++;
    117     *types |= 1U << pos->type;
    118   }
    119   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 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   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 check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
    163 {
    164   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   uint64_t etag = 42;
    169 
    170   TDB_account (pg,
    171                10,
    172                &account);
    173   TDB_denom (pg,
    174              10,
    175              "5",
    176              "0.1",
    177              &denom);
    178   TDB_FILL (unknown,
    179             99);
    180   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   FAILIF (NULL != tl);
    191   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 check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
    204 {
    205   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
    206   uint64_t etag = 42;
    207 
    208   TDB_reserve_in (pg,
    209                   &account,
    210                   11,
    211                   "10",
    212                   &reserve_pub);
    213   withdraw_id = TDB_withdraw (pg,
    214                               &denom,
    215                               &reserve_pub,
    216                               11,
    217                               "5");
    218   TDB_coin (pg,
    219             &denom,
    220             20,
    221             &coin,
    222             &known_coin_id);
    223   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    224           history (pg,
    225                    0,
    226                    0,
    227                    &etag,
    228                    &tl));
    229   FAILIF (NULL != tl);
    230   FAILIF (0 != TDB_count (pg,
    231                           "FROM coin_history"));
    232   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 check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
    244 {
    245   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    246   struct TALER_EXCHANGEDB_Refund refund;
    247   struct TALER_Amount deposit_fee = TDB_amount ("0.1");
    248   struct TALER_Amount balance;
    249   struct TALER_Amount expect = TDB_amount ("4.5");
    250   struct TALER_DenominationHashP h_denom_pub;
    251   unsigned int types;
    252   uint64_t etag = 0;
    253   bool not_found;
    254   bool refund_ok;
    255   bool gone;
    256   bool conflict;
    257 
    258   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   FAILIF (1 != TDB_count (pg,
    268                           "FROM coin_history"));
    269 
    270   memset (&refund,
    271           0,
    272           sizeof (refund));
    273   refund.coin = coin;
    274   refund.details.merchant_pub = deposit.merchant_pub;
    275   TDB_FILL (refund.details.merchant_sig,
    276             31);
    277   refund.details.h_contract_terms = deposit.h_contract_terms;
    278   refund.details.rtransaction_id = 1;
    279   refund.details.refund_amount = TDB_amount ("0.5");
    280   refund.details.refund_fee = TDB_amount ("0");
    281   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   FAILIF (not_found);
    291   FAILIF (! refund_ok);
    292   FAILIF (2 != TDB_count (pg,
    293                           "FROM coin_history"));
    294 
    295   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   FAILIF_C (2 != summarize (tl,
    306                             &types),
    307             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    308   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)),
    309             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    310   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
    311             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    312   /* 5 - 1 + 0.5 */
    313   FAILIF_C (0 != TALER_amount_cmp (&balance,
    314                                    &expect),
    315             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    316   FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
    317                                 &denom.h_denom_pub),
    318             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    319   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    320   FAILIF (0 == etag);
    321   etag_after_refund = etag;
    322   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 check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg)
    335 {
    336   struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
    337   uint64_t etag = 0;
    338 
    339   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    340           history (pg,
    341                    0,
    342                    etag_after_refund,
    343                    &etag,
    344                    &tl));
    345   FAILIF (NULL != tl);
    346   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   FAILIF (NULL != pg->transaction_name);
    350 
    351   /* an ETag that is not the current one still returns the history */
    352   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    353           history (pg,
    354                    0,
    355                    etag_after_refund - 1,
    356                    &etag,
    357                    &tl));
    358   FAILIF (NULL == tl);
    359   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    360   FAILIF (NULL != pg->transaction_name);
    361   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 check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg)
    373 {
    374   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    375   unsigned int types;
    376   uint64_t etag = 0;
    377 
    378   /* everything after the deposit: only the refund is left */
    379   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    380           history (pg,
    381                    etag_after_refund - 1,
    382                    0,
    383                    &etag,
    384                    &tl));
    385   FAILIF_C (1 != summarize (tl,
    386                             &types),
    387             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    388   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
    389             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    390   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    391 
    392   /* everything after the last entry: nothing, but the ETag is still
    393      reported */
    394   tl = (void *) 1;
    395   etag = 0;
    396   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    397           history (pg,
    398                    etag_after_refund,
    399                    0,
    400                    &etag,
    401                    &tl));
    402   FAILIF (NULL != tl);
    403   FAILIF (etag != etag_after_refund);
    404   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 check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg)
    417 {
    418   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    419   struct TDB_Purse purse;
    420   struct TALER_CoinSpendSignatureP coin_sig;
    421   struct TALER_ReserveSignatureP reserve_sig;
    422   struct TALER_Amount one = TDB_amount ("1");
    423   struct TALER_Amount zero = TDB_amount ("0");
    424   unsigned int types;
    425   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   TDB_purse (pg,
    433              40,
    434              "1",
    435              GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
    436              &purse);
    437   TDB_FILL (coin_sig,
    438             41);
    439   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   FAILIF (! balance_ok);
    450   FAILIF (conflict);
    451 
    452   /* to keep a reserve open */
    453   TDB_FILL (coin_sig,
    454             42);
    455   TDB_FILL (reserve_sig,
    456             42);
    457   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   FAILIF (insufficient_funds);
    467 
    468   /* and into a melt */
    469   {
    470     struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
    471     struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
    472     struct TALER_Amount coin_balance;
    473     bool found;
    474     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     memset (&rf,
    481             0,
    482             sizeof (rf));
    483     rf.coin.coin_pub = coin.coin_pub;
    484     rf.coin.denom_pub_hash = coin.denom_pub_hash;
    485     rf.coin.no_age_commitment = coin.no_age_commitment;
    486     TDB_FILL (rf.coin_sig,
    487               43);
    488     TDB_FILL (rf.rc,
    489               43);
    490     TDB_FILL (rf.refresh_seed,
    491               43);
    492     TDB_FILL (rf.planchets_h,
    493               43);
    494     TDB_FILL (rf.selected_h,
    495               44);
    496     rf.amount_with_fee = one;
    497     TDB_denom (pg,
    498                11,
    499                "0.1",
    500                "0.01",
    501                &fresh_denom);
    502     rf.num_coins = 3;
    503     rf.denom_serials = GNUNET_new_array (rf.num_coins,
    504                                          uint64_t);
    505     /* Deliberately neither sorted nor distinct. */
    506     rf.denom_serials[0] = fresh_denom.serial;
    507     rf.denom_serials[1] = denom.serial;
    508     rf.denom_serials[2] = fresh_denom.serial;
    509     rf.denom_sigs = GNUNET_new_array (
    510       rf.num_coins,
    511       struct TALER_BlindedDenominationSignature);
    512     for (unsigned int i = 0; i < rf.num_coins; i++)
    513       TDB_blinded_denom_sig (43 + i,
    514                              &rf.denom_sigs[i]);
    515     rf.noreveal_index = 1;
    516     rf.is_v27_refresh = true;
    517     rf.no_blinding_seed = true;
    518     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     for (unsigned int i = 0; i < rf.num_coins; i++)
    528       TALER_blinded_denom_sig_free (&rf.denom_sigs[i]);
    529     GNUNET_free (rf.denom_sigs);
    530     GNUNET_free (rf.denom_serials);
    531     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
    532     FAILIF (! melt_balance_ok);
    533 
    534     /* The original output list is available before and after reveal. */
    535     for (unsigned int revealed = 0; revealed < 2; revealed++)
    536     {
    537       bool found_melt = false;
    538 
    539       if (revealed)
    540         FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    541                 TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
    542                                                              &rf.rc));
    543       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    544               history (pg,
    545                        0,
    546                        0,
    547                        &etag,
    548                        &tl));
    549       for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
    550            NULL != pos;
    551            pos = pos->next)
    552       {
    553         const struct TALER_EXCHANGEDB_MeltListEntry *melt;
    554 
    555         if (TALER_EXCHANGEDB_TT_MELT != pos->type)
    556           continue;
    557         found_melt = true;
    558         melt = pos->details.melt;
    559         FAILIF_C (3 != melt->num_coins,
    560                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    561         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         FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1],
    565                                       &denom.h_denom_pub),
    566                   TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    567         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       TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    572       tl = NULL;
    573       FAILIF (! found_melt);
    574     }
    575   }
    576 
    577   FAILIF (5 != TDB_count (pg,
    578                           "FROM coin_history"));
    579   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    580           history (pg,
    581                    0,
    582                    0,
    583                    &etag,
    584                    &tl));
    585   FAILIF_C (5 != summarize (tl,
    586                             &types),
    587             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    588   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)),
    589             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    590   FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)),
    591             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    592   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     uint64_t max = 0;
    598 
    599     for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
    600          NULL != pos;
    601          pos = pos->next)
    602       max = GNUNET_MAX (max,
    603                         pos->coin_history_id);
    604     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     struct TALER_Amount expect = TDB_amount ("3.5");
    612 
    613     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     FAILIF_C (0 != TALER_amount_cmp (&total,
    619                                      &expect),
    620               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    621   }
    622   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    623   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 check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    635 {
    636   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   struct TALER_Amount zero = TDB_amount ("0");
    643   struct TALER_DenominationHashP h_denom_pub;
    644   unsigned int types;
    645   uint64_t etag = 0;
    646   bool recoup_ok;
    647   bool internal_failure;
    648 
    649   TDB_FILL (coin_sig,
    650             50);
    651   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   recoup_timestamp = GNUNET_TIME_timestamp_get ();
    656   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   FAILIF (internal_failure);
    670   FAILIF (! recoup_ok);
    671   FAILIF (6 != TDB_count (pg,
    672                           "FROM coin_history"));
    673 
    674   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   FAILIF_C (6 != summarize (tl,
    685                             &types),
    686             TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    687   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     const struct TALER_EXCHANGEDB_RecoupListEntry *recoup = NULL;
    693     struct TALER_HashBlindedPlanchetsP planchets_h;
    694 
    695     TDB_FILL (planchets_h,
    696               11);
    697     for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
    698          NULL != pos;
    699          pos = pos->next)
    700       if (TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW == pos->type)
    701         recoup = pos->details.recoup;
    702     FAILIF_C (NULL == recoup,
    703               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    704     FAILIF_C (3 != recoup->coin_index,
    705               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    706     FAILIF_C (0 != GNUNET_memcmp (&recoup->planchets_h,
    707                                   &planchets_h),
    708               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    709     FAILIF_C (0 != GNUNET_memcmp (&recoup->reserve_pub,
    710                                   &reserve_pub),
    711               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    712     FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_sig,
    713                                   &coin_sig),
    714               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    715     FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_blind,
    716                                   &coin_bks),
    717               TALER_EXCHANGEDB_free_coin_transaction_list (tl));
    718   }
    719   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    720   FAILIF (0 != TALER_amount_cmp (&balance,
    721                                  &zero));
    722   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 check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg)
    735 {
    736   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    737   struct TALER_Amount balance;
    738   struct TALER_DenominationHashP h_denom_pub;
    739   unsigned int types;
    740   uint64_t etag = 0;
    741 
    742   FAILIF (GNUNET_OK !=
    743           TALER_EXCHANGEDB_start (pg,
    744                                   "coin-history"));
    745   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   FAILIF_C (6 != summarize (tl,
    757                             &types),
    758             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    759             TALER_EXCHANGEDB_rollback (pg));
    760   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    761   FAILIF_C (NULL == pg->transaction_name,
    762             TALER_EXCHANGEDB_rollback (pg));
    763   TALER_EXCHANGEDB_rollback (pg);
    764   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 check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
    780 {
    781   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    782   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   struct TALER_Amount zero = TDB_amount ("0");
    791   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   uint64_t etag = 0;
    796   unsigned int types;
    797   bool recoup_ok;
    798   bool internal_failure;
    799 
    800   TDB_FILL (rc,
    801             43);
    802   /* row of the melt made in check_other_spends() */
    803   {
    804     struct GNUNET_PQ_QueryParam params[] = {
    805       GNUNET_PQ_query_param_auto_from_type (&rc),
    806       GNUNET_PQ_query_param_end
    807     };
    808     struct GNUNET_PQ_ResultSpec rs[] = {
    809       GNUNET_PQ_result_spec_uint64 ("refresh_id",
    810                                     &refresh_id),
    811       GNUNET_PQ_result_spec_end
    812     };
    813 
    814     FAILIF (GNUNET_OK !=
    815             GNUNET_PQ_prepare_anon (pg->conn,
    816                                     "SELECT refresh_id"
    817                                     " FROM refresh"
    818                                     " WHERE rc=$1;"));
    819     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    820             GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    821                                                       "",
    822                                                       params,
    823                                                       rs));
    824   }
    825   TDB_coin (pg,
    826             &fresh_denom,
    827             60,
    828             &fresh_coin,
    829             &fresh_known_coin_id);
    830   TDB_FILL (coin_sig,
    831             60);
    832   TDB_FILL (coin_bks,
    833             60);
    834   recoup_timestamp = GNUNET_TIME_timestamp_get ();
    835   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   FAILIF_C (internal_failure || ! recoup_ok,
    850             TDB_coin_free (&fresh_coin));
    851   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   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   FAILIF_C (7 != summarize (tl,
    868                             &types),
    869             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    870             TDB_coin_free (&fresh_coin));
    871   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
    872        NULL != pos;
    873        pos = pos->next)
    874     if (TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER == pos->type)
    875       rr = pos->details.old_coin_recoup;
    876   FAILIF_C (NULL == rr,
    877             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    878             TDB_coin_free (&fresh_coin));
    879   FAILIF_C (2 != rr->coin_index,
    880             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    881             TDB_coin_free (&fresh_coin));
    882   FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
    883                                 &rc),
    884             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    885             TDB_coin_free (&fresh_coin));
    886   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   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   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   FAILIF_C (0 != TALER_amount_cmp (&balance,
    899                                    &credited),
    900             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    901             TDB_coin_free (&fresh_coin));
    902   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    903   tl = NULL;
    904 
    905   /* the recouped coin is emptied and sees the debit entry */
    906   etag = 0;
    907   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   FAILIF_C (1 != summarize (tl,
    919                             &types),
    920             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    921             TDB_coin_free (&fresh_coin));
    922   FAILIF_C (TALER_EXCHANGEDB_TT_RECOUP_REFRESH != tl->type,
    923             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    924             TDB_coin_free (&fresh_coin));
    925   rr = tl->details.recoup_refresh;
    926   FAILIF_C (2 != rr->coin_index,
    927             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    928             TDB_coin_free (&fresh_coin));
    929   FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
    930                                 &rc),
    931             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    932             TDB_coin_free (&fresh_coin));
    933   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   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   FAILIF_C (0 != TALER_amount_cmp (&balance,
    942                                    &zero),
    943             TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    944             TDB_coin_free (&fresh_coin));
    945   TALER_EXCHANGEDB_free_coin_transaction_list (tl);
    946   TDB_coin_free (&fresh_coin);
    947   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 main (int argc,
    979       char *const *argv)
    980 {
    981   int ret;
    982 
    983   ret = TDB_main (argc,
    984                   argv,
    985                   "test-coin-history",
    986                   "Tests for the exchangedb `coin_history' table",
    987                   tests);
    988   TDB_coin_free (&coin);
    989   TDB_denom_free (&denom);
    990   TDB_denom_free (&fresh_denom);
    991   TDB_account_free (&account);
    992   return ret;
    993 }
    994 
    995 
    996 /* end of test_coin_history.c */