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 (25248B)


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