exchange

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

test_denominations.c (12756B)


      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_denominations.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `denominations`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_insert_denomination_info(),
     23  * #TALER_EXCHANGEDB_get_denomination_info(),
     24  * #TALER_EXCHANGEDB_get_denomination_by_serial(),
     25  * #TALER_EXCHANGEDB_get_denomination_meta(),
     26  * #TALER_EXCHANGEDB_iterate_denomination_info() and
     27  * #TALER_EXCHANGEDB_iterate_denominations().
     28  *
     29  * `denominations` has no foreign keys, so the checks need no fixtures.  The
     30  * empty-table checks run first, before any denomination exists.
     31  */
     32 #include "test_common.h"
     33 #include "exchange-database/insert_denomination_info.h"
     34 #include "exchange-database/get_denomination_info.h"
     35 #include "exchange-database/get_denomination_by_serial.h"
     36 #include "exchange-database/get_denomination_meta.h"
     37 #include "exchange-database/iterate_denomination_info.h"
     38 #include "exchange-database/iterate_denominations.h"
     39 
     40 
     41 /**
     42  * Check that nothing is found while the table is still empty.
     43  *
     44  * @param pg the database context
     45  * @return 0 on success
     46  */
     47 static int
     48 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     49 {
     50   struct TALER_DenominationHashP h_denom_pub;
     51   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
     52   struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
     53   uint64_t serial;
     54 
     55   TDB_FILL (h_denom_pub,
     56             1);
     57   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     58           TALER_EXCHANGEDB_get_denomination_info (pg,
     59                                                   &h_denom_pub,
     60                                                   &serial,
     61                                                   &issue));
     62   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     63           TALER_EXCHANGEDB_get_denomination_by_serial (pg,
     64                                                        42,
     65                                                        &issue));
     66   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     67           TALER_EXCHANGEDB_get_denomination_meta (pg,
     68                                                   &h_denom_pub,
     69                                                   &meta));
     70   return 0;
     71 }
     72 
     73 
     74 /**
     75  * Closure for #count_info_cb() and #count_denoms_cb().
     76  */
     77 struct CountContext
     78 {
     79   /**
     80    * Denomination we are looking for.
     81    */
     82   const struct TDB_Denom *denom;
     83 
     84   /**
     85    * How many rows did the callback see in total?
     86    */
     87   unsigned int total;
     88 
     89   /**
     90    * How many times did we see @e denom?
     91    */
     92   unsigned int matched;
     93 
     94   /**
     95    * Set to true if @e denom was reported with data that does not match
     96    * what we inserted.
     97    */
     98   bool mismatch;
     99 
    100   /**
    101    * Recoup flag reported for @e denom.
    102    */
    103   bool recoup_possible;
    104 
    105   /**
    106    * Row reported for @e denom.
    107    */
    108   uint64_t serial;
    109 };
    110 
    111 
    112 /**
    113  * Callback for #TALER_EXCHANGEDB_iterate_denomination_info().
    114  *
    115  * @param cls a `struct CountContext *`
    116  * @param denom_serial table row of the denomination
    117  * @param denom_pub public key of the denomination
    118  * @param issue information about the denomination
    119  */
    120 static void
    121 count_info_cb (void *cls,
    122                uint64_t denom_serial,
    123                const struct TALER_DenominationPublicKey *denom_pub,
    124                const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
    125 {
    126   struct CountContext *ctx = cls;
    127 
    128   ctx->total++;
    129   if (0 != GNUNET_memcmp (&issue->denom_hash,
    130                           &ctx->denom->h_denom_pub))
    131     return;
    132   ctx->matched++;
    133   ctx->serial = denom_serial;
    134   if ( (0 != TALER_denom_pub_cmp (denom_pub,
    135                                   &ctx->denom->pub)) ||
    136        (0 != TALER_amount_cmp (&issue->value,
    137                                &ctx->denom->issue.value)) ||
    138        (0 != TALER_amount_cmp (&issue->fees.deposit,
    139                                &ctx->denom->issue.fees.deposit)) )
    140     ctx->mismatch = true;
    141 }
    142 
    143 
    144 /**
    145  * Callback for #TALER_EXCHANGEDB_iterate_denominations().
    146  *
    147  * @param cls a `struct CountContext *`
    148  * @param denom_pub public key of the denomination
    149  * @param h_denom_pub hash of @a denom_pub
    150  * @param meta meta data of the denomination
    151  * @param master_sig master signature over the denomination
    152  * @param recoup_possible true if the denomination was revoked
    153  */
    154 static void
    155 count_denoms_cb (void *cls,
    156                  const struct TALER_DenominationPublicKey *denom_pub,
    157                  const struct TALER_DenominationHashP *h_denom_pub,
    158                  const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
    159                  const struct TALER_MasterSignatureP *master_sig,
    160                  bool recoup_possible)
    161 {
    162   struct CountContext *ctx = cls;
    163 
    164   ctx->total++;
    165   if (0 != GNUNET_memcmp (h_denom_pub,
    166                           &ctx->denom->h_denom_pub))
    167     return;
    168   ctx->matched++;
    169   ctx->serial = meta->serial;
    170   ctx->recoup_possible = recoup_possible;
    171   if ( (0 != TALER_denom_pub_cmp (denom_pub,
    172                                   &ctx->denom->pub)) ||
    173        (0 != GNUNET_memcmp (master_sig,
    174                             &ctx->denom->issue.signature)) ||
    175        (0 != TALER_amount_cmp (&meta->value,
    176                                &ctx->denom->issue.value)) ||
    177        (GNUNET_TIME_timestamp_cmp (meta->start,
    178                                    !=,
    179                                    ctx->denom->issue.start)) )
    180     ctx->mismatch = true;
    181 }
    182 
    183 
    184 /**
    185  * Insert a denomination and read it back through every lookup the
    186  * table offers.
    187  *
    188  * @param pg the database context
    189  * @return 0 on success
    190  */
    191 static int
    192 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    193 {
    194   struct TDB_Denom denom;
    195   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
    196   struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
    197   struct TALER_DenominationHashP other;
    198   uint64_t serial;
    199 
    200   /* TDB_denom() inserts and looks up the row it created. */
    201   TDB_denom (pg,
    202              10,
    203              "5",
    204              "0.1",
    205              &denom);
    206   memset (&issue,
    207           0,
    208           sizeof (issue));
    209   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    210             TALER_EXCHANGEDB_get_denomination_info (pg,
    211                                                     &denom.h_denom_pub,
    212                                                     &serial,
    213                                                     &issue),
    214             TDB_denom_free (&denom));
    215   FAILIF_C (0 != GNUNET_memcmp (&issue,
    216                                 &denom.issue),
    217             TDB_denom_free (&denom));
    218   FAILIF_C (serial != denom.serial,
    219             TDB_denom_free (&denom));
    220 
    221   /* the denom_serial output is optional */
    222   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    223             TALER_EXCHANGEDB_get_denomination_info (pg,
    224                                                     &denom.h_denom_pub,
    225                                                     NULL,
    226                                                     &issue),
    227             TDB_denom_free (&denom));
    228 
    229   memset (&issue,
    230           0,
    231           sizeof (issue));
    232   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    233             TALER_EXCHANGEDB_get_denomination_by_serial (pg,
    234                                                          serial,
    235                                                          &issue),
    236             TDB_denom_free (&denom));
    237   FAILIF_C (0 != GNUNET_memcmp (&issue,
    238                                 &denom.issue),
    239             TDB_denom_free (&denom));
    240 
    241   memset (&meta,
    242           0,
    243           sizeof (meta));
    244   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    245             TALER_EXCHANGEDB_get_denomination_meta (pg,
    246                                                     &denom.h_denom_pub,
    247                                                     &meta),
    248             TDB_denom_free (&denom));
    249   FAILIF_C (meta.serial != serial,
    250             TDB_denom_free (&denom));
    251   FAILIF_C (0 != TALER_amount_cmp (&meta.value,
    252                                    &denom.issue.value),
    253             TDB_denom_free (&denom));
    254   FAILIF_C (0 != TALER_amount_cmp (&meta.fees.refund,
    255                                    &denom.issue.fees.refund),
    256             TDB_denom_free (&denom));
    257   FAILIF_C (GNUNET_TIME_timestamp_cmp (meta.expire_legal,
    258                                        !=,
    259                                        denom.issue.expire_legal),
    260             TDB_denom_free (&denom));
    261   FAILIF_C (meta.age_mask.bits != denom.issue.age_mask.bits,
    262             TDB_denom_free (&denom));
    263 
    264   /* ...and a denomination that was never inserted is still not found. */
    265   TDB_FILL (other,
    266             99);
    267   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    268             TALER_EXCHANGEDB_get_denomination_info (pg,
    269                                                     &other,
    270                                                     &serial,
    271                                                     &issue),
    272             TDB_denom_free (&denom));
    273   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    274             TALER_EXCHANGEDB_get_denomination_meta (pg,
    275                                                     &other,
    276                                                     &meta),
    277             TDB_denom_free (&denom));
    278   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    279             TALER_EXCHANGEDB_get_denomination_by_serial (pg,
    280                                                          denom.serial + 1000,
    281                                                          &issue),
    282             TDB_denom_free (&denom));
    283   TDB_denom_free (&denom);
    284   return 0;
    285 }
    286 
    287 
    288 /**
    289  * Both iterators must report every denomination in the table.
    290  *
    291  * @param pg the database context
    292  * @return 0 on success
    293  */
    294 static int
    295 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    296 {
    297   struct TDB_Denom d1;
    298   struct TDB_Denom d2;
    299   struct CountContext ctx;
    300   unsigned int seen;
    301 
    302   TDB_denom (pg,
    303              20,
    304              "1",
    305              "0.01",
    306              &d1);
    307   TDB_denom (pg,
    308              21,
    309              "2",
    310              "0.02",
    311              &d2);
    312   memset (&ctx,
    313           0,
    314           sizeof (ctx));
    315   ctx.denom = &d2;
    316   FAILIF_C (0 >=
    317             TALER_EXCHANGEDB_iterate_denomination_info (pg,
    318                                                         &count_info_cb,
    319                                                         &ctx),
    320             TDB_denom_free (&d1); TDB_denom_free (&d2));
    321   FAILIF_C (1 != ctx.matched,
    322             TDB_denom_free (&d1); TDB_denom_free (&d2));
    323   FAILIF_C (ctx.mismatch,
    324             TDB_denom_free (&d1); TDB_denom_free (&d2));
    325   FAILIF_C (ctx.serial != d2.serial,
    326             TDB_denom_free (&d1); TDB_denom_free (&d2));
    327   seen = ctx.total;
    328   FAILIF_C (seen < 2,
    329             TDB_denom_free (&d1); TDB_denom_free (&d2));
    330 
    331   memset (&ctx,
    332           0,
    333           sizeof (ctx));
    334   ctx.denom = &d1;
    335   FAILIF_C (0 >=
    336             TALER_EXCHANGEDB_iterate_denominations (pg,
    337                                                     &count_denoms_cb,
    338                                                     &ctx),
    339             TDB_denom_free (&d1); TDB_denom_free (&d2));
    340   FAILIF_C (1 != ctx.matched,
    341             TDB_denom_free (&d1); TDB_denom_free (&d2));
    342   FAILIF_C (ctx.mismatch,
    343             TDB_denom_free (&d1); TDB_denom_free (&d2));
    344   FAILIF_C (ctx.serial != d1.serial,
    345             TDB_denom_free (&d1); TDB_denom_free (&d2));
    346   /* nothing was revoked in this binary */
    347   FAILIF_C (ctx.recoup_possible,
    348             TDB_denom_free (&d1); TDB_denom_free (&d2));
    349   /* both iterators walk the same table, so they must agree on its size */
    350   FAILIF_C (ctx.total != seen,
    351             TDB_denom_free (&d1); TDB_denom_free (&d2));
    352   TDB_denom_free (&d1);
    353   TDB_denom_free (&d2);
    354   return 0;
    355 }
    356 
    357 
    358 /**
    359  * The checks to run, in order.
    360  */
    361 static const struct TDB_Test tests[] = {
    362   { "denominations-empty",
    363     &check_empty },
    364   { "denominations-insert-and-lookup",
    365     &check_insert_and_lookup },
    366   { "denominations-iterate",
    367     &check_iterate },
    368   { NULL, NULL }
    369 };
    370 
    371 
    372 int
    373 main (int argc,
    374       char *const *argv)
    375 {
    376   return TDB_main (argc,
    377                    argv,
    378                    "test-denominations",
    379                    "Tests for the exchangedb `denominations' table",
    380                    tests);
    381 }
    382 
    383 
    384 /* end of test_denominations.c */