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


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