exchange

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

test_coin_deposits.c (12025B)


      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_coin_deposits.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `coin_deposits`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id() and
     23  * #TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check().
     24  *
     25  * The rows themselves are written by #TALER_EXCHANGEDB_do_deposit(), whose
     26  * primary table is `batch_deposits` -- TDB_deposit() calls it.  What is
     27  * checked here is the two views onto the coins of a deposit: the auditor's
     28  * serial walk and the KYC sum per merchant account.
     29  */
     30 #include "test_common.h"
     31 #include "exchange-database/iterate_coin_deposits_above_serial_id.h"
     32 #include "exchange-database/iterate_deposit_amounts_for_kyc_check.h"
     33 
     34 
     35 /**
     36  * Account the merchants of the checks are paid at.
     37  */
     38 static struct TDB_Account account;
     39 
     40 
     41 /**
     42  * A second account, to show the KYC view is per account.
     43  */
     44 static struct TDB_Account other;
     45 
     46 
     47 /**
     48  * Denomination the checks deposit.
     49  */
     50 static struct TDB_Denom denom;
     51 
     52 
     53 /**
     54  * Build a timestamp from a number of seconds since the epoch.
     55  *
     56  * @param secs seconds since the epoch
     57  * @return the timestamp
     58  */
     59 static struct GNUNET_TIME_Timestamp
     60 ts (uint64_t secs)
     61 {
     62   struct GNUNET_TIME_Absolute abs = {
     63     .abs_value_us = secs * 1000LLU * 1000LLU
     64   };
     65 
     66   return GNUNET_TIME_absolute_to_timestamp (abs);
     67 }
     68 
     69 
     70 /**
     71  * Closure for #deposit_cb().
     72  */
     73 struct DepositContext
     74 {
     75   /**
     76    * How many rows did the callback see?
     77    */
     78   unsigned int total;
     79 
     80   /**
     81    * Stop after this many rows; 0 for no limit.
     82    */
     83   unsigned int stop_after;
     84 
     85   /**
     86    * Coin we are looking for, NULL to match nothing.
     87    */
     88   const struct TALER_CoinSpendPublicKeyP *coin_pub;
     89 
     90   /**
     91    * How many times did we see it?
     92    */
     93   unsigned int matched;
     94 
     95   /**
     96    * Amount reported for it.
     97    */
     98   struct TALER_Amount amount;
     99 
    100   /**
    101    * Merchant reported for it.
    102    */
    103   struct TALER_MerchantPublicKeyP merchant_pub;
    104 
    105   /**
    106    * Was it reported as executed?
    107    */
    108   bool done;
    109 };
    110 
    111 
    112 /**
    113  * Callback for #TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id().
    114  *
    115  * @param cls a `struct DepositContext *`
    116  * @param rowid row of the coin deposit
    117  * @param exchange_timestamp when the deposit was made
    118  * @param deposit details of the deposit
    119  * @param denom_pub denomination of the coin
    120  * @param done whether the deposit was already wired out
    121  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
    122  */
    123 static enum GNUNET_GenericReturnValue
    124 deposit_cb (void *cls,
    125             uint64_t rowid,
    126             struct GNUNET_TIME_Timestamp exchange_timestamp,
    127             const struct TALER_EXCHANGEDB_Deposit *deposit,
    128             const struct TALER_DenominationPublicKey *denom_pub,
    129             bool done)
    130 {
    131   struct DepositContext *ctx = cls;
    132 
    133   (void) rowid;
    134   (void) exchange_timestamp;
    135   (void) denom_pub;
    136   ctx->total++;
    137   if ( (NULL != ctx->coin_pub) &&
    138        (0 == GNUNET_memcmp (&deposit->coin.coin_pub,
    139                             ctx->coin_pub)) )
    140   {
    141     ctx->matched++;
    142     ctx->amount = deposit->amount_with_fee;
    143     ctx->merchant_pub = deposit->merchant_pub;
    144     ctx->done = done;
    145   }
    146   if ( (0 != ctx->stop_after) &&
    147        (ctx->total >= ctx->stop_after) )
    148     return GNUNET_SYSERR;
    149   return GNUNET_OK;
    150 }
    151 
    152 
    153 /**
    154  * Closure for #amount_cb().
    155  */
    156 struct AmountContext
    157 {
    158   /**
    159    * How many amounts did the callback see?
    160    */
    161   unsigned int total;
    162 
    163   /**
    164    * Sum of the whole-unit parts of the amounts seen.
    165    */
    166   uint64_t value_sum;
    167 
    168   /**
    169    * Return this from the callback.
    170    */
    171   enum GNUNET_GenericReturnValue ret;
    172 };
    173 
    174 
    175 /**
    176  * Callback for #TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check().
    177  *
    178  * @param cls a `struct AmountContext *`
    179  * @param amount the deposited amount
    180  * @param date when it was deposited
    181  * @return what @e ret of the closure says
    182  */
    183 static enum GNUNET_GenericReturnValue
    184 amount_cb (void *cls,
    185            const struct TALER_Amount *amount,
    186            struct GNUNET_TIME_Absolute date)
    187 {
    188   struct AmountContext *ctx = cls;
    189 
    190   (void) date;
    191   ctx->total++;
    192   ctx->value_sum += amount->value;
    193   return ctx->ret;
    194 }
    195 
    196 
    197 /**
    198  * Nothing is reported while the table is empty.
    199  *
    200  * @param pg the database context
    201  * @return 0 on success
    202  */
    203 static int
    204 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    205 {
    206   struct DepositContext ctx = { 0 };
    207   struct AmountContext actx = { 0 };
    208   struct TALER_NormalizedPaytoHashP h_payto;
    209 
    210   TDB_FILL (h_payto,
    211             1);
    212   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    213           TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
    214             pg,
    215             0,
    216             &deposit_cb,
    217             &ctx));
    218   actx.ret = GNUNET_OK;
    219   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    220           TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
    221             pg,
    222             &h_payto,
    223             GNUNET_TIME_UNIT_ZERO_ABS,
    224             &amount_cb,
    225             &actx));
    226   FAILIF (0 != ctx.total);
    227   FAILIF (0 != actx.total);
    228   return 0;
    229 }
    230 
    231 
    232 /**
    233  * A deposited coin is reported with the merchant and amount it went to.
    234  *
    235  * @param pg the database context
    236  * @return 0 on success
    237  */
    238 static int
    239 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    240 {
    241   struct TALER_CoinPublicInfo coin;
    242   struct TDB_Deposit dep;
    243   struct DepositContext ctx;
    244   struct TALER_Amount expect = TDB_amount ("1");
    245 
    246   TDB_denom (pg,
    247              10,
    248              "5",
    249              "0.1",
    250              &denom);
    251   TDB_account (pg,
    252                10,
    253                &account);
    254   TDB_coin (pg,
    255             &denom,
    256             20,
    257             &coin,
    258             NULL);
    259   TDB_deposit (pg,
    260                &account,
    261                &coin,
    262                20,
    263                "1",
    264                "0.1",
    265                ts (1600000000),
    266                ts (1600000000),
    267                &dep);
    268   memset (&ctx,
    269           0,
    270           sizeof (ctx));
    271   ctx.coin_pub = &coin.coin_pub;
    272   FAILIF_C (1 !=
    273             TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
    274               pg,
    275               0,
    276               &deposit_cb,
    277               &ctx),
    278             TDB_coin_free (&coin));
    279   FAILIF_C (1 != ctx.matched,
    280             TDB_coin_free (&coin));
    281   FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
    282                                    &expect),
    283             TDB_coin_free (&coin));
    284   FAILIF_C (0 != GNUNET_memcmp (&ctx.merchant_pub,
    285                                 &dep.merchant_pub),
    286             TDB_coin_free (&coin));
    287   FAILIF_C (ctx.done,
    288             TDB_coin_free (&coin));
    289 
    290   /* the serial bound is exclusive */
    291   memset (&ctx,
    292           0,
    293           sizeof (ctx));
    294   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    295             TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
    296               pg,
    297               1000,
    298               &deposit_cb,
    299               &ctx),
    300             TDB_coin_free (&coin));
    301   FAILIF_C (0 != ctx.total,
    302             TDB_coin_free (&coin));
    303   TDB_coin_free (&coin);
    304   return 0;
    305 }
    306 
    307 
    308 /**
    309  * The KYC view sums the deposits per merchant account, and honours the
    310  * time limit and the callback's abort.
    311  *
    312  * @param pg the database context
    313  * @return 0 on success
    314  */
    315 static int
    316 check_kyc_amounts (struct TALER_EXCHANGEDB_PostgresContext *pg)
    317 {
    318   struct TALER_CoinPublicInfo c2;
    319   struct TALER_CoinPublicInfo c3;
    320   struct TDB_Deposit dep;
    321   struct AmountContext ctx;
    322 
    323   TDB_account (pg,
    324                11,
    325                &other);
    326   /* a second deposit to the same account, and one to another */
    327   TDB_coin (pg,
    328             &denom,
    329             21,
    330             &c2,
    331             NULL);
    332   TDB_deposit (pg,
    333                &account,
    334                &c2,
    335                21,
    336                "2",
    337                "0.1",
    338                ts (1600003600),
    339                ts (1600003600),
    340                &dep);
    341   TDB_coin (pg,
    342             &denom,
    343             22,
    344             &c3,
    345             NULL);
    346   TDB_deposit (pg,
    347                &other,
    348                &c3,
    349                22,
    350                "4",
    351                "0.1",
    352                ts (1600007200),
    353                ts (1600007200),
    354                &dep);
    355   TDB_coin_free (&c2);
    356   TDB_coin_free (&c3);
    357   FAILIF (3 != TDB_count (pg,
    358                           "FROM coin_deposits"));
    359 
    360   memset (&ctx,
    361           0,
    362           sizeof (ctx));
    363   ctx.ret = GNUNET_OK;
    364   FAILIF (2 !=
    365           TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
    366             pg,
    367             &account.h_normalized,
    368             GNUNET_TIME_UNIT_ZERO_ABS,
    369             &amount_cb,
    370             &ctx));
    371   FAILIF (1 + 2 != ctx.value_sum);
    372 
    373   memset (&ctx,
    374           0,
    375           sizeof (ctx));
    376   ctx.ret = GNUNET_OK;
    377   FAILIF (1 !=
    378           TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
    379             pg,
    380             &other.h_normalized,
    381             GNUNET_TIME_UNIT_ZERO_ABS,
    382             &amount_cb,
    383             &ctx));
    384   FAILIF (4 != ctx.value_sum);
    385 
    386   /* a limit in the future hides everything */
    387   memset (&ctx,
    388           0,
    389           sizeof (ctx));
    390   ctx.ret = GNUNET_OK;
    391   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    392           TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
    393             pg,
    394             &account.h_normalized,
    395             GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS),
    396             &amount_cb,
    397             &ctx));
    398   FAILIF (0 != ctx.total);
    399 
    400   /* a callback that aborts with GNUNET_NO is not an error */
    401   memset (&ctx,
    402           0,
    403           sizeof (ctx));
    404   ctx.ret = GNUNET_NO;
    405   FAILIF (0 >
    406           TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
    407             pg,
    408             &account.h_normalized,
    409             GNUNET_TIME_UNIT_ZERO_ABS,
    410             &amount_cb,
    411             &ctx));
    412   FAILIF (1 != ctx.total);
    413   return 0;
    414 }
    415 
    416 
    417 /**
    418  * A deposit marked done is reported as such.
    419  *
    420  * @param pg the database context
    421  * @return 0 on success
    422  */
    423 static int
    424 check_done_flag (struct TALER_EXCHANGEDB_PostgresContext *pg)
    425 {
    426   struct DepositContext ctx;
    427 
    428   FAILIF (GNUNET_OK !=
    429           TDB_exec (pg,
    430                     "UPDATE batch_deposits SET done=TRUE;"));
    431   memset (&ctx,
    432           0,
    433           sizeof (ctx));
    434   ctx.stop_after = 1;
    435   FAILIF (3 !=
    436           TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
    437             pg,
    438             0,
    439             &deposit_cb,
    440             &ctx));
    441   FAILIF (1 != ctx.total);
    442 
    443   {
    444     struct TALER_CoinSpendPublicKeyP coin_pub;
    445 
    446     TDB_FILL (coin_pub,
    447               20);
    448     memset (&ctx,
    449             0,
    450             sizeof (ctx));
    451     ctx.coin_pub = &coin_pub;
    452     FAILIF (3 !=
    453             TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
    454               pg,
    455               0,
    456               &deposit_cb,
    457               &ctx));
    458     FAILIF (1 != ctx.matched);
    459     FAILIF (! ctx.done);
    460   }
    461   return 0;
    462 }
    463 
    464 
    465 /**
    466  * The checks to run, in order.
    467  */
    468 static const struct TDB_Test tests[] = {
    469   { "coin-deposits-empty",
    470     &check_empty },
    471   { "coin-deposits-iterate",
    472     &check_iterate },
    473   { "coin-deposits-kyc-amounts",
    474     &check_kyc_amounts },
    475   { "coin-deposits-done-flag",
    476     &check_done_flag },
    477   { NULL, NULL }
    478 };
    479 
    480 
    481 int
    482 main (int argc,
    483       char *const *argv)
    484 {
    485   int ret;
    486 
    487   ret = TDB_main (argc,
    488                   argv,
    489                   "test-coin-deposits",
    490                   "Tests for the exchangedb `coin_deposits' table",
    491                   tests);
    492   TDB_account_free (&account);
    493   TDB_account_free (&other);
    494   TDB_denom_free (&denom);
    495   return ret;
    496 }
    497 
    498 
    499 /* end of test_coin_deposits.c */