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


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