exchange

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

test_common.h (14984B)


      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.h
     18  * @brief shared scaffolding for the per-table exchangedb tests
     19  * @author Christian Grothoff
     20  *
     21  * Every `test_$TABLE.c` in this directory is a stand-alone binary that runs
     22  * the exported database functions whose primary test table is `$TABLE`
     23  * against a scratch database.  This header holds the bits all of them need:
     24  * the main loop, a raw-SQL escape hatch and fixtures for the rows that the
     25  * functions under test reference by foreign key.
     26  *
     27  * The test data is deliberately bogus: keys are derived from a seed byte and
     28  * signatures are never verifiable.  The database layer does not check any of
     29  * that, and building real signatures would only make the tests slower and
     30  * harder to read.
     31  */
     32 #ifndef TEST_COMMON_H
     33 #define TEST_COMMON_H
     34 
     35 #include "exchangedb_lib.h"
     36 #include "helper.h"
     37 #include "taler/taler_json_lib.h"
     38 
     39 
     40 /**
     41  * Currency the scratch database is configured for.  Must match
     42  * test_table.sh.
     43  */
     44 #define CURRENCY "EUR"
     45 
     46 
     47 /**
     48  * Report a failed expectation and return 1 from the calling check.
     49  */
     50 #define FAILIF(cond)                            \
     51         do {                                          \
     52           if (! (cond)) break;                        \
     53           GNUNET_break (0);                           \
     54           fprintf (stderr,                            \
     55                    "FAILED: %s at %s:%u\n",           \
     56                    # cond, __FILE__, __LINE__);       \
     57           return 1;                                   \
     58         } while (0)
     59 
     60 
     61 /**
     62  * Like #FAILIF(), but runs @a cleanup before returning.
     63  */
     64 #define FAILIF_C(cond,cleanup)                  \
     65         do {                                          \
     66           if (! (cond)) break;                        \
     67           GNUNET_break (0);                           \
     68           fprintf (stderr,                            \
     69                    "FAILED: %s at %s:%u\n",           \
     70                    # cond, __FILE__, __LINE__);       \
     71           cleanup;                                    \
     72           return 1;                                   \
     73         } while (0)
     74 
     75 
     76 /**
     77  * One check in a `test_$TABLE.c` binary.
     78  */
     79 struct TDB_Test
     80 {
     81   /**
     82    * Name of the check, used for the `-t` command-line option.  NULL
     83    * terminates the array.
     84    */
     85   const char *name;
     86 
     87   /**
     88    * Function to run.  Returns 0 on success.
     89    */
     90   int (*fn)(struct TALER_EXCHANGEDB_PostgresContext *pg);
     91 };
     92 
     93 
     94 /**
     95  * Run the checks in @a tests.  Connects to the database, creates the
     96  * schema and then runs every check (or only the one named with `-t`).
     97  *
     98  * @param argc number of arguments in @a argv
     99  * @param argv command line
    100  * @param binary name of the binary, for the usage message
    101  * @param description one-line description of the binary
    102  * @param tests NULL-terminated array of checks to run
    103  * @return 0 on success, 1 if a check failed, 77 if the database was
    104  *         unusable, 3 on a command-line error
    105  */
    106 int
    107 TDB_main (int argc,
    108           char *const *argv,
    109           const char *binary,
    110           const char *description,
    111           const struct TDB_Test *tests);
    112 
    113 
    114 /**
    115  * Run @a fmt on the database connection, outside of any transaction.
    116  * Used to set up rows that no exported function can create, and to
    117  * assert over the database from the outside.
    118  *
    119  * @param pg the database context
    120  * @param fmt printf-style SQL statement(s)
    121  * @return #GNUNET_OK on success
    122  */
    123 enum GNUNET_GenericReturnValue
    124 TDB_exec (struct TALER_EXCHANGEDB_PostgresContext *pg,
    125           const char *fmt,
    126           ...)
    127 __attribute__ ((format (printf, 2, 3)));
    128 
    129 
    130 /**
    131  * Count the rows the SQL fragment @a fmt selects.  The fragment is
    132  * appended to "SELECT COUNT(*) AS n ", so it starts with FROM.
    133  *
    134  * @param pg the database context
    135  * @param fmt printf-style SQL fragment, e.g. "FROM reserves WHERE ..."
    136  * @return number of matching rows
    137  */
    138 uint64_t
    139 TDB_count (struct TALER_EXCHANGEDB_PostgresContext *pg,
    140            const char *fmt,
    141            ...)
    142 __attribute__ ((format (printf, 2, 3)));
    143 
    144 
    145 /**
    146  * Convert @a str to an amount in our currency.
    147  *
    148  * @param str amount without the currency prefix, e.g. "10.5"
    149  * @return the parsed amount
    150  */
    151 struct TALER_Amount
    152 TDB_amount (const char *str);
    153 
    154 
    155 /**
    156  * Fill @a size bytes at @a ptr with data derived from @a seed.  Different
    157  * seeds give different data, the same seed always gives the same data.
    158  *
    159  * @param[out] ptr buffer to fill
    160  * @param size number of bytes to write to @a ptr
    161  * @param seed seed to derive the data from
    162  */
    163 void
    164 TDB_fill (void *ptr,
    165           size_t size,
    166           uint32_t seed);
    167 
    168 
    169 /**
    170  * Fill the object @a x with data derived from @a seed.
    171  */
    172 #define TDB_FILL(x,seed) TDB_fill (&(x), sizeof (x), seed)
    173 
    174 
    175 /**
    176  * Render @a size bytes at @a data as a lowercase hex string for use in an
    177  * SQL literal.
    178  *
    179  * @param data binary data
    180  * @param size number of bytes in @a data
    181  * @return hex string, to be freed by the caller
    182  */
    183 char *
    184 TDB_hex (const void *data,
    185          size_t size);
    186 
    187 
    188 /**
    189  * A denomination that exists in the `denominations` table.
    190  */
    191 struct TDB_Denom
    192 {
    193   /**
    194    * Private key; never used to sign anything meaningful.
    195    */
    196   struct TALER_DenominationPrivateKey priv;
    197 
    198   /**
    199    * Public key, as stored in the database.
    200    */
    201   struct TALER_DenominationPublicKey pub;
    202 
    203   /**
    204    * Hash of @e pub, the primary key of the denomination.
    205    */
    206   struct TALER_DenominationHashP h_denom_pub;
    207 
    208   /**
    209    * Row of the denomination in the `denominations` table.
    210    */
    211   uint64_t serial;
    212 
    213   /**
    214    * Value, fees and validity period as inserted.
    215    */
    216   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
    217 };
    218 
    219 
    220 /**
    221  * Create a denomination and insert it into the `denominations` table.
    222  * Fails hard if the insert does not work: a test that cannot set up its
    223  * fixtures has nothing to say about the function under test.
    224  *
    225  * @param pg the database context
    226  * @param seed seed for the key material and the master signature
    227  * @param value value of a coin of this denomination, e.g. "5"
    228  * @param fee fee charged for every operation on such a coin, e.g. "0.1"
    229  * @param[out] denom set to the denomination
    230  */
    231 void
    232 TDB_denom (struct TALER_EXCHANGEDB_PostgresContext *pg,
    233            uint32_t seed,
    234            const char *value,
    235            const char *fee,
    236            struct TDB_Denom *denom);
    237 
    238 
    239 /**
    240  * Release the key material of @a denom.  The row stays in the database.
    241  *
    242  * @param[in,out] denom denomination to clean up
    243  */
    244 void
    245 TDB_denom_free (struct TDB_Denom *denom);
    246 
    247 
    248 /**
    249  * Build an (invalid) denomination signature that the database layer will
    250  * happily store and read back.
    251  *
    252  * @param seed seed to derive the signature from
    253  * @param[out] sig set to the signature, to be freed with
    254  *        TALER_denom_sig_free()
    255  */
    256 void
    257 TDB_denom_sig (uint32_t seed,
    258                struct TALER_DenominationSignature *sig);
    259 
    260 
    261 /**
    262  * Build an (invalid) blinded denomination signature that the database
    263  * layer will happily store and read back.
    264  *
    265  * @param seed seed to derive the signature from
    266  * @param[out] sig set to the signature, to be freed with
    267  *        TALER_blinded_denom_sig_free()
    268  */
    269 void
    270 TDB_blinded_denom_sig (uint32_t seed,
    271                        struct TALER_BlindedDenominationSignature *sig);
    272 
    273 
    274 /**
    275  * Create a coin of denomination @a denom and insert it into the
    276  * `known_coins` table.  Fails hard if the insert does not work.
    277  *
    278  * @param pg the database context
    279  * @param denom denomination of the coin
    280  * @param seed seed for the coin's key and signature
    281  * @param[out] coin set to the coin, to be cleaned up with TDB_coin_free()
    282  * @param[out] known_coin_id set to the row of the coin, may be NULL
    283  */
    284 void
    285 TDB_coin (struct TALER_EXCHANGEDB_PostgresContext *pg,
    286           const struct TDB_Denom *denom,
    287           uint32_t seed,
    288           struct TALER_CoinPublicInfo *coin,
    289           uint64_t *known_coin_id);
    290 
    291 
    292 /**
    293  * Release the signature of @a coin.  The row stays in the database.
    294  *
    295  * @param[in,out] coin coin to clean up
    296  */
    297 void
    298 TDB_coin_free (struct TALER_CoinPublicInfo *coin);
    299 
    300 
    301 /**
    302  * A bank account known to the exchange.
    303  */
    304 struct TDB_Account
    305 {
    306   /**
    307    * Full payto URI of the account.
    308    */
    309   struct TALER_FullPayto payto;
    310 
    311   /**
    312    * Hash of @e payto, the primary key in `wire_targets`.
    313    */
    314   struct TALER_FullPaytoHashP h_full;
    315 
    316   /**
    317    * Hash of the normalized form of @e payto, the primary key in
    318    * `kyc_targets`.
    319    */
    320   struct TALER_NormalizedPaytoHashP h_normalized;
    321 };
    322 
    323 
    324 /**
    325  * Create a `kyc_targets` and a `wire_targets` row for a fresh bank
    326  * account.  Both tables are referenced by foreign keys all over the
    327  * schema, but no exported function creates a row in them on its own.
    328  *
    329  * @param pg the database context
    330  * @param seed seed for the account name
    331  * @param[out] account set to the account, to be cleaned up with
    332  *        TDB_account_free()
    333  */
    334 void
    335 TDB_account (struct TALER_EXCHANGEDB_PostgresContext *pg,
    336              uint32_t seed,
    337              struct TDB_Account *account);
    338 
    339 
    340 /**
    341  * Release the payto URI of @a account.  The rows stay in the database.
    342  *
    343  * @param[in,out] account account to clean up
    344  */
    345 void
    346 TDB_account_free (struct TDB_Account *account);
    347 
    348 
    349 /**
    350  * Create a reserve with the given @a balance, as
    351  * exchange_do_purse_merge() does: without a `reserves_in` row, so the
    352  * reserve has no origin account.
    353  *
    354  * @param pg the database context
    355  * @param seed seed for the reserve's public key
    356  * @param balance balance to give the reserve, e.g. "10"
    357  * @param[out] reserve_pub set to the public key of the reserve
    358  */
    359 void
    360 TDB_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg,
    361              uint32_t seed,
    362              const char *balance,
    363              struct TALER_ReservePublicKeyP *reserve_pub);
    364 
    365 
    366 /**
    367  * A purse, as TDB_purse() created it.
    368  */
    369 struct TDB_Purse
    370 {
    371   /**
    372    * Public key of the purse.
    373    */
    374   struct TALER_PurseContractPublicKeyP purse_pub;
    375 
    376   /**
    377    * Key that allows merging the purse.
    378    */
    379   struct TALER_PurseMergePublicKeyP merge_pub;
    380 
    381   /**
    382    * Contract the purse is for.
    383    */
    384   struct TALER_PrivateContractHashP h_contract_terms;
    385 
    386   /**
    387    * Signature over the purse's meta data.
    388    */
    389   struct TALER_PurseContractSignatureP purse_sig;
    390 
    391   /**
    392    * When the purse expires.
    393    */
    394   struct GNUNET_TIME_Timestamp purse_expiration;
    395 
    396   /**
    397    * Target amount of the purse.
    398    */
    399   struct TALER_Amount amount;
    400 };
    401 
    402 
    403 /**
    404  * Create a purse with #TALER_EXCHANGEDB_insert_purse_request().  Fails
    405  * hard if the purse cannot be created.
    406  *
    407  * @param pg the database context
    408  * @param seed seed for the purse's keys, contract and signature
    409  * @param amount target amount of the purse, e.g. "5"
    410  * @param purse_expiration when the purse expires
    411  * @param[out] purse set to the purse
    412  */
    413 void
    414 TDB_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
    415            uint32_t seed,
    416            const char *amount,
    417            struct GNUNET_TIME_Timestamp purse_expiration,
    418            struct TDB_Purse *purse);
    419 
    420 
    421 /**
    422  * A batch deposit of one coin, as TDB_deposit() made it.
    423  */
    424 struct TDB_Deposit
    425 {
    426   /**
    427    * Merchant the coin was deposited with.
    428    */
    429   struct TALER_MerchantPublicKeyP merchant_pub;
    430 
    431   /**
    432    * Merchant signature over the contract.
    433    */
    434   struct TALER_MerchantSignatureP merchant_sig;
    435 
    436   /**
    437    * Contract the deposit is for.
    438    */
    439   struct TALER_PrivateContractHashP h_contract_terms;
    440 
    441   /**
    442    * Salt the merchant used for @e h_wire.
    443    */
    444   struct TALER_WireSaltP wire_salt;
    445 
    446   /**
    447    * Salted hash of the merchant's account.
    448    */
    449   struct TALER_MerchantWireHashP h_wire;
    450 
    451   /**
    452    * When the merchant wants to be paid.
    453    */
    454   struct GNUNET_TIME_Timestamp wire_deadline;
    455 
    456   /**
    457    * Until when the merchant may refund.
    458    */
    459   struct GNUNET_TIME_Timestamp refund_deadline;
    460 
    461   /**
    462    * Time the exchange recorded for the deposit.
    463    */
    464   struct GNUNET_TIME_Timestamp exchange_timestamp;
    465 
    466   /**
    467    * Row of the deposit in `batch_deposits`.
    468    */
    469   uint64_t serial;
    470 };
    471 
    472 
    473 /**
    474  * Deposit @a coin with a fresh merchant, creating the `batch_deposits`
    475  * and `coin_deposits` rows for it.  Fails hard if the deposit does not go
    476  * through.
    477  *
    478  * @param pg the database context
    479  * @param account account the merchant wants to be paid at
    480  * @param coin coin to deposit
    481  * @param seed seed for the merchant key, the contract and the salt
    482  * @param amount amount to deposit, e.g. "1"
    483  * @param fee deposit fee of the coin's denomination, e.g. "0.1"
    484  * @param wire_deadline when the merchant wants to be paid
    485  * @param refund_deadline until when the merchant may refund
    486  * @param[out] dep set to the deposit
    487  */
    488 void
    489 TDB_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg,
    490              const struct TDB_Account *account,
    491              const struct TALER_CoinPublicInfo *coin,
    492              uint32_t seed,
    493              const char *amount,
    494              const char *fee,
    495              struct GNUNET_TIME_Timestamp wire_deadline,
    496              struct GNUNET_TIME_Timestamp refund_deadline,
    497              struct TDB_Deposit *dep);
    498 
    499 
    500 /**
    501  * Withdraw one coin of @a denom from @a reserve_pub, creating a `withdraw`
    502  * row for it.  The reserve must hold at least @a amount.
    503  *
    504  * @param pg the database context
    505  * @param denom denomination of the coin to withdraw
    506  * @param reserve_pub reserve to withdraw from
    507  * @param seed seed for the planchet hash and the signatures
    508  * @param amount amount to withdraw, e.g. "5"
    509  * @return row of the new entry in `withdraw`
    510  */
    511 uint64_t
    512 TDB_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
    513               const struct TDB_Denom *denom,
    514               const struct TALER_ReservePublicKeyP *reserve_pub,
    515               uint32_t seed,
    516               const char *amount);
    517 
    518 
    519 /**
    520  * Wire @a amount from @a account into a fresh reserve, creating a
    521  * `reserves_in` row for it.
    522  *
    523  * @param pg the database context
    524  * @param account account the money comes from
    525  * @param seed seed for the reserve's public key and the wire subject
    526  * @param amount amount that was wired, e.g. "10"
    527  * @param[out] reserve_pub set to the public key of the reserve
    528  * @return row of the new entry in `reserves_in`
    529  */
    530 uint64_t
    531 TDB_reserve_in (struct TALER_EXCHANGEDB_PostgresContext *pg,
    532                 const struct TDB_Account *account,
    533                 uint32_t seed,
    534                 const char *amount,
    535                 struct TALER_ReservePublicKeyP *reserve_pub);
    536 
    537 
    538 #endif