exchange

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

test_common.c (27186B)


      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_common.c
     18  * @brief shared scaffolding for the per-table exchangedb tests
     19  * @author Christian Grothoff
     20  */
     21 #include "test_common.h"
     22 #include "exchange-database/create_tables.h"
     23 #include "exchange-database/do_insert_known_coins.h"
     24 #include "exchange-database/insert_denomination_info.h"
     25 #include "exchange-database/get_denomination_info.h"
     26 #include "exchange-database/do_deposit.h"
     27 #include "exchange-database/insert_purse_request.h"
     28 #include "exchange-database/do_withdraw.h"
     29 
     30 
     31 /**
     32  * Name of the single check to run, NULL to run all of them.
     33  */
     34 static char *only;
     35 
     36 /**
     37  * Return value of the process.
     38  */
     39 static int result;
     40 
     41 /**
     42  * Checks the current binary wants to run.
     43  */
     44 static const struct TDB_Test *tdb_tests;
     45 
     46 
     47 enum GNUNET_GenericReturnValue
     48 TDB_exec (struct TALER_EXCHANGEDB_PostgresContext *pg,
     49           const char *fmt,
     50           ...)
     51 {
     52   enum GNUNET_GenericReturnValue ret;
     53   va_list ap;
     54   char *sql;
     55   int len;
     56 
     57   va_start (ap,
     58             fmt);
     59   len = vsnprintf (NULL,
     60                    0,
     61                    fmt,
     62                    ap);
     63   va_end (ap);
     64   GNUNET_assert (len >= 0);
     65   sql = GNUNET_malloc ((size_t) len + 1);
     66   va_start (ap,
     67             fmt);
     68   GNUNET_assert (len ==
     69                  vsnprintf (sql,
     70                             (size_t) len + 1,
     71                             fmt,
     72                             ap));
     73   va_end (ap);
     74   {
     75     struct GNUNET_PQ_ExecuteStatement es[] = {
     76       GNUNET_PQ_make_execute (sql),
     77       GNUNET_PQ_EXECUTE_STATEMENT_END
     78     };
     79 
     80     ret = GNUNET_PQ_exec_statements (pg->conn,
     81                                      es);
     82   }
     83   GNUNET_free (sql);
     84   return ret;
     85 }
     86 
     87 
     88 uint64_t
     89 TDB_count (struct TALER_EXCHANGEDB_PostgresContext *pg,
     90            const char *fmt,
     91            ...)
     92 {
     93   struct GNUNET_PQ_QueryParam params[] = {
     94     GNUNET_PQ_query_param_end
     95   };
     96   uint64_t n;
     97   struct GNUNET_PQ_ResultSpec rs[] = {
     98     GNUNET_PQ_result_spec_uint64 ("n",
     99                                   &n),
    100     GNUNET_PQ_result_spec_end
    101   };
    102   va_list ap;
    103   char *frag;
    104   char *sql;
    105   int len;
    106 
    107   va_start (ap,
    108             fmt);
    109   len = vsnprintf (NULL,
    110                    0,
    111                    fmt,
    112                    ap);
    113   va_end (ap);
    114   GNUNET_assert (len >= 0);
    115   frag = GNUNET_malloc ((size_t) len + 1);
    116   va_start (ap,
    117             fmt);
    118   GNUNET_assert (len ==
    119                  vsnprintf (frag,
    120                             (size_t) len + 1,
    121                             fmt,
    122                             ap));
    123   va_end (ap);
    124   GNUNET_asprintf (&sql,
    125                    "SELECT COUNT(*) AS n %s;",
    126                    frag);
    127   GNUNET_free (frag);
    128   GNUNET_assert (GNUNET_OK ==
    129                  GNUNET_PQ_prepare_anon (pg->conn,
    130                                          sql));
    131   GNUNET_free (sql);
    132   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    133                  GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    134                                                            "",
    135                                                            params,
    136                                                            rs));
    137   return n;
    138 }
    139 
    140 
    141 struct TALER_Amount
    142 TDB_amount (const char *str)
    143 {
    144   struct TALER_Amount amount;
    145   char *s;
    146 
    147   GNUNET_asprintf (&s,
    148                    CURRENCY ":%s",
    149                    str);
    150   GNUNET_assert (GNUNET_OK ==
    151                  TALER_string_to_amount (s,
    152                                          &amount));
    153   GNUNET_free (s);
    154   return amount;
    155 }
    156 
    157 
    158 void
    159 TDB_fill (void *ptr,
    160           size_t size,
    161           uint32_t seed)
    162 {
    163   unsigned char *buf = ptr;
    164   uint32_t state = seed * 2654435761U + 1;
    165 
    166   for (size_t i = 0; i<size; i++)
    167   {
    168     state = state * 1103515245U + 12345U;
    169     buf[i] = (unsigned char) (state >> 16);
    170   }
    171 }
    172 
    173 
    174 char *
    175 TDB_hex (const void *data,
    176          size_t size)
    177 {
    178   const unsigned char *raw = data;
    179   char *hex;
    180 
    181   hex = GNUNET_malloc (size * 2 + 1);
    182   for (size_t i = 0; i<size; i++)
    183     GNUNET_snprintf (&hex[i * 2],
    184                      3,
    185                      "%02x",
    186                      raw[i]);
    187   return hex;
    188 }
    189 
    190 
    191 void
    192 TDB_denom_sig (uint32_t seed,
    193                struct TALER_DenominationSignature *sig)
    194 {
    195   struct GNUNET_CRYPTO_UnblindedSignature *ubs;
    196 
    197   ubs = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
    198   ubs->cipher = GNUNET_CRYPTO_BSA_CS;
    199   ubs->rc = 1;
    200   TDB_fill (&ubs->details.cs_signature,
    201             sizeof (ubs->details.cs_signature),
    202             seed);
    203   sig->unblinded_sig = ubs;
    204 }
    205 
    206 
    207 void
    208 TDB_blinded_denom_sig (uint32_t seed,
    209                        struct TALER_BlindedDenominationSignature *sig)
    210 {
    211   struct GNUNET_CRYPTO_BlindedSignature *bs;
    212 
    213   bs = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
    214   bs->cipher = GNUNET_CRYPTO_BSA_CS;
    215   bs->rc = 1;
    216   TDB_fill (&bs->details.blinded_cs_answer,
    217             sizeof (bs->details.blinded_cs_answer),
    218             seed);
    219   sig->blinded_sig = bs;
    220 }
    221 
    222 
    223 void
    224 TDB_denom (struct TALER_EXCHANGEDB_PostgresContext *pg,
    225            uint32_t seed,
    226            const char *value,
    227            const char *fee,
    228            struct TDB_Denom *denom)
    229 {
    230   struct GNUNET_TIME_Timestamp now;
    231   struct TALER_Amount f;
    232 
    233   memset (denom,
    234           0,
    235           sizeof (*denom));
    236   /* Build the Clause-Schnorr key pair from @a seed instead of at random, so
    237      that two calls with the same seed name the same denomination -- checks
    238      rely on that to refer to a row an earlier check created.  The top nibble
    239      of the scalar is cleared to keep it below the group order. */
    240   {
    241     struct GNUNET_CRYPTO_BlindSignPrivateKey *bpriv;
    242     struct GNUNET_CRYPTO_BlindSignPublicKey *bpub;
    243     unsigned char *d;
    244 
    245     bpriv = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPrivateKey);
    246     bpriv->cipher = GNUNET_CRYPTO_BSA_CS;
    247     bpriv->rc = 1;
    248     d = bpriv->details.cs_private_key.scalar.d;
    249     TDB_fill (d,
    250               sizeof (bpriv->details.cs_private_key.scalar.d),
    251               seed);
    252     d[sizeof (bpriv->details.cs_private_key.scalar.d) - 1] &= 0x0f;
    253     bpub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
    254     bpub->cipher = GNUNET_CRYPTO_BSA_CS;
    255     bpub->rc = 1;
    256     GNUNET_CRYPTO_cs_private_key_get_public (
    257       &bpriv->details.cs_private_key,
    258       &bpub->details.cs_public_key);
    259     GNUNET_CRYPTO_hash (&bpub->details.cs_public_key,
    260                         sizeof (bpub->details.cs_public_key),
    261                         &bpub->pub_key_hash);
    262     denom->priv.bsign_priv_key = bpriv;
    263     denom->pub.bsign_pub_key = bpub;
    264   }
    265   TALER_denom_pub_hash (&denom->pub,
    266                         &denom->h_denom_pub);
    267   /* The same seed may be used by more than one check to name the same
    268      denomination; the second call then only has to load it. */
    269   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    270       TALER_EXCHANGEDB_get_denomination_info (pg,
    271                                               &denom->h_denom_pub,
    272                                               &denom->serial,
    273                                               &denom->issue))
    274     return;
    275   now = GNUNET_TIME_timestamp_get ();
    276   f = TDB_amount (fee);
    277   denom->issue.start = now;
    278   denom->issue.expire_withdraw
    279     = GNUNET_TIME_absolute_to_timestamp (
    280         GNUNET_TIME_absolute_add (now.abs_time,
    281                                   GNUNET_TIME_UNIT_HOURS));
    282   denom->issue.expire_deposit
    283     = GNUNET_TIME_absolute_to_timestamp (
    284         GNUNET_TIME_absolute_add (now.abs_time,
    285                                   GNUNET_TIME_relative_multiply (
    286                                     GNUNET_TIME_UNIT_HOURS,
    287                                     2)));
    288   denom->issue.expire_legal
    289     = GNUNET_TIME_absolute_to_timestamp (
    290         GNUNET_TIME_absolute_add (now.abs_time,
    291                                   GNUNET_TIME_relative_multiply (
    292                                     GNUNET_TIME_UNIT_HOURS,
    293                                     3)));
    294   denom->issue.value = TDB_amount (value);
    295   denom->issue.fees.withdraw = f;
    296   denom->issue.fees.deposit = f;
    297   denom->issue.fees.refresh = f;
    298   denom->issue.fees.refund = f;
    299   denom->issue.denom_hash = denom->h_denom_pub;
    300   TDB_fill (&denom->issue.signature,
    301             sizeof (denom->issue.signature),
    302             seed);
    303   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    304                  TALER_EXCHANGEDB_insert_denomination_info (pg,
    305                                                             &denom->pub,
    306                                                             &denom->issue));
    307   {
    308     struct GNUNET_PQ_QueryParam params[] = {
    309       GNUNET_PQ_query_param_auto_from_type (&denom->h_denom_pub),
    310       GNUNET_PQ_query_param_end
    311     };
    312     struct GNUNET_PQ_ResultSpec rs[] = {
    313       GNUNET_PQ_result_spec_uint64 ("denominations_serial",
    314                                     &denom->serial),
    315       GNUNET_PQ_result_spec_end
    316     };
    317 
    318     GNUNET_assert (GNUNET_OK ==
    319                    GNUNET_PQ_prepare_anon (
    320                      pg->conn,
    321                      "SELECT denominations_serial"
    322                      " FROM denominations"
    323                      " WHERE denom_pub_hash=$1;"));
    324     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    325                    GNUNET_PQ_eval_prepared_singleton_select (
    326                      pg->conn,
    327                      "",
    328                      params,
    329                      rs));
    330   }
    331 }
    332 
    333 
    334 void
    335 TDB_denom_free (struct TDB_Denom *denom)
    336 {
    337   TALER_denom_pub_free (&denom->pub);
    338   TALER_denom_priv_free (&denom->priv);
    339 }
    340 
    341 
    342 void
    343 TDB_coin (struct TALER_EXCHANGEDB_PostgresContext *pg,
    344           const struct TDB_Denom *denom,
    345           uint32_t seed,
    346           struct TALER_CoinPublicInfo *coin,
    347           uint64_t *known_coin_id)
    348 {
    349   struct TALER_EXCHANGEDB_CoinKnownResult res;
    350   const struct TALER_CoinPublicInfo *coins[1] = { coin };
    351 
    352   memset (coin,
    353           0,
    354           sizeof (*coin));
    355   TDB_FILL (coin->coin_pub,
    356             seed);
    357   coin->denom_pub_hash = denom->h_denom_pub;
    358   coin->no_age_commitment = true;
    359   TDB_denom_sig (seed,
    360                  &coin->denom_sig);
    361   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    362                  TALER_EXCHANGEDB_do_insert_known_coins (pg,
    363                                                          1,
    364                                                          coins,
    365                                                          &res));
    366   /* CKS_PRESENT is fine: the same seed may name a coin an earlier check
    367      already made known. */
    368   GNUNET_assert ( (TALER_EXCHANGEDB_CKS_ADDED == res.status) ||
    369                   (TALER_EXCHANGEDB_CKS_PRESENT == res.status) );
    370   if (NULL != known_coin_id)
    371     *known_coin_id = res.known_coin_id;
    372 }
    373 
    374 
    375 void
    376 TDB_coin_free (struct TALER_CoinPublicInfo *coin)
    377 {
    378   TALER_denom_sig_free (&coin->denom_sig);
    379 }
    380 
    381 
    382 void
    383 TDB_account (struct TALER_EXCHANGEDB_PostgresContext *pg,
    384              uint32_t seed,
    385              struct TDB_Account *account)
    386 {
    387   struct TALER_NormalizedPayto npayto;
    388   char *fhex;
    389   char *nhex;
    390 
    391   GNUNET_asprintf (&account->payto.full_payto,
    392                    "payto://x-taler-bank/localhost/tdb%u?receiver-name=tdb%u",
    393                    (unsigned int) seed,
    394                    (unsigned int) seed);
    395   TALER_full_payto_hash (account->payto,
    396                          &account->h_full);
    397   npayto = TALER_payto_normalize (account->payto);
    398   GNUNET_assert (NULL != npayto.normalized_payto);
    399   TALER_normalized_payto_hash (npayto,
    400                                &account->h_normalized);
    401   GNUNET_free (npayto.normalized_payto);
    402   fhex = TDB_hex (&account->h_full,
    403                   sizeof (account->h_full));
    404   nhex = TDB_hex (&account->h_normalized,
    405                   sizeof (account->h_normalized));
    406   GNUNET_assert (GNUNET_OK ==
    407                  TDB_exec (pg,
    408                            "INSERT INTO kyc_targets"
    409                            " (h_normalized_payto,is_wallet)"
    410                            " VALUES (decode('%s','hex'),FALSE)"
    411                            " ON CONFLICT DO NOTHING;"
    412                            "INSERT INTO wire_targets"
    413                            " (wire_target_h_payto,payto_uri"
    414                            " ,h_normalized_payto)"
    415                            " VALUES (decode('%s','hex'),'%s'"
    416                            "        ,decode('%s','hex'))"
    417                            " ON CONFLICT DO NOTHING;",
    418                            nhex,
    419                            fhex,
    420                            account->payto.full_payto,
    421                            nhex));
    422   GNUNET_free (fhex);
    423   GNUNET_free (nhex);
    424 }
    425 
    426 
    427 void
    428 TDB_account_free (struct TDB_Account *account)
    429 {
    430   GNUNET_free (account->payto.full_payto);
    431 }
    432 
    433 
    434 void
    435 TDB_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg,
    436              uint32_t seed,
    437              const char *balance,
    438              struct TALER_ReservePublicKeyP *reserve_pub)
    439 {
    440   struct TALER_Amount b = TDB_amount (balance);
    441   char *hex;
    442 
    443   TDB_fill (reserve_pub,
    444             sizeof (*reserve_pub),
    445             seed);
    446   hex = TDB_hex (reserve_pub,
    447                  sizeof (*reserve_pub));
    448   GNUNET_assert (GNUNET_OK ==
    449                  TDB_exec (pg,
    450                            "INSERT INTO reserves"
    451                            " (reserve_pub,current_balance"
    452                            " ,expiration_date,gc_date)"
    453                            " VALUES (decode('%s','hex')"
    454                            "        ,ROW(%llu,%u)::taler_amount"
    455                            "        ,%llu,%llu)"
    456                            " ON CONFLICT DO NOTHING;",
    457                            hex,
    458                            (unsigned long long) b.value,
    459                            (unsigned int) b.fraction,
    460                            (unsigned long long) 4000000000000000LLU,
    461                            (unsigned long long) 5000000000000000LLU));
    462   GNUNET_free (hex);
    463 }
    464 
    465 
    466 void
    467 TDB_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
    468            uint32_t seed,
    469            const char *amount,
    470            struct GNUNET_TIME_Timestamp purse_expiration,
    471            struct TDB_Purse *purse)
    472 {
    473   struct TALER_Amount purse_fee = TDB_amount ("0");
    474   enum GNUNET_DB_QueryStatus qs;
    475   bool in_conflict = false;
    476 
    477   memset (purse,
    478           0,
    479           sizeof (*purse));
    480   TDB_fill (&purse->purse_pub,
    481             sizeof (purse->purse_pub),
    482             seed);
    483   TDB_fill (&purse->merge_pub,
    484             sizeof (purse->merge_pub),
    485             seed);
    486   TDB_fill (&purse->h_contract_terms,
    487             sizeof (purse->h_contract_terms),
    488             seed);
    489   TDB_fill (&purse->purse_sig,
    490             sizeof (purse->purse_sig),
    491             seed);
    492   purse->purse_expiration = purse_expiration;
    493   purse->amount = TDB_amount (amount);
    494   qs = TALER_EXCHANGEDB_insert_purse_request (
    495     pg,
    496     &purse->purse_pub,
    497     &purse->merge_pub,
    498     purse_expiration,
    499     &purse->h_contract_terms,
    500     0,
    501     TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA,
    502     &purse_fee,
    503     &purse->amount,
    504     &purse->purse_sig,
    505     &in_conflict);
    506   /* SUCCESS_NO_RESULTS is the answer for an identical replay, which is
    507      what a second call with the same seed is. */
    508   GNUNET_assert (0 <= qs);
    509   GNUNET_assert (! in_conflict);
    510 }
    511 
    512 
    513 void
    514 TDB_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg,
    515              const struct TDB_Account *account,
    516              const struct TALER_CoinPublicInfo *coin,
    517              uint32_t seed,
    518              const char *amount,
    519              const char *fee,
    520              struct GNUNET_TIME_Timestamp wire_deadline,
    521              struct GNUNET_TIME_Timestamp refund_deadline,
    522              struct TDB_Deposit *dep)
    523 {
    524   struct TALER_EXCHANGEDB_CoinDepositInformation cdi;
    525   struct TALER_EXCHANGEDB_BatchDeposit bd;
    526   struct TALER_Amount fees[1];
    527   struct TALER_Amount total;
    528   uint32_t bad_balance_index;
    529   bool balance_ok;
    530   bool in_conflict;
    531 
    532   memset (dep,
    533           0,
    534           sizeof (*dep));
    535   TDB_fill (&dep->merchant_pub,
    536             sizeof (dep->merchant_pub),
    537             seed);
    538   TDB_fill (&dep->merchant_sig,
    539             sizeof (dep->merchant_sig),
    540             seed);
    541   TDB_fill (&dep->h_contract_terms,
    542             sizeof (dep->h_contract_terms),
    543             seed);
    544   TDB_fill (&dep->wire_salt,
    545             sizeof (dep->wire_salt),
    546             seed);
    547   TALER_merchant_wire_signature_hash (account->payto,
    548                                       &dep->wire_salt,
    549                                       &dep->h_wire);
    550   dep->wire_deadline = wire_deadline;
    551   dep->refund_deadline = refund_deadline;
    552   memset (&cdi,
    553           0,
    554           sizeof (cdi));
    555   cdi.coin = *coin;
    556   TDB_fill (&cdi.csig,
    557             sizeof (cdi.csig),
    558             seed);
    559   cdi.amount_with_fee = TDB_amount (amount);
    560   fees[0] = TDB_amount (fee);
    561   memset (&bd,
    562           0,
    563           sizeof (bd));
    564   bd.merchant_pub = dep->merchant_pub;
    565   bd.merchant_sig = dep->merchant_sig;
    566   bd.h_contract_terms = dep->h_contract_terms;
    567   bd.no_wallet_data_hash = true;
    568   bd.wire_target_h_payto = account->h_full;
    569   bd.wire_salt = dep->wire_salt;
    570   bd.wallet_timestamp = wire_deadline;
    571   bd.refund_deadline = refund_deadline;
    572   bd.wire_deadline = wire_deadline;
    573   bd.receiver_wire_account = account->payto;
    574   bd.cdis = &cdi;
    575   bd.num_cdis = 1;
    576   dep->exchange_timestamp = GNUNET_TIME_timestamp_get ();
    577   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    578                  TALER_EXCHANGEDB_do_deposit (pg,
    579                                               &bd,
    580                                               fees,
    581                                               &dep->exchange_timestamp,
    582                                               &total,
    583                                               &balance_ok,
    584                                               &bad_balance_index,
    585                                               &in_conflict));
    586   GNUNET_assert (balance_ok);
    587   GNUNET_assert (! in_conflict);
    588   {
    589     struct GNUNET_PQ_QueryParam params[] = {
    590       GNUNET_PQ_query_param_auto_from_type (&dep->merchant_pub),
    591       GNUNET_PQ_query_param_auto_from_type (&dep->h_contract_terms),
    592       GNUNET_PQ_query_param_end
    593     };
    594     struct GNUNET_PQ_ResultSpec rs[] = {
    595       GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
    596                                     &dep->serial),
    597       GNUNET_PQ_result_spec_end
    598     };
    599 
    600     GNUNET_assert (GNUNET_OK ==
    601                    GNUNET_PQ_prepare_anon (pg->conn,
    602                                            "SELECT batch_deposit_serial_id"
    603                                            " FROM batch_deposits"
    604                                            " WHERE merchant_pub=$1"
    605                                            "   AND h_contract_terms=$2;"));
    606     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    607                    GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    608                                                              "",
    609                                                              params,
    610                                                              rs));
    611   }
    612 }
    613 
    614 
    615 uint64_t
    616 TDB_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
    617               const struct TDB_Denom *denom,
    618               const struct TALER_ReservePublicKeyP *reserve_pub,
    619               uint32_t seed,
    620               const char *amount)
    621 {
    622   struct TALER_EXCHANGEDB_Withdraw wd;
    623   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
    624   struct TALER_Amount reserve_balance;
    625   struct TALER_BlindedDenominationSignature denom_sig;
    626   uint64_t denom_serial = denom->serial;
    627   uint64_t withdraw_id;
    628   uint16_t allowed_maximum_age;
    629   uint16_t noreveal_index;
    630   uint32_t reserve_birthday;
    631   bool balance_ok;
    632   bool age_ok;
    633   bool idempotent;
    634   bool nonce_reuse;
    635 
    636   memset (&wd,
    637           0,
    638           sizeof (wd));
    639   wd.amount_with_fee = TDB_amount (amount);
    640   wd.reserve_pub = *reserve_pub;
    641   TDB_fill (&wd.planchets_h,
    642             sizeof (wd.planchets_h),
    643             seed);
    644   TDB_fill (&wd.reserve_sig,
    645             sizeof (wd.reserve_sig),
    646             seed);
    647   wd.num_coins = 1;
    648   wd.denom_serials = &denom_serial;
    649   TDB_blinded_denom_sig (seed,
    650                          &denom_sig);
    651   wd.denom_sigs = &denom_sig;
    652   wd.no_blinding_seed = true;
    653   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    654                  TALER_EXCHANGEDB_do_withdraw (pg,
    655                                                &wd,
    656                                                &now,
    657                                                &balance_ok,
    658                                                &reserve_balance,
    659                                                &age_ok,
    660                                                &allowed_maximum_age,
    661                                                &reserve_birthday,
    662                                                &idempotent,
    663                                                &noreveal_index,
    664                                                &nonce_reuse));
    665   GNUNET_assert (balance_ok);
    666   GNUNET_assert (age_ok);
    667   GNUNET_assert (! nonce_reuse);
    668   TALER_blinded_denom_sig_free (&denom_sig);
    669   {
    670     struct GNUNET_PQ_QueryParam params[] = {
    671       GNUNET_PQ_query_param_auto_from_type (&wd.planchets_h),
    672       GNUNET_PQ_query_param_end
    673     };
    674     struct GNUNET_PQ_ResultSpec rs[] = {
    675       GNUNET_PQ_result_spec_uint64 ("withdraw_id",
    676                                     &withdraw_id),
    677       GNUNET_PQ_result_spec_end
    678     };
    679 
    680     GNUNET_assert (GNUNET_OK ==
    681                    GNUNET_PQ_prepare_anon (pg->conn,
    682                                            "SELECT withdraw_id"
    683                                            " FROM withdraw"
    684                                            " WHERE planchets_h=$1;"));
    685     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    686                    GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    687                                                              "",
    688                                                              params,
    689                                                              rs));
    690   }
    691   return withdraw_id;
    692 }
    693 
    694 
    695 uint64_t
    696 TDB_reserve_in (struct TALER_EXCHANGEDB_PostgresContext *pg,
    697                 const struct TDB_Account *account,
    698                 uint32_t seed,
    699                 const char *amount,
    700                 struct TALER_ReservePublicKeyP *reserve_pub)
    701 {
    702   struct TALER_Amount a = TDB_amount (amount);
    703   uint64_t serial;
    704   char *rhex;
    705   char *ahex;
    706 
    707   TDB_reserve (pg,
    708                seed,
    709                amount,
    710                reserve_pub);
    711   rhex = TDB_hex (reserve_pub,
    712                   sizeof (*reserve_pub));
    713   ahex = TDB_hex (&account->h_full,
    714                   sizeof (account->h_full));
    715   GNUNET_assert (GNUNET_OK ==
    716                  TDB_exec (pg,
    717                            "INSERT INTO reserves_in"
    718                            " (reserve_pub,wire_reference,credit"
    719                            " ,wire_source_h_payto"
    720                            " ,exchange_account_section,execution_date)"
    721                            " VALUES (decode('%s','hex'),%u"
    722                            "        ,ROW(%llu,%u)::taler_amount"
    723                            "        ,decode('%s','hex'),'exchange-account-1'"
    724                            "        ,%llu);",
    725                            rhex,
    726                            (unsigned int) seed,
    727                            (unsigned long long) a.value,
    728                            (unsigned int) a.fraction,
    729                            ahex,
    730                            (unsigned long long) 1600000000000000LLU));
    731   {
    732     struct GNUNET_PQ_QueryParam params[] = {
    733       GNUNET_PQ_query_param_auto_from_type (reserve_pub),
    734       GNUNET_PQ_query_param_end
    735     };
    736     struct GNUNET_PQ_ResultSpec rs[] = {
    737       GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
    738                                     &serial),
    739       GNUNET_PQ_result_spec_end
    740     };
    741 
    742     GNUNET_assert (GNUNET_OK ==
    743                    GNUNET_PQ_prepare_anon (
    744                      pg->conn,
    745                      "SELECT reserve_in_serial_id"
    746                      " FROM reserves_in"
    747                      " WHERE reserve_pub=$1;"));
    748     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    749                    GNUNET_PQ_eval_prepared_singleton_select (
    750                      pg->conn,
    751                      "",
    752                      params,
    753                      rs));
    754   }
    755   GNUNET_free (rhex);
    756   GNUNET_free (ahex);
    757   return serial;
    758 }
    759 
    760 
    761 /**
    762  * Main function that runs the checks.
    763  *
    764  * @param cls closure
    765  * @param args remaining command-line arguments
    766  * @param cfgfile name of the configuration file used
    767  * @param cfg configuration
    768  */
    769 static void
    770 run (void *cls,
    771      char *const *args,
    772      const char *cfgfile,
    773      const struct GNUNET_CONFIGURATION_Handle *cfg)
    774 {
    775   struct TALER_EXCHANGEDB_PostgresContext *pg;
    776   unsigned int ran = 0;
    777 
    778   (void) cls;
    779   (void) args;
    780   (void) cfgfile;
    781   pg = TALER_EXCHANGEDB_connect_admin (cfg);
    782   if (NULL == pg)
    783   {
    784     fprintf (stderr,
    785              "Failed to connect to the database\n");
    786     result = 77;
    787     return;
    788   }
    789   if (GNUNET_OK !=
    790       TALER_EXCHANGEDB_create_tables (pg,
    791                                       false,
    792                                       0))
    793   {
    794     fprintf (stderr,
    795              "Failed to create the database schema\n");
    796     result = 77;
    797     goto cleanup;
    798   }
    799   for (unsigned int i = 0; NULL != tdb_tests[i].name; i++)
    800   {
    801     if ( (NULL != only) &&
    802          (0 != strcmp (only,
    803                        tdb_tests[i].name)) )
    804       continue;
    805     fprintf (stderr,
    806              "Running check `%s'\n",
    807              tdb_tests[i].name);
    808     ran++;
    809     if (0 != tdb_tests[i].fn (pg))
    810     {
    811       fprintf (stderr,
    812                "Check `%s' FAILED\n",
    813                tdb_tests[i].name);
    814       result = 1;
    815     }
    816   }
    817   if (0 == ran)
    818   {
    819     fprintf (stderr,
    820              "No check matched `%s'\n",
    821              only);
    822     result = 1;
    823   }
    824 cleanup:
    825   TALER_EXCHANGEDB_disconnect (pg);
    826 }
    827 
    828 
    829 int
    830 TDB_main (int argc,
    831           char *const *argv,
    832           const char *binary,
    833           const char *description,
    834           const struct TDB_Test *tests)
    835 {
    836   struct GNUNET_GETOPT_CommandLineOption options[] = {
    837     GNUNET_GETOPT_option_string ('t',
    838                                  "test",
    839                                  "NAME",
    840                                  "only run the check called NAME",
    841                                  &only),
    842     GNUNET_GETOPT_OPTION_END
    843   };
    844   enum GNUNET_GenericReturnValue ret;
    845 
    846   result = 0;
    847   tdb_tests = tests;
    848   ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
    849                             argc,
    850                             argv,
    851                             binary,
    852                             description,
    853                             options,
    854                             &run,
    855                             NULL);
    856   if (GNUNET_SYSERR == ret)
    857     return 3;
    858   if (GNUNET_NO == ret)
    859     return 0;
    860   return result;
    861 }
    862 
    863 
    864 /* end of test_common.c */