exchange

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

test_contracts.c (11161B)


      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_contracts.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `contracts`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_insert_contract(),
     23  * #TALER_EXCHANGEDB_get_contract() and
     24  * #TALER_EXCHANGEDB_get_contract_by_purse().
     25  *
     26  * `contracts` references `purse_requests`: the encrypted contract belongs
     27  * to a purse.  It is fetched either by the purse or by the Diffie-Hellman
     28  * public key the client derived, which is why there are two getters.  A
     29  * second, different contract for the same purse is reported through
     30  * @e in_conflict rather than as an error.
     31  */
     32 #include "test_common.h"
     33 #include "exchange-database/get_contract.h"
     34 #include "exchange-database/get_contract_by_purse.h"
     35 #include "exchange-database/insert_contract.h"
     36 
     37 
     38 /**
     39  * Build a timestamp from a number of seconds since the epoch.
     40  *
     41  * @param secs seconds since the epoch
     42  * @return the timestamp
     43  */
     44 static struct GNUNET_TIME_Timestamp
     45 ts (uint64_t secs)
     46 {
     47   struct GNUNET_TIME_Absolute abs = {
     48     .abs_value_us = secs * 1000LLU * 1000LLU
     49   };
     50 
     51   return GNUNET_TIME_absolute_to_timestamp (abs);
     52 }
     53 
     54 
     55 /**
     56  * Build an encrypted contract.
     57  *
     58  * @param seed seed for the key, signature and payload
     59  * @param payload contents of the "encrypted" contract
     60  * @param[out] econtract set to the contract; free @e econtract with
     61  *        GNUNET_free()
     62  */
     63 static void
     64 make_econtract (uint32_t seed,
     65                 const char *payload,
     66                 struct TALER_EncryptedContract *econtract)
     67 {
     68   memset (econtract,
     69           0,
     70           sizeof (*econtract));
     71   TDB_fill (&econtract->contract_pub,
     72             sizeof (econtract->contract_pub),
     73             seed);
     74   TDB_fill (&econtract->econtract_sig,
     75             sizeof (econtract->econtract_sig),
     76             seed);
     77   econtract->econtract_size = strlen (payload) + 1;
     78   econtract->econtract = GNUNET_memdup (payload,
     79                                         econtract->econtract_size);
     80 }
     81 
     82 
     83 /**
     84  * Nothing is known while the table is empty, and a contract for a purse
     85  * that does not exist is not stored.
     86  *
     87  * @param pg the database context
     88  * @return 0 on success
     89  */
     90 static int
     91 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     92 {
     93   struct TALER_PurseContractPublicKeyP purse_pub;
     94   struct TALER_PurseContractSignatureP econtract_sig;
     95   struct TALER_ContractDiffiePublicP pub_ckey;
     96   struct TALER_EncryptedContract econtract;
     97   size_t econtract_size;
     98   void *econtract_data = NULL;
     99 
    100   TDB_FILL (purse_pub,
    101             1);
    102   TDB_FILL (pub_ckey,
    103             1);
    104   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    105           TALER_EXCHANGEDB_get_contract (pg,
    106                                          &pub_ckey,
    107                                          &purse_pub,
    108                                          &econtract_sig,
    109                                          &econtract_size,
    110                                          &econtract_data));
    111   FAILIF (NULL != econtract_data);
    112   memset (&econtract,
    113           0,
    114           sizeof (econtract));
    115   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    116           TALER_EXCHANGEDB_get_contract_by_purse (pg,
    117                                                   &purse_pub,
    118                                                   &econtract));
    119   return 0;
    120 }
    121 
    122 
    123 /**
    124  * A contract is stored and found by both keys.
    125  *
    126  * @param pg the database context
    127  * @return 0 on success
    128  */
    129 static int
    130 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    131 {
    132   struct TDB_Purse purse;
    133   struct TALER_EncryptedContract econtract;
    134   struct TALER_EncryptedContract got;
    135   struct TALER_PurseContractPublicKeyP got_purse;
    136   struct TALER_PurseContractSignatureP got_sig;
    137   size_t got_size;
    138   void *got_data = NULL;
    139   bool in_conflict = true;
    140 
    141   TDB_purse (pg,
    142              10,
    143              "5",
    144              ts (1700000000),
    145              &purse);
    146   make_econtract (10,
    147                   "the-contract",
    148                   &econtract);
    149   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    150             TALER_EXCHANGEDB_insert_contract (pg,
    151                                               &purse.purse_pub,
    152                                               &econtract,
    153                                               &in_conflict),
    154             GNUNET_free (econtract.econtract));
    155   FAILIF_C (in_conflict,
    156             GNUNET_free (econtract.econtract));
    157   FAILIF_C (1 != TDB_count (pg,
    158                             "FROM contracts"),
    159             GNUNET_free (econtract.econtract));
    160 
    161   /* by the Diffie-Hellman key the client derived */
    162   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    163             TALER_EXCHANGEDB_get_contract (pg,
    164                                            &econtract.contract_pub,
    165                                            &got_purse,
    166                                            &got_sig,
    167                                            &got_size,
    168                                            &got_data),
    169             GNUNET_free (econtract.econtract));
    170   FAILIF_C (0 != GNUNET_memcmp (&got_purse,
    171                                 &purse.purse_pub),
    172             GNUNET_free (got_data); GNUNET_free (econtract.econtract));
    173   FAILIF_C (0 != GNUNET_memcmp (&got_sig,
    174                                 &econtract.econtract_sig),
    175             GNUNET_free (got_data); GNUNET_free (econtract.econtract));
    176   FAILIF_C (got_size != econtract.econtract_size,
    177             GNUNET_free (got_data); GNUNET_free (econtract.econtract));
    178   FAILIF_C (0 != memcmp (got_data,
    179                          econtract.econtract,
    180                          got_size),
    181             GNUNET_free (got_data); GNUNET_free (econtract.econtract));
    182   GNUNET_free (got_data);
    183 
    184   /* and by the purse */
    185   memset (&got,
    186           0,
    187           sizeof (got));
    188   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    189             TALER_EXCHANGEDB_get_contract_by_purse (pg,
    190                                                     &purse.purse_pub,
    191                                                     &got),
    192             GNUNET_free (econtract.econtract));
    193   FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub,
    194                                 &econtract.contract_pub),
    195             GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
    196   FAILIF_C (got.econtract_size != econtract.econtract_size,
    197             GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
    198   FAILIF_C (0 != memcmp (got.econtract,
    199                          econtract.econtract,
    200                          got.econtract_size),
    201             GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
    202   GNUNET_free (got.econtract);
    203 
    204   /* re-inserting the identical contract is a no-op */
    205   in_conflict = true;
    206   FAILIF_C (0 >
    207             TALER_EXCHANGEDB_insert_contract (pg,
    208                                               &purse.purse_pub,
    209                                               &econtract,
    210                                               &in_conflict),
    211             GNUNET_free (econtract.econtract));
    212   FAILIF_C (1 != TDB_count (pg,
    213                             "FROM contracts"),
    214             GNUNET_free (econtract.econtract));
    215   GNUNET_free (econtract.econtract);
    216   return 0;
    217 }
    218 
    219 
    220 /**
    221  * A different contract for the same purse is a conflict.
    222  *
    223  * @param pg the database context
    224  * @return 0 on success
    225  */
    226 static int
    227 check_conflict (struct TALER_EXCHANGEDB_PostgresContext *pg)
    228 {
    229   struct TDB_Purse purse;
    230   struct TALER_EncryptedContract econtract;
    231   bool in_conflict = false;
    232 
    233   TDB_purse (pg,
    234              10,
    235              "5",
    236              ts (1700000000),
    237              &purse);
    238   make_econtract (11,
    239                   "another-contract",
    240                   &econtract);
    241   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    242             TALER_EXCHANGEDB_insert_contract (pg,
    243                                               &purse.purse_pub,
    244                                               &econtract,
    245                                               &in_conflict),
    246             GNUNET_free (econtract.econtract));
    247   FAILIF_C (! in_conflict,
    248             GNUNET_free (econtract.econtract));
    249   FAILIF_C (1 != TDB_count (pg,
    250                             "FROM contracts"),
    251             GNUNET_free (econtract.econtract));
    252   GNUNET_free (econtract.econtract);
    253   return 0;
    254 }
    255 
    256 
    257 /**
    258  * A second purse can have its own contract, and the two do not mix.
    259  *
    260  * @param pg the database context
    261  * @return 0 on success
    262  */
    263 static int
    264 check_second_purse (struct TALER_EXCHANGEDB_PostgresContext *pg)
    265 {
    266   struct TDB_Purse purse;
    267   struct TALER_EncryptedContract econtract;
    268   struct TALER_EncryptedContract got;
    269   bool in_conflict = true;
    270 
    271   TDB_purse (pg,
    272              12,
    273              "3",
    274              ts (1700000000),
    275              &purse);
    276   make_econtract (12,
    277                   "second-contract",
    278                   &econtract);
    279   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    280             TALER_EXCHANGEDB_insert_contract (pg,
    281                                               &purse.purse_pub,
    282                                               &econtract,
    283                                               &in_conflict),
    284             GNUNET_free (econtract.econtract));
    285   FAILIF_C (in_conflict,
    286             GNUNET_free (econtract.econtract));
    287   FAILIF_C (2 != TDB_count (pg,
    288                             "FROM contracts"),
    289             GNUNET_free (econtract.econtract));
    290   memset (&got,
    291           0,
    292           sizeof (got));
    293   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    294             TALER_EXCHANGEDB_get_contract_by_purse (pg,
    295                                                     &purse.purse_pub,
    296                                                     &got),
    297             GNUNET_free (econtract.econtract));
    298   FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub,
    299                                 &econtract.contract_pub),
    300             GNUNET_free (got.econtract); GNUNET_free (econtract.econtract));
    301   GNUNET_free (got.econtract);
    302   GNUNET_free (econtract.econtract);
    303   return 0;
    304 }
    305 
    306 
    307 /**
    308  * The checks to run, in order.
    309  */
    310 static const struct TDB_Test tests[] = {
    311   { "contracts-empty",
    312     &check_empty },
    313   { "contracts-insert-and-lookup",
    314     &check_insert_and_lookup },
    315   { "contracts-conflict",
    316     &check_conflict },
    317   { "contracts-second-purse",
    318     &check_second_purse },
    319   { NULL, NULL }
    320 };
    321 
    322 
    323 int
    324 main (int argc,
    325       char *const *argv)
    326 {
    327   return TDB_main (argc,
    328                    argv,
    329                    "test-contracts",
    330                    "Tests for the exchangedb `contracts' table",
    331                    tests);
    332 }
    333 
    334 
    335 /* end of test_contracts.c */