exchange

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

test_known_coins.c (16150B)


      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_known_coins.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `known_coins`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_do_insert_known_coin(),
     23  * #TALER_EXCHANGEDB_get_known_coin(),
     24  * #TALER_EXCHANGEDB_get_coin_denomination(),
     25  * #TALER_EXCHANGEDB_get_signature_for_known_coin() and
     26  * #TALER_EXCHANGEDB_get_count_known_coins().
     27  *
     28  * `known_coins` references `denominations`, so a denomination is created
     29  * first with TDB_denom().  do_insert_known_coin() is the interesting one:
     30  * it is idempotent, but only for a coin that comes back with the *same*
     31  * denomination and age commitment -- the conflicting cases are what its
     32  * negative status codes are for.
     33  */
     34 #include "test_common.h"
     35 #include "exchange-database/do_insert_known_coin.h"
     36 #include "exchange-database/get_known_coin.h"
     37 #include "exchange-database/get_coin_denomination.h"
     38 #include "exchange-database/get_signature_for_known_coin.h"
     39 #include "exchange-database/get_count_known_coins.h"
     40 
     41 
     42 /**
     43  * Nothing is known about a coin that was never inserted.
     44  *
     45  * @param pg the database context
     46  * @return 0 on success
     47  */
     48 static int
     49 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     50 {
     51   struct TALER_CoinSpendPublicKeyP coin_pub;
     52   struct TALER_CoinPublicInfo info;
     53   struct TALER_DenominationHashP h_denom_pub;
     54   struct TALER_DenominationPublicKey denom_pub = { 0 };
     55   struct TALER_DenominationSignature denom_sig = { 0 };
     56   uint64_t known_coin_id;
     57 
     58   TDB_FILL (coin_pub,
     59             1);
     60   TDB_FILL (h_denom_pub,
     61             1);
     62   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     63           TALER_EXCHANGEDB_get_known_coin (pg,
     64                                            &coin_pub,
     65                                            &info));
     66   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     67           TALER_EXCHANGEDB_get_coin_denomination (pg,
     68                                                   &coin_pub,
     69                                                   &known_coin_id,
     70                                                   &h_denom_pub));
     71   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     72           TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
     73                                                          &coin_pub,
     74                                                          &denom_pub,
     75                                                          &denom_sig));
     76   FAILIF (NULL != denom_pub.bsign_pub_key);
     77   FAILIF (NULL != denom_sig.unblinded_sig);
     78   /* a denomination that does not exist has no coins */
     79   FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
     80                                                        &h_denom_pub));
     81   return 0;
     82 }
     83 
     84 
     85 /**
     86  * A coin of an unknown denomination cannot be made known.
     87  *
     88  * @param pg the database context
     89  * @return 0 on success
     90  */
     91 static int
     92 check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg)
     93 {
     94   struct TALER_CoinPublicInfo coin;
     95   struct TALER_DenominationHashP dh;
     96   struct TALER_AgeCommitmentHashP hac;
     97   uint64_t known_coin_id;
     98 
     99   memset (&coin,
    100           0,
    101           sizeof (coin));
    102   TDB_FILL (coin.coin_pub,
    103             2);
    104   TDB_FILL (coin.denom_pub_hash,
    105             2);
    106   coin.no_age_commitment = true;
    107   TDB_denom_sig (2,
    108                  &coin.denom_sig);
    109   /* the "dd" CTE finds no denomination, so nothing is inserted and the
    110      UNION's second branch finds no coin either */
    111   FAILIF_C (TALER_EXCHANGEDB_CKS_HARD_FAIL !=
    112             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    113                                                    &coin,
    114                                                    &known_coin_id,
    115                                                    &dh,
    116                                                    &hac),
    117             TALER_denom_sig_free (&coin.denom_sig));
    118   TALER_denom_sig_free (&coin.denom_sig);
    119   FAILIF (0 != TDB_count (pg,
    120                           "FROM known_coins"));
    121   return 0;
    122 }
    123 
    124 
    125 /**
    126  * Making a coin known stores it, and every lookup finds it.
    127  *
    128  * @param pg the database context
    129  * @return 0 on success
    130  */
    131 static int
    132 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    133 {
    134   struct TDB_Denom denom;
    135   struct TALER_CoinPublicInfo coin;
    136   struct TALER_CoinPublicInfo got;
    137   struct TALER_DenominationHashP h_denom_pub;
    138   struct TALER_DenominationPublicKey denom_pub = { 0 };
    139   struct TALER_DenominationSignature denom_sig = { 0 };
    140   uint64_t known_coin_id = 0;
    141   uint64_t id2 = 0;
    142 
    143   TDB_denom (pg,
    144              10,
    145              "5",
    146              "0.1",
    147              &denom);
    148   TDB_coin (pg,
    149             &denom,
    150             20,
    151             &coin,
    152             &known_coin_id);
    153   FAILIF_C (0 == known_coin_id,
    154             TDB_coin_free (&coin); TDB_denom_free (&denom));
    155 
    156   memset (&got,
    157           0,
    158           sizeof (got));
    159   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    160             TALER_EXCHANGEDB_get_known_coin (pg,
    161                                              &coin.coin_pub,
    162                                              &got),
    163             TDB_coin_free (&coin); TDB_denom_free (&denom));
    164   FAILIF_C (0 != GNUNET_memcmp (&got.denom_pub_hash,
    165                                 &denom.h_denom_pub),
    166             TALER_denom_sig_free (&got.denom_sig);
    167             TDB_coin_free (&coin); TDB_denom_free (&denom));
    168   FAILIF_C (! got.no_age_commitment,
    169             TALER_denom_sig_free (&got.denom_sig);
    170             TDB_coin_free (&coin); TDB_denom_free (&denom));
    171   FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig,
    172                                       &coin.denom_sig),
    173             TALER_denom_sig_free (&got.denom_sig);
    174             TDB_coin_free (&coin); TDB_denom_free (&denom));
    175   TALER_denom_sig_free (&got.denom_sig);
    176 
    177   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    178             TALER_EXCHANGEDB_get_coin_denomination (pg,
    179                                                     &coin.coin_pub,
    180                                                     &id2,
    181                                                     &h_denom_pub),
    182             TDB_coin_free (&coin); TDB_denom_free (&denom));
    183   FAILIF_C (id2 != known_coin_id,
    184             TDB_coin_free (&coin); TDB_denom_free (&denom));
    185   FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
    186                                 &denom.h_denom_pub),
    187             TDB_coin_free (&coin); TDB_denom_free (&denom));
    188 
    189   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    190             TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
    191                                                            &coin.coin_pub,
    192                                                            &denom_pub,
    193                                                            &denom_sig),
    194             TDB_coin_free (&coin); TDB_denom_free (&denom));
    195   FAILIF_C (0 != TALER_denom_pub_cmp (&denom_pub,
    196                                       &denom.pub),
    197             TALER_denom_pub_free (&denom_pub);
    198             TALER_denom_sig_free (&denom_sig);
    199             TDB_coin_free (&coin); TDB_denom_free (&denom));
    200   FAILIF_C (0 != TALER_denom_sig_cmp (&denom_sig,
    201                                       &coin.denom_sig),
    202             TALER_denom_pub_free (&denom_pub);
    203             TALER_denom_sig_free (&denom_sig);
    204             TDB_coin_free (&coin); TDB_denom_free (&denom));
    205   TALER_denom_pub_free (&denom_pub);
    206   TALER_denom_sig_free (&denom_sig);
    207 
    208   FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
    209                                                          &denom.h_denom_pub),
    210             TDB_coin_free (&coin); TDB_denom_free (&denom));
    211   TDB_coin_free (&coin);
    212   TDB_denom_free (&denom);
    213   return 0;
    214 }
    215 
    216 
    217 /**
    218  * Re-inserting the same coin is a no-op; re-inserting it under a different
    219  * denomination or with an age commitment is a conflict.
    220  *
    221  * @param pg the database context
    222  * @return 0 on success
    223  */
    224 static int
    225 check_conflicts (struct TALER_EXCHANGEDB_PostgresContext *pg)
    226 {
    227   struct TDB_Denom denom;
    228   struct TDB_Denom other;
    229   struct TALER_CoinPublicInfo coin;
    230   struct TALER_DenominationHashP dh;
    231   struct TALER_AgeCommitmentHashP hac;
    232   uint64_t known_coin_id = 0;
    233   uint64_t id2 = 0;
    234 
    235   TDB_denom (pg,
    236              10,
    237              "5",
    238              "0.1",
    239              &denom);
    240   TDB_denom (pg,
    241              11,
    242              "5",
    243              "0.1",
    244              &other);
    245   memset (&coin,
    246           0,
    247           sizeof (coin));
    248   TDB_FILL (coin.coin_pub,
    249             20);
    250   coin.denom_pub_hash = denom.h_denom_pub;
    251   coin.no_age_commitment = true;
    252   TDB_denom_sig (20,
    253                  &coin.denom_sig);
    254 
    255   /* the coin from the previous check: already present, no conflict */
    256   FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT !=
    257             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    258                                                    &coin,
    259                                                    &known_coin_id,
    260                                                    &dh,
    261                                                    &hac),
    262             TALER_denom_sig_free (&coin.denom_sig);
    263             TDB_denom_free (&denom); TDB_denom_free (&other));
    264   FAILIF_C (0 == known_coin_id,
    265             TALER_denom_sig_free (&coin.denom_sig);
    266             TDB_denom_free (&denom); TDB_denom_free (&other));
    267 
    268   /* same coin key, different denomination: conflict, and the stored
    269      denomination is handed back so the caller can report it */
    270   coin.denom_pub_hash = other.h_denom_pub;
    271   FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT !=
    272             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    273                                                    &coin,
    274                                                    &id2,
    275                                                    &dh,
    276                                                    &hac),
    277             TALER_denom_sig_free (&coin.denom_sig);
    278             TDB_denom_free (&denom); TDB_denom_free (&other));
    279   FAILIF_C (0 != GNUNET_memcmp (&dh,
    280                                 &denom.h_denom_pub),
    281             TALER_denom_sig_free (&coin.denom_sig);
    282             TDB_denom_free (&denom); TDB_denom_free (&other));
    283 
    284   /* same coin key and denomination, but now with an age commitment where
    285      the stored row has none: the caller should have passed NULL */
    286   coin.denom_pub_hash = denom.h_denom_pub;
    287   coin.no_age_commitment = false;
    288   TDB_FILL (coin.h_age_commitment,
    289             21);
    290   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL !=
    291             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    292                                                    &coin,
    293                                                    &id2,
    294                                                    &dh,
    295                                                    &hac),
    296             TALER_denom_sig_free (&coin.denom_sig);
    297             TDB_denom_free (&denom); TDB_denom_free (&other));
    298   TALER_denom_sig_free (&coin.denom_sig);
    299 
    300   /* the conflicting attempts did not add anything */
    301   FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
    302                                                          &denom.h_denom_pub),
    303             TDB_denom_free (&denom); TDB_denom_free (&other));
    304   FAILIF_C (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
    305                                                          &other.h_denom_pub),
    306             TDB_denom_free (&denom); TDB_denom_free (&other));
    307   TDB_denom_free (&denom);
    308   TDB_denom_free (&other);
    309   return 0;
    310 }
    311 
    312 
    313 /**
    314  * A coin with an age commitment round-trips, and the mirror-image
    315  * conflict is reported.
    316  *
    317  * @param pg the database context
    318  * @return 0 on success
    319  */
    320 static int
    321 check_age_commitment (struct TALER_EXCHANGEDB_PostgresContext *pg)
    322 {
    323   struct TDB_Denom denom;
    324   struct TALER_CoinPublicInfo coin;
    325   struct TALER_CoinPublicInfo got;
    326   struct TALER_DenominationHashP dh;
    327   struct TALER_AgeCommitmentHashP hac;
    328   uint64_t known_coin_id = 0;
    329 
    330   TDB_denom (pg,
    331              10,
    332              "5",
    333              "0.1",
    334              &denom);
    335   memset (&coin,
    336           0,
    337           sizeof (coin));
    338   TDB_FILL (coin.coin_pub,
    339             30);
    340   coin.denom_pub_hash = denom.h_denom_pub;
    341   coin.no_age_commitment = false;
    342   TDB_FILL (coin.h_age_commitment,
    343             31);
    344   TDB_denom_sig (30,
    345                  &coin.denom_sig);
    346   FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED !=
    347             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    348                                                    &coin,
    349                                                    &known_coin_id,
    350                                                    &dh,
    351                                                    &hac),
    352             TALER_denom_sig_free (&coin.denom_sig);
    353             TDB_denom_free (&denom));
    354   memset (&got,
    355           0,
    356           sizeof (got));
    357   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    358             TALER_EXCHANGEDB_get_known_coin (pg,
    359                                              &coin.coin_pub,
    360                                              &got),
    361             TALER_denom_sig_free (&coin.denom_sig);
    362             TDB_denom_free (&denom));
    363   FAILIF_C (got.no_age_commitment,
    364             TALER_denom_sig_free (&got.denom_sig);
    365             TALER_denom_sig_free (&coin.denom_sig);
    366             TDB_denom_free (&denom));
    367   FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment,
    368                                 &coin.h_age_commitment),
    369             TALER_denom_sig_free (&got.denom_sig);
    370             TALER_denom_sig_free (&coin.denom_sig);
    371             TDB_denom_free (&denom));
    372   TALER_denom_sig_free (&got.denom_sig);
    373 
    374   /* a different age commitment for the same coin is a conflict... */
    375   TDB_FILL (coin.h_age_commitment,
    376             32);
    377   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS !=
    378             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    379                                                    &coin,
    380                                                    &known_coin_id,
    381                                                    &dh,
    382                                                    &hac),
    383             TALER_denom_sig_free (&coin.denom_sig);
    384             TDB_denom_free (&denom));
    385   /* ...and so is no age commitment at all, where one is on file */
    386   coin.no_age_commitment = true;
    387   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL !=
    388             TALER_EXCHANGEDB_do_insert_known_coin (pg,
    389                                                    &coin,
    390                                                    &known_coin_id,
    391                                                    &dh,
    392                                                    &hac),
    393             TALER_denom_sig_free (&coin.denom_sig);
    394             TDB_denom_free (&denom));
    395   TALER_denom_sig_free (&coin.denom_sig);
    396 
    397   /* two coins of this denomination by now */
    398   FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg,
    399                                                          &denom.h_denom_pub),
    400             TDB_denom_free (&denom));
    401   TDB_denom_free (&denom);
    402   return 0;
    403 }
    404 
    405 
    406 /**
    407  * The checks to run, in order.
    408  */
    409 static const struct TDB_Test tests[] = {
    410   { "known-coins-empty",
    411     &check_empty },
    412   { "known-coins-unknown-denomination",
    413     &check_unknown_denomination },
    414   { "known-coins-insert-and-lookup",
    415     &check_insert_and_lookup },
    416   { "known-coins-conflicts",
    417     &check_conflicts },
    418   { "known-coins-age-commitment",
    419     &check_age_commitment },
    420   { NULL, NULL }
    421 };
    422 
    423 
    424 int
    425 main (int argc,
    426       char *const *argv)
    427 {
    428   return TDB_main (argc,
    429                    argv,
    430                    "test-known-coins",
    431                    "Tests for the exchangedb `known_coins' table",
    432                    tests);
    433 }
    434 
    435 
    436 /* end of test_known_coins.c */