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

            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_purse_deletion.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `purse_deletion`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_purse_delete() and
      23              :  * #TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id().
      24              :  *
      25              :  * Deleting a purse is only possible while it is undecided: the two
      26              :  * negative answers of do_purse_delete() are "no such purse" and "already
      27              :  * decided", and the checks below produce both.
      28              :  */
      29              : #include "test_common.h"
      30              : #include "exchange-database/do_expire_purse.h"
      31              : #include "exchange-database/do_purse_delete.h"
      32              : #include "exchange-database/do_purse_deposit.h"
      33              : #include "exchange-database/get_purse.h"
      34              : #include "exchange-database/iterate_all_purse_deletions_above_serial_id.h"
      35              : 
      36              : 
      37              : /**
      38              :  * Denomination the checks deposit into purses.
      39              :  */
      40              : static struct TDB_Denom denom;
      41              : 
      42              : 
      43              : /**
      44              :  * Build a timestamp from a number of seconds since the epoch.
      45              :  *
      46              :  * @param secs seconds since the epoch
      47              :  * @return the timestamp
      48              :  */
      49              : static struct GNUNET_TIME_Timestamp
      50            2 : ts (uint64_t secs)
      51              : {
      52            2 :   struct GNUNET_TIME_Absolute abs = {
      53            2 :     .abs_value_us = secs * 1000LLU * 1000LLU
      54              :   };
      55              : 
      56            2 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      57              : }
      58              : 
      59              : 
      60              : /**
      61              :  * Closure for #deletion_cb().
      62              :  */
      63              : struct DeletionContext
      64              : {
      65              :   /**
      66              :    * How many rows did the callback see?
      67              :    */
      68              :   unsigned int total;
      69              : 
      70              :   /**
      71              :    * Stop after this many rows; 0 for no limit.
      72              :    */
      73              :   unsigned int stop_after;
      74              : 
      75              :   /**
      76              :    * Purse we are looking for, NULL to match nothing.
      77              :    */
      78              :   const struct TALER_PurseContractPublicKeyP *purse_pub;
      79              : 
      80              :   /**
      81              :    * How many times did we see it?
      82              :    */
      83              :   unsigned int matched;
      84              : 
      85              :   /**
      86              :    * Signature reported for it.
      87              :    */
      88              :   struct TALER_PurseContractSignatureP purse_sig;
      89              : };
      90              : 
      91              : 
      92              : /**
      93              :  * Callback for
      94              :  * #TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id().
      95              :  *
      96              :  * @param cls a `struct DeletionContext *`
      97              :  * @param rowid row of the deletion
      98              :  * @param purse_pub the purse that was deleted
      99              :  * @param purse_sig signature affirming the deletion
     100              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     101              :  */
     102              : static enum GNUNET_GenericReturnValue
     103            2 : deletion_cb (void *cls,
     104              :              uint64_t rowid,
     105              :              const struct TALER_PurseContractPublicKeyP *purse_pub,
     106              :              const struct TALER_PurseContractSignatureP *purse_sig)
     107              : {
     108            2 :   struct DeletionContext *ctx = cls;
     109              : 
     110              :   (void) rowid;
     111            2 :   ctx->total++;
     112            2 :   if ( (NULL != ctx->purse_pub) &&
     113            1 :        (0 == GNUNET_memcmp (purse_pub,
     114              :                             ctx->purse_pub)) )
     115              :   {
     116            1 :     ctx->matched++;
     117            1 :     ctx->purse_sig = *purse_sig;
     118              :   }
     119            2 :   if ( (0 != ctx->stop_after) &&
     120            1 :        (ctx->total >= ctx->stop_after) )
     121            1 :     return GNUNET_SYSERR;
     122            1 :   return GNUNET_OK;
     123              : }
     124              : 
     125              : 
     126              : /**
     127              :  * A purse that does not exist cannot be deleted.
     128              :  *
     129              :  * @param pg the database context
     130              :  * @return 0 on success
     131              :  */
     132              : static int
     133            1 : check_unknown_purse (struct TALER_EXCHANGEDB_PostgresContext *pg)
     134              : {
     135              :   struct TALER_PurseContractPublicKeyP purse_pub;
     136              :   struct TALER_PurseContractSignatureP purse_sig;
     137            1 :   struct DeletionContext ctx = { 0 };
     138            1 :   bool decided = true;
     139            1 :   bool found = true;
     140              : 
     141            1 :   TDB_denom (pg,
     142              :              10,
     143              :              "5",
     144              :              "0.1",
     145              :              &denom);
     146            1 :   TDB_FILL (purse_pub,
     147              :             1);
     148            1 :   TDB_FILL (purse_sig,
     149              :             1);
     150            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     151              :           TALER_EXCHANGEDB_do_purse_delete (pg,
     152              :                                             &purse_pub,
     153              :                                             &purse_sig,
     154              :                                             &decided,
     155              :                                             &found));
     156            1 :   FAILIF (found);
     157            1 :   FAILIF (0 != TDB_count (pg,
     158              :                           "FROM purse_deletion"));
     159            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     160              :           TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
     161              :             pg,
     162              :             0,
     163              :             &deletion_cb,
     164              :             &ctx));
     165            1 :   FAILIF (0 != ctx.total);
     166            1 :   return 0;
     167              : }
     168              : 
     169              : 
     170              : /**
     171              :  * Deleting an undecided purse records the deletion, refunds the coins in
     172              :  * it and shows up in the purse's status.
     173              :  *
     174              :  * @param pg the database context
     175              :  * @return 0 on success
     176              :  */
     177              : static int
     178            1 : check_delete (struct TALER_EXCHANGEDB_PostgresContext *pg)
     179              : {
     180              :   struct TDB_Purse purse;
     181              :   struct TALER_CoinPublicInfo coin;
     182              :   struct TALER_PurseContractSignatureP purse_sig;
     183              :   struct TALER_CoinSpendSignatureP coin_sig;
     184            1 :   struct TALER_Amount amount = TDB_amount ("1");
     185              :   struct GNUNET_TIME_Timestamp purse_creation;
     186              :   struct GNUNET_TIME_Timestamp purse_expiration;
     187              :   struct GNUNET_TIME_Timestamp merge_timestamp;
     188              :   struct TALER_Amount target;
     189              :   struct TALER_Amount deposited;
     190              :   struct TALER_PrivateContractHashP h_contract_terms;
     191              :   struct DeletionContext ctx;
     192              :   bool balance_ok;
     193              :   bool too_late;
     194              :   bool conflict;
     195            1 :   bool decided = true;
     196            1 :   bool found = false;
     197            1 :   bool purse_deleted = false;
     198            1 :   bool purse_refunded = true;
     199              :   char *hex;
     200              : 
     201            1 :   TDB_purse (pg,
     202              :              10,
     203              :              "5",
     204              :              ts (1700000000),
     205              :              &purse);
     206            1 :   TDB_coin (pg,
     207              :             &denom,
     208              :             20,
     209              :             &coin,
     210              :             NULL);
     211            1 :   TDB_FILL (coin_sig,
     212              :             20);
     213            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     214              :             TALER_EXCHANGEDB_do_purse_deposit (pg,
     215              :                                                &purse.purse_pub,
     216              :                                                &coin.coin_pub,
     217              :                                                &amount,
     218              :                                                &coin_sig,
     219              :                                                &amount,
     220              :                                                &balance_ok,
     221              :                                                &too_late,
     222              :                                                &conflict),
     223              :             TDB_coin_free (&coin));
     224            1 :   FAILIF_C (! balance_ok,
     225              :             TDB_coin_free (&coin));
     226              : 
     227            1 :   TDB_FILL (purse_sig,
     228              :             10);
     229            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     230              :             TALER_EXCHANGEDB_do_purse_delete (pg,
     231              :                                               &purse.purse_pub,
     232              :                                               &purse_sig,
     233              :                                               &decided,
     234              :                                               &found),
     235              :             TDB_coin_free (&coin));
     236            1 :   FAILIF_C (! found,
     237              :             TDB_coin_free (&coin));
     238            1 :   FAILIF_C (decided,
     239              :             TDB_coin_free (&coin));
     240            1 :   FAILIF_C (1 != TDB_count (pg,
     241              :                             "FROM purse_deletion"),
     242              :             TDB_coin_free (&coin));
     243              :   /* the coin got its money back */
     244            1 :   hex = TDB_hex (&coin.coin_pub,
     245              :                  sizeof (coin.coin_pub));
     246            1 :   FAILIF_C (1 != TDB_count (pg,
     247              :                             "FROM known_coins"
     248              :                             " WHERE coin_pub=decode('%s','hex')"
     249              :                             "   AND remaining=ROW(5,0)::taler_amount",
     250              :                             hex),
     251              :             GNUNET_free (hex); TDB_coin_free (&coin));
     252            1 :   GNUNET_free (hex);
     253            1 :   TDB_coin_free (&coin);
     254              : 
     255              :   /* the purse reports itself as deleted */
     256            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     257              :           TALER_EXCHANGEDB_get_purse (pg,
     258              :                                       &purse.purse_pub,
     259              :                                       &purse_creation,
     260              :                                       &purse_expiration,
     261              :                                       &target,
     262              :                                       &deposited,
     263              :                                       &h_contract_terms,
     264              :                                       &merge_timestamp,
     265              :                                       &purse_deleted,
     266              :                                       &purse_refunded));
     267            1 :   FAILIF (! purse_deleted);
     268              : 
     269              :   /* the deletion is reported with the signature it was made with */
     270            1 :   memset (&ctx,
     271              :           0,
     272              :           sizeof (ctx));
     273            1 :   ctx.purse_pub = &purse.purse_pub;
     274            1 :   FAILIF (0 >=
     275              :           TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
     276              :             pg,
     277              :             0,
     278              :             &deletion_cb,
     279              :             &ctx));
     280            1 :   FAILIF (1 != ctx.matched);
     281            1 :   FAILIF (0 != GNUNET_memcmp (&ctx.purse_sig,
     282              :                               &purse_sig));
     283              : 
     284              :   /* deleting it again finds it already decided */
     285            1 :   decided = false;
     286            1 :   found = false;
     287            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     288              :           TALER_EXCHANGEDB_do_purse_delete (pg,
     289              :                                             &purse.purse_pub,
     290              :                                             &purse_sig,
     291              :                                             &decided,
     292              :                                             &found));
     293            1 :   FAILIF (! found);
     294            1 :   FAILIF (! decided);
     295            1 :   FAILIF (1 != TDB_count (pg,
     296              :                           "FROM purse_deletion"));
     297            1 :   return 0;
     298              : }
     299              : 
     300              : 
     301              : /**
     302              :  * A purse that expired first can no longer be deleted.
     303              :  *
     304              :  * @param pg the database context
     305              :  * @return 0 on success
     306              :  */
     307              : static int
     308            1 : check_already_decided (struct TALER_EXCHANGEDB_PostgresContext *pg)
     309              : {
     310              :   struct TDB_Purse purse;
     311              :   struct TALER_PurseContractSignatureP purse_sig;
     312              :   struct GNUNET_TIME_Absolute expiration;
     313            1 :   bool decided = false;
     314            1 :   bool found = false;
     315              : 
     316            1 :   TDB_purse (pg,
     317              :              11,
     318              :              "5",
     319              :              ts (1600000000),
     320              :              &purse);
     321            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     322              :           TALER_EXCHANGEDB_do_expire_purse (pg,
     323              :                                             GNUNET_TIME_UNIT_ZERO_ABS,
     324              :                                             GNUNET_TIME_absolute_get (),
     325              :                                             &expiration));
     326            1 :   TDB_FILL (purse_sig,
     327              :             11);
     328            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     329              :           TALER_EXCHANGEDB_do_purse_delete (pg,
     330              :                                             &purse.purse_pub,
     331              :                                             &purse_sig,
     332              :                                             &decided,
     333              :                                             &found));
     334            1 :   FAILIF (! found);
     335            1 :   FAILIF (! decided);
     336            1 :   FAILIF (1 != TDB_count (pg,
     337              :                           "FROM purse_deletion"));
     338            1 :   return 0;
     339              : }
     340              : 
     341              : 
     342              : /**
     343              :  * The iterator's serial bound and abort return behave as documented.
     344              :  *
     345              :  * @param pg the database context
     346              :  * @return 0 on success
     347              :  */
     348              : static int
     349            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     350              : {
     351              :   struct DeletionContext ctx;
     352              : 
     353            1 :   memset (&ctx,
     354              :           0,
     355              :           sizeof (ctx));
     356            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     357              :           TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
     358              :             pg,
     359              :             1000,
     360              :             &deletion_cb,
     361              :             &ctx));
     362            1 :   FAILIF (0 != ctx.total);
     363            1 :   memset (&ctx,
     364              :           0,
     365              :           sizeof (ctx));
     366            1 :   ctx.stop_after = 1;
     367            1 :   FAILIF (1 !=
     368              :           TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id (
     369              :             pg,
     370              :             0,
     371              :             &deletion_cb,
     372              :             &ctx));
     373            1 :   FAILIF (1 != ctx.total);
     374            1 :   return 0;
     375              : }
     376              : 
     377              : 
     378              : /**
     379              :  * The checks to run, in order.
     380              :  */
     381              : static const struct TDB_Test tests[] = {
     382              :   { "purse-deletion-unknown-purse",
     383              :     &check_unknown_purse },
     384              :   { "purse-deletion-delete",
     385              :     &check_delete },
     386              :   { "purse-deletion-already-decided",
     387              :     &check_already_decided },
     388              :   { "purse-deletion-iterate",
     389              :     &check_iterate },
     390              :   { NULL, NULL }
     391              : };
     392              : 
     393              : 
     394              : int
     395            1 : main (int argc,
     396              :       char *const *argv)
     397              : {
     398              :   int ret;
     399              : 
     400            1 :   ret = TDB_main (argc,
     401              :                   argv,
     402              :                   "test-purse-deletion",
     403              :                   "Tests for the exchangedb `purse_deletion' table",
     404              :                   tests);
     405            1 :   TDB_denom_free (&denom);
     406            1 :   return ret;
     407              : }
     408              : 
     409              : 
     410              : /* end of test_purse_deletion.c */
        

Generated by: LCOV version 2.0-1