LCOV - code coverage report
Current view: top level - exchangedb - test_coin_deposits.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 95 95
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 8 8

            Line data    Source code
       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            6 : ts (uint64_t secs)
      61              : {
      62            6 :   struct GNUNET_TIME_Absolute abs = {
      63            6 :     .abs_value_us = secs * 1000LLU * 1000LLU
      64              :   };
      65              : 
      66            6 :   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            5 : 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            5 :   struct DepositContext *ctx = cls;
     132              : 
     133              :   (void) rowid;
     134              :   (void) exchange_timestamp;
     135              :   (void) denom_pub;
     136            5 :   ctx->total++;
     137            5 :   if ( (NULL != ctx->coin_pub) &&
     138            4 :        (0 == GNUNET_memcmp (&deposit->coin.coin_pub,
     139              :                             ctx->coin_pub)) )
     140              :   {
     141            2 :     ctx->matched++;
     142            2 :     ctx->amount = deposit->amount_with_fee;
     143            2 :     ctx->merchant_pub = deposit->merchant_pub;
     144            2 :     ctx->done = done;
     145              :   }
     146            5 :   if ( (0 != ctx->stop_after) &&
     147            1 :        (ctx->total >= ctx->stop_after) )
     148            1 :     return GNUNET_SYSERR;
     149            4 :   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            4 : amount_cb (void *cls,
     185              :            const struct TALER_Amount *amount,
     186              :            struct GNUNET_TIME_Absolute date)
     187              : {
     188            4 :   struct AmountContext *ctx = cls;
     189              : 
     190              :   (void) date;
     191            4 :   ctx->total++;
     192            4 :   ctx->value_sum += amount->value;
     193            4 :   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            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     205              : {
     206            1 :   struct DepositContext ctx = { 0 };
     207            1 :   struct AmountContext actx = { 0 };
     208              :   struct TALER_NormalizedPaytoHashP h_payto;
     209              : 
     210            1 :   TDB_FILL (h_payto,
     211              :             1);
     212            1 :   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            1 :   actx.ret = GNUNET_OK;
     219            1 :   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            1 :   FAILIF (0 != ctx.total);
     227            1 :   FAILIF (0 != actx.total);
     228            1 :   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            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     240              : {
     241              :   struct TALER_CoinPublicInfo coin;
     242              :   struct TDB_Deposit dep;
     243              :   struct DepositContext ctx;
     244            1 :   struct TALER_Amount expect = TDB_amount ("1");
     245              : 
     246            1 :   TDB_denom (pg,
     247              :              10,
     248              :              "5",
     249              :              "0.1",
     250              :              &denom);
     251            1 :   TDB_account (pg,
     252              :                10,
     253              :                &account);
     254            1 :   TDB_coin (pg,
     255              :             &denom,
     256              :             20,
     257              :             &coin,
     258              :             NULL);
     259            1 :   TDB_deposit (pg,
     260              :                &account,
     261              :                &coin,
     262              :                20,
     263              :                "1",
     264              :                "0.1",
     265              :                ts (1600000000),
     266              :                ts (1600000000),
     267              :                &dep);
     268            1 :   memset (&ctx,
     269              :           0,
     270              :           sizeof (ctx));
     271            1 :   ctx.coin_pub = &coin.coin_pub;
     272            1 :   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            1 :   FAILIF_C (1 != ctx.matched,
     280              :             TDB_coin_free (&coin));
     281            1 :   FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
     282              :                                    &expect),
     283              :             TDB_coin_free (&coin));
     284            1 :   FAILIF_C (0 != GNUNET_memcmp (&ctx.merchant_pub,
     285              :                                 &dep.merchant_pub),
     286              :             TDB_coin_free (&coin));
     287            1 :   FAILIF_C (ctx.done,
     288              :             TDB_coin_free (&coin));
     289              : 
     290              :   /* the serial bound is exclusive */
     291            1 :   memset (&ctx,
     292              :           0,
     293              :           sizeof (ctx));
     294            1 :   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            1 :   FAILIF_C (0 != ctx.total,
     302              :             TDB_coin_free (&coin));
     303            1 :   TDB_coin_free (&coin);
     304            1 :   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            1 : 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            1 :   TDB_account (pg,
     324              :                11,
     325              :                &other);
     326              :   /* a second deposit to the same account, and one to another */
     327            1 :   TDB_coin (pg,
     328              :             &denom,
     329              :             21,
     330              :             &c2,
     331              :             NULL);
     332            1 :   TDB_deposit (pg,
     333              :                &account,
     334              :                &c2,
     335              :                21,
     336              :                "2",
     337              :                "0.1",
     338              :                ts (1600003600),
     339              :                ts (1600003600),
     340              :                &dep);
     341            1 :   TDB_coin (pg,
     342              :             &denom,
     343              :             22,
     344              :             &c3,
     345              :             NULL);
     346            1 :   TDB_deposit (pg,
     347              :                &other,
     348              :                &c3,
     349              :                22,
     350              :                "4",
     351              :                "0.1",
     352              :                ts (1600007200),
     353              :                ts (1600007200),
     354              :                &dep);
     355            1 :   TDB_coin_free (&c2);
     356            1 :   TDB_coin_free (&c3);
     357            1 :   FAILIF (3 != TDB_count (pg,
     358              :                           "FROM coin_deposits"));
     359              : 
     360            1 :   memset (&ctx,
     361              :           0,
     362              :           sizeof (ctx));
     363            1 :   ctx.ret = GNUNET_OK;
     364            1 :   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            1 :   FAILIF (1 + 2 != ctx.value_sum);
     372              : 
     373            1 :   memset (&ctx,
     374              :           0,
     375              :           sizeof (ctx));
     376            1 :   ctx.ret = GNUNET_OK;
     377            1 :   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            1 :   FAILIF (4 != ctx.value_sum);
     385              : 
     386              :   /* a limit in the future hides everything */
     387            1 :   memset (&ctx,
     388              :           0,
     389              :           sizeof (ctx));
     390            1 :   ctx.ret = GNUNET_OK;
     391            1 :   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            1 :   FAILIF (0 != ctx.total);
     399              : 
     400              :   /* a callback that aborts with GNUNET_NO is not an error */
     401            1 :   memset (&ctx,
     402              :           0,
     403              :           sizeof (ctx));
     404            1 :   ctx.ret = GNUNET_NO;
     405            1 :   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            1 :   FAILIF (1 != ctx.total);
     413            1 :   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            1 : check_done_flag (struct TALER_EXCHANGEDB_PostgresContext *pg)
     425              : {
     426              :   struct DepositContext ctx;
     427              : 
     428            1 :   FAILIF (GNUNET_OK !=
     429              :           TDB_exec (pg,
     430              :                     "UPDATE batch_deposits SET done=TRUE;"));
     431            1 :   memset (&ctx,
     432              :           0,
     433              :           sizeof (ctx));
     434            1 :   ctx.stop_after = 1;
     435            1 :   FAILIF (3 !=
     436              :           TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
     437              :             pg,
     438              :             0,
     439              :             &deposit_cb,
     440              :             &ctx));
     441            1 :   FAILIF (1 != ctx.total);
     442              : 
     443              :   {
     444              :     struct TALER_CoinSpendPublicKeyP coin_pub;
     445              : 
     446            1 :     TDB_FILL (coin_pub,
     447              :               20);
     448            1 :     memset (&ctx,
     449              :             0,
     450              :             sizeof (ctx));
     451            1 :     ctx.coin_pub = &coin_pub;
     452            1 :     FAILIF (3 !=
     453              :             TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
     454              :               pg,
     455              :               0,
     456              :               &deposit_cb,
     457              :               &ctx));
     458            1 :     FAILIF (1 != ctx.matched);
     459            1 :     FAILIF (! ctx.done);
     460              :   }
     461            1 :   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            1 : main (int argc,
     483              :       char *const *argv)
     484              : {
     485              :   int ret;
     486              : 
     487            1 :   ret = TDB_main (argc,
     488              :                   argv,
     489              :                   "test-coin-deposits",
     490              :                   "Tests for the exchangedb `coin_deposits' table",
     491              :                   tests);
     492            1 :   TDB_account_free (&account);
     493            1 :   TDB_account_free (&other);
     494            1 :   TDB_denom_free (&denom);
     495            1 :   return ret;
     496              : }
     497              : 
     498              : 
     499              : /* end of test_coin_deposits.c */
        

Generated by: LCOV version 2.0-1