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


      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_coin.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_DenominationHashP dh;
    350   struct TALER_AgeCommitmentHashP hac;
    351   enum TALER_EXCHANGEDB_CoinKnownStatus cks;
    352   uint64_t kci;
    353 
    354   memset (coin,
    355           0,
    356           sizeof (*coin));
    357   TDB_FILL (coin->coin_pub,
    358             seed);
    359   coin->denom_pub_hash = denom->h_denom_pub;
    360   coin->no_age_commitment = true;
    361   TDB_denom_sig (seed,
    362                  &coin->denom_sig);
    363   cks = TALER_EXCHANGEDB_do_insert_known_coin (pg,
    364                                                coin,
    365                                                &kci,
    366                                                &dh,
    367                                                &hac);
    368   /* CKS_PRESENT is fine: the same seed may name a coin an earlier check
    369      already made known. */
    370   GNUNET_assert ( (TALER_EXCHANGEDB_CKS_ADDED == cks) ||
    371                   (TALER_EXCHANGEDB_CKS_PRESENT == cks) );
    372   if (NULL != known_coin_id)
    373     *known_coin_id = kci;
    374 }
    375 
    376 
    377 void
    378 TDB_coin_free (struct TALER_CoinPublicInfo *coin)
    379 {
    380   TALER_denom_sig_free (&coin->denom_sig);
    381 }
    382 
    383 
    384 void
    385 TDB_account (struct TALER_EXCHANGEDB_PostgresContext *pg,
    386              uint32_t seed,
    387              struct TDB_Account *account)
    388 {
    389   struct TALER_NormalizedPayto npayto;
    390   char *fhex;
    391   char *nhex;
    392 
    393   GNUNET_asprintf (&account->payto.full_payto,
    394                    "payto://x-taler-bank/localhost/tdb%u?receiver-name=tdb%u",
    395                    (unsigned int) seed,
    396                    (unsigned int) seed);
    397   TALER_full_payto_hash (account->payto,
    398                          &account->h_full);
    399   npayto = TALER_payto_normalize (account->payto);
    400   GNUNET_assert (NULL != npayto.normalized_payto);
    401   TALER_normalized_payto_hash (npayto,
    402                                &account->h_normalized);
    403   GNUNET_free (npayto.normalized_payto);
    404   fhex = TDB_hex (&account->h_full,
    405                   sizeof (account->h_full));
    406   nhex = TDB_hex (&account->h_normalized,
    407                   sizeof (account->h_normalized));
    408   GNUNET_assert (GNUNET_OK ==
    409                  TDB_exec (pg,
    410                            "INSERT INTO kyc_targets"
    411                            " (h_normalized_payto,is_wallet)"
    412                            " VALUES (decode('%s','hex'),FALSE)"
    413                            " ON CONFLICT DO NOTHING;"
    414                            "INSERT INTO wire_targets"
    415                            " (wire_target_h_payto,payto_uri"
    416                            " ,h_normalized_payto)"
    417                            " VALUES (decode('%s','hex'),'%s'"
    418                            "        ,decode('%s','hex'))"
    419                            " ON CONFLICT DO NOTHING;",
    420                            nhex,
    421                            fhex,
    422                            account->payto.full_payto,
    423                            nhex));
    424   GNUNET_free (fhex);
    425   GNUNET_free (nhex);
    426 }
    427 
    428 
    429 void
    430 TDB_account_free (struct TDB_Account *account)
    431 {
    432   GNUNET_free (account->payto.full_payto);
    433 }
    434 
    435 
    436 void
    437 TDB_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg,
    438              uint32_t seed,
    439              const char *balance,
    440              struct TALER_ReservePublicKeyP *reserve_pub)
    441 {
    442   struct TALER_Amount b = TDB_amount (balance);
    443   char *hex;
    444 
    445   TDB_fill (reserve_pub,
    446             sizeof (*reserve_pub),
    447             seed);
    448   hex = TDB_hex (reserve_pub,
    449                  sizeof (*reserve_pub));
    450   GNUNET_assert (GNUNET_OK ==
    451                  TDB_exec (pg,
    452                            "INSERT INTO reserves"
    453                            " (reserve_pub,current_balance"
    454                            " ,expiration_date,gc_date)"
    455                            " VALUES (decode('%s','hex')"
    456                            "        ,ROW(%llu,%u)::taler_amount"
    457                            "        ,%llu,%llu)"
    458                            " ON CONFLICT DO NOTHING;",
    459                            hex,
    460                            (unsigned long long) b.value,
    461                            (unsigned int) b.fraction,
    462                            (unsigned long long) 4000000000000000LLU,
    463                            (unsigned long long) 5000000000000000LLU));
    464   GNUNET_free (hex);
    465 }
    466 
    467 
    468 void
    469 TDB_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
    470            uint32_t seed,
    471            const char *amount,
    472            struct GNUNET_TIME_Timestamp purse_expiration,
    473            struct TDB_Purse *purse)
    474 {
    475   struct TALER_Amount purse_fee = TDB_amount ("0");
    476   enum GNUNET_DB_QueryStatus qs;
    477   bool in_conflict = false;
    478 
    479   memset (purse,
    480           0,
    481           sizeof (*purse));
    482   TDB_fill (&purse->purse_pub,
    483             sizeof (purse->purse_pub),
    484             seed);
    485   TDB_fill (&purse->merge_pub,
    486             sizeof (purse->merge_pub),
    487             seed);
    488   TDB_fill (&purse->h_contract_terms,
    489             sizeof (purse->h_contract_terms),
    490             seed);
    491   TDB_fill (&purse->purse_sig,
    492             sizeof (purse->purse_sig),
    493             seed);
    494   purse->purse_expiration = purse_expiration;
    495   purse->amount = TDB_amount (amount);
    496   qs = TALER_EXCHANGEDB_insert_purse_request (
    497     pg,
    498     &purse->purse_pub,
    499     &purse->merge_pub,
    500     purse_expiration,
    501     &purse->h_contract_terms,
    502     0,
    503     TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA,
    504     &purse_fee,
    505     &purse->amount,
    506     &purse->purse_sig,
    507     &in_conflict);
    508   /* SUCCESS_NO_RESULTS is the answer for an identical replay, which is
    509      what a second call with the same seed is. */
    510   GNUNET_assert (0 <= qs);
    511   GNUNET_assert (! in_conflict);
    512 }
    513 
    514 
    515 void
    516 TDB_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg,
    517              const struct TDB_Account *account,
    518              const struct TALER_CoinPublicInfo *coin,
    519              uint32_t seed,
    520              const char *amount,
    521              const char *fee,
    522              struct GNUNET_TIME_Timestamp wire_deadline,
    523              struct GNUNET_TIME_Timestamp refund_deadline,
    524              struct TDB_Deposit *dep)
    525 {
    526   struct TALER_EXCHANGEDB_CoinDepositInformation cdi;
    527   struct TALER_EXCHANGEDB_BatchDeposit bd;
    528   struct TALER_Amount fees[1];
    529   struct TALER_Amount total;
    530   uint32_t bad_balance_index;
    531   bool balance_ok;
    532   bool in_conflict;
    533 
    534   memset (dep,
    535           0,
    536           sizeof (*dep));
    537   TDB_fill (&dep->merchant_pub,
    538             sizeof (dep->merchant_pub),
    539             seed);
    540   TDB_fill (&dep->merchant_sig,
    541             sizeof (dep->merchant_sig),
    542             seed);
    543   TDB_fill (&dep->h_contract_terms,
    544             sizeof (dep->h_contract_terms),
    545             seed);
    546   TDB_fill (&dep->wire_salt,
    547             sizeof (dep->wire_salt),
    548             seed);
    549   TALER_merchant_wire_signature_hash (account->payto,
    550                                       &dep->wire_salt,
    551                                       &dep->h_wire);
    552   dep->wire_deadline = wire_deadline;
    553   dep->refund_deadline = refund_deadline;
    554   memset (&cdi,
    555           0,
    556           sizeof (cdi));
    557   cdi.coin = *coin;
    558   TDB_fill (&cdi.csig,
    559             sizeof (cdi.csig),
    560             seed);
    561   cdi.amount_with_fee = TDB_amount (amount);
    562   fees[0] = TDB_amount (fee);
    563   memset (&bd,
    564           0,
    565           sizeof (bd));
    566   bd.merchant_pub = dep->merchant_pub;
    567   bd.merchant_sig = dep->merchant_sig;
    568   bd.h_contract_terms = dep->h_contract_terms;
    569   bd.no_wallet_data_hash = true;
    570   bd.wire_target_h_payto = account->h_full;
    571   bd.wire_salt = dep->wire_salt;
    572   bd.wallet_timestamp = wire_deadline;
    573   bd.refund_deadline = refund_deadline;
    574   bd.wire_deadline = wire_deadline;
    575   bd.receiver_wire_account = account->payto;
    576   bd.cdis = &cdi;
    577   bd.num_cdis = 1;
    578   dep->exchange_timestamp = GNUNET_TIME_timestamp_get ();
    579   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    580                  TALER_EXCHANGEDB_do_deposit (pg,
    581                                               &bd,
    582                                               fees,
    583                                               &dep->exchange_timestamp,
    584                                               &total,
    585                                               &balance_ok,
    586                                               &bad_balance_index,
    587                                               &in_conflict));
    588   GNUNET_assert (balance_ok);
    589   GNUNET_assert (! in_conflict);
    590   {
    591     struct GNUNET_PQ_QueryParam params[] = {
    592       GNUNET_PQ_query_param_auto_from_type (&dep->merchant_pub),
    593       GNUNET_PQ_query_param_auto_from_type (&dep->h_contract_terms),
    594       GNUNET_PQ_query_param_end
    595     };
    596     struct GNUNET_PQ_ResultSpec rs[] = {
    597       GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
    598                                     &dep->serial),
    599       GNUNET_PQ_result_spec_end
    600     };
    601 
    602     GNUNET_assert (GNUNET_OK ==
    603                    GNUNET_PQ_prepare_anon (pg->conn,
    604                                            "SELECT batch_deposit_serial_id"
    605                                            " FROM batch_deposits"
    606                                            " WHERE merchant_pub=$1"
    607                                            "   AND h_contract_terms=$2;"));
    608     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    609                    GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    610                                                              "",
    611                                                              params,
    612                                                              rs));
    613   }
    614 }
    615 
    616 
    617 uint64_t
    618 TDB_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
    619               const struct TDB_Denom *denom,
    620               const struct TALER_ReservePublicKeyP *reserve_pub,
    621               uint32_t seed,
    622               const char *amount)
    623 {
    624   struct TALER_EXCHANGEDB_Withdraw wd;
    625   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
    626   struct TALER_Amount reserve_balance;
    627   struct TALER_BlindedDenominationSignature denom_sig;
    628   uint64_t denom_serial = denom->serial;
    629   uint64_t withdraw_id;
    630   uint16_t allowed_maximum_age;
    631   uint16_t noreveal_index;
    632   uint32_t reserve_birthday;
    633   bool balance_ok;
    634   bool age_ok;
    635   bool idempotent;
    636   bool nonce_reuse;
    637 
    638   memset (&wd,
    639           0,
    640           sizeof (wd));
    641   wd.amount_with_fee = TDB_amount (amount);
    642   wd.reserve_pub = *reserve_pub;
    643   TDB_fill (&wd.planchets_h,
    644             sizeof (wd.planchets_h),
    645             seed);
    646   TDB_fill (&wd.reserve_sig,
    647             sizeof (wd.reserve_sig),
    648             seed);
    649   wd.num_coins = 1;
    650   wd.denom_serials = &denom_serial;
    651   TDB_blinded_denom_sig (seed,
    652                          &denom_sig);
    653   wd.denom_sigs = &denom_sig;
    654   wd.no_blinding_seed = true;
    655   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    656                  TALER_EXCHANGEDB_do_withdraw (pg,
    657                                                &wd,
    658                                                &now,
    659                                                &balance_ok,
    660                                                &reserve_balance,
    661                                                &age_ok,
    662                                                &allowed_maximum_age,
    663                                                &reserve_birthday,
    664                                                &idempotent,
    665                                                &noreveal_index,
    666                                                &nonce_reuse));
    667   GNUNET_assert (balance_ok);
    668   GNUNET_assert (age_ok);
    669   GNUNET_assert (! nonce_reuse);
    670   TALER_blinded_denom_sig_free (&denom_sig);
    671   {
    672     struct GNUNET_PQ_QueryParam params[] = {
    673       GNUNET_PQ_query_param_auto_from_type (&wd.planchets_h),
    674       GNUNET_PQ_query_param_end
    675     };
    676     struct GNUNET_PQ_ResultSpec rs[] = {
    677       GNUNET_PQ_result_spec_uint64 ("withdraw_id",
    678                                     &withdraw_id),
    679       GNUNET_PQ_result_spec_end
    680     };
    681 
    682     GNUNET_assert (GNUNET_OK ==
    683                    GNUNET_PQ_prepare_anon (pg->conn,
    684                                            "SELECT withdraw_id"
    685                                            " FROM withdraw"
    686                                            " WHERE planchets_h=$1;"));
    687     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    688                    GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    689                                                              "",
    690                                                              params,
    691                                                              rs));
    692   }
    693   return withdraw_id;
    694 }
    695 
    696 
    697 uint64_t
    698 TDB_reserve_in (struct TALER_EXCHANGEDB_PostgresContext *pg,
    699                 const struct TDB_Account *account,
    700                 uint32_t seed,
    701                 const char *amount,
    702                 struct TALER_ReservePublicKeyP *reserve_pub)
    703 {
    704   struct TALER_Amount a = TDB_amount (amount);
    705   uint64_t serial;
    706   char *rhex;
    707   char *ahex;
    708 
    709   TDB_reserve (pg,
    710                seed,
    711                amount,
    712                reserve_pub);
    713   rhex = TDB_hex (reserve_pub,
    714                   sizeof (*reserve_pub));
    715   ahex = TDB_hex (&account->h_full,
    716                   sizeof (account->h_full));
    717   GNUNET_assert (GNUNET_OK ==
    718                  TDB_exec (pg,
    719                            "INSERT INTO reserves_in"
    720                            " (reserve_pub,wire_reference,credit"
    721                            " ,wire_source_h_payto"
    722                            " ,exchange_account_section,execution_date)"
    723                            " VALUES (decode('%s','hex'),%u"
    724                            "        ,ROW(%llu,%u)::taler_amount"
    725                            "        ,decode('%s','hex'),'exchange-account-1'"
    726                            "        ,%llu);",
    727                            rhex,
    728                            (unsigned int) seed,
    729                            (unsigned long long) a.value,
    730                            (unsigned int) a.fraction,
    731                            ahex,
    732                            (unsigned long long) 1600000000000000LLU));
    733   {
    734     struct GNUNET_PQ_QueryParam params[] = {
    735       GNUNET_PQ_query_param_auto_from_type (reserve_pub),
    736       GNUNET_PQ_query_param_end
    737     };
    738     struct GNUNET_PQ_ResultSpec rs[] = {
    739       GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
    740                                     &serial),
    741       GNUNET_PQ_result_spec_end
    742     };
    743 
    744     GNUNET_assert (GNUNET_OK ==
    745                    GNUNET_PQ_prepare_anon (
    746                      pg->conn,
    747                      "SELECT reserve_in_serial_id"
    748                      " FROM reserves_in"
    749                      " WHERE reserve_pub=$1;"));
    750     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    751                    GNUNET_PQ_eval_prepared_singleton_select (
    752                      pg->conn,
    753                      "",
    754                      params,
    755                      rs));
    756   }
    757   GNUNET_free (rhex);
    758   GNUNET_free (ahex);
    759   return serial;
    760 }
    761 
    762 
    763 /**
    764  * Main function that runs the checks.
    765  *
    766  * @param cls closure
    767  * @param args remaining command-line arguments
    768  * @param cfgfile name of the configuration file used
    769  * @param cfg configuration
    770  */
    771 static void
    772 run (void *cls,
    773      char *const *args,
    774      const char *cfgfile,
    775      const struct GNUNET_CONFIGURATION_Handle *cfg)
    776 {
    777   struct TALER_EXCHANGEDB_PostgresContext *pg;
    778   unsigned int ran = 0;
    779 
    780   (void) cls;
    781   (void) args;
    782   (void) cfgfile;
    783   pg = TALER_EXCHANGEDB_connect_admin (cfg);
    784   if (NULL == pg)
    785   {
    786     fprintf (stderr,
    787              "Failed to connect to the database\n");
    788     result = 77;
    789     return;
    790   }
    791   if (GNUNET_OK !=
    792       TALER_EXCHANGEDB_create_tables (pg,
    793                                       false,
    794                                       0))
    795   {
    796     fprintf (stderr,
    797              "Failed to create the database schema\n");
    798     result = 77;
    799     goto cleanup;
    800   }
    801   for (unsigned int i = 0; NULL != tdb_tests[i].name; i++)
    802   {
    803     if ( (NULL != only) &&
    804          (0 != strcmp (only,
    805                        tdb_tests[i].name)) )
    806       continue;
    807     fprintf (stderr,
    808              "Running check `%s'\n",
    809              tdb_tests[i].name);
    810     ran++;
    811     if (0 != tdb_tests[i].fn (pg))
    812     {
    813       fprintf (stderr,
    814                "Check `%s' FAILED\n",
    815                tdb_tests[i].name);
    816       result = 1;
    817     }
    818   }
    819   if (0 == ran)
    820   {
    821     fprintf (stderr,
    822              "No check matched `%s'\n",
    823              only);
    824     result = 1;
    825   }
    826 cleanup:
    827   TALER_EXCHANGEDB_disconnect (pg);
    828 }
    829 
    830 
    831 int
    832 TDB_main (int argc,
    833           char *const *argv,
    834           const char *binary,
    835           const char *description,
    836           const struct TDB_Test *tests)
    837 {
    838   struct GNUNET_GETOPT_CommandLineOption options[] = {
    839     GNUNET_GETOPT_option_string ('t',
    840                                  "test",
    841                                  "NAME",
    842                                  "only run the check called NAME",
    843                                  &only),
    844     GNUNET_GETOPT_OPTION_END
    845   };
    846   enum GNUNET_GenericReturnValue ret;
    847 
    848   result = 0;
    849   tdb_tests = tests;
    850   ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
    851                             argc,
    852                             argv,
    853                             binary,
    854                             description,
    855                             options,
    856                             &run,
    857                             NULL);
    858   if (GNUNET_SYSERR == ret)
    859     return 3;
    860   if (GNUNET_NO == ret)
    861     return 0;
    862   return result;
    863 }
    864 
    865 
    866 /* end of test_common.c */