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

            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_known_coins.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `known_coins`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_insert_known_coin(),
      23              :  * #TALER_EXCHANGEDB_get_known_coin(),
      24              :  * #TALER_EXCHANGEDB_get_coin_denomination(),
      25              :  * #TALER_EXCHANGEDB_get_signature_for_known_coin() and
      26              :  * #TALER_EXCHANGEDB_get_count_known_coins().
      27              :  *
      28              :  * `known_coins` references `denominations`, so a denomination is created
      29              :  * first with TDB_denom().  do_insert_known_coin() is the interesting one:
      30              :  * it is idempotent, but only for a coin that comes back with the *same*
      31              :  * denomination and age commitment -- the conflicting cases are what its
      32              :  * negative status codes are for.
      33              :  */
      34              : #include "test_common.h"
      35              : #include "exchange-database/do_insert_known_coin.h"
      36              : #include "exchange-database/get_known_coin.h"
      37              : #include "exchange-database/get_coin_denomination.h"
      38              : #include "exchange-database/get_signature_for_known_coin.h"
      39              : #include "exchange-database/get_count_known_coins.h"
      40              : 
      41              : 
      42              : /**
      43              :  * Nothing is known about a coin that was never inserted.
      44              :  *
      45              :  * @param pg the database context
      46              :  * @return 0 on success
      47              :  */
      48              : static int
      49            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
      50              : {
      51              :   struct TALER_CoinSpendPublicKeyP coin_pub;
      52              :   struct TALER_CoinPublicInfo info;
      53              :   struct TALER_DenominationHashP h_denom_pub;
      54            1 :   struct TALER_DenominationPublicKey denom_pub = { 0 };
      55            1 :   struct TALER_DenominationSignature denom_sig = { 0 };
      56              :   uint64_t known_coin_id;
      57              : 
      58            1 :   TDB_FILL (coin_pub,
      59              :             1);
      60            1 :   TDB_FILL (h_denom_pub,
      61              :             1);
      62            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      63              :           TALER_EXCHANGEDB_get_known_coin (pg,
      64              :                                            &coin_pub,
      65              :                                            &info));
      66            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      67              :           TALER_EXCHANGEDB_get_coin_denomination (pg,
      68              :                                                   &coin_pub,
      69              :                                                   &known_coin_id,
      70              :                                                   &h_denom_pub));
      71            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      72              :           TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
      73              :                                                          &coin_pub,
      74              :                                                          &denom_pub,
      75              :                                                          &denom_sig));
      76            1 :   FAILIF (NULL != denom_pub.bsign_pub_key);
      77            1 :   FAILIF (NULL != denom_sig.unblinded_sig);
      78              :   /* a denomination that does not exist has no coins */
      79            1 :   FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
      80              :                                                        &h_denom_pub));
      81            1 :   return 0;
      82              : }
      83              : 
      84              : 
      85              : /**
      86              :  * A coin of an unknown denomination cannot be made known.
      87              :  *
      88              :  * @param pg the database context
      89              :  * @return 0 on success
      90              :  */
      91              : static int
      92            1 : check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg)
      93              : {
      94              :   struct TALER_CoinPublicInfo coin;
      95              :   struct TALER_DenominationHashP dh;
      96              :   struct TALER_AgeCommitmentHashP hac;
      97              :   uint64_t known_coin_id;
      98              : 
      99            1 :   memset (&coin,
     100              :           0,
     101              :           sizeof (coin));
     102            1 :   TDB_FILL (coin.coin_pub,
     103              :             2);
     104            1 :   TDB_FILL (coin.denom_pub_hash,
     105              :             2);
     106            1 :   coin.no_age_commitment = true;
     107            1 :   TDB_denom_sig (2,
     108              :                  &coin.denom_sig);
     109              :   /* the "dd" CTE finds no denomination, so nothing is inserted and the
     110              :      UNION's second branch finds no coin either */
     111            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_HARD_FAIL !=
     112              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     113              :                                                    &coin,
     114              :                                                    &known_coin_id,
     115              :                                                    &dh,
     116              :                                                    &hac),
     117              :             TALER_denom_sig_free (&coin.denom_sig));
     118            1 :   TALER_denom_sig_free (&coin.denom_sig);
     119            1 :   FAILIF (0 != TDB_count (pg,
     120              :                           "FROM known_coins"));
     121            1 :   return 0;
     122              : }
     123              : 
     124              : 
     125              : /**
     126              :  * Making a coin known stores it, and every lookup finds it.
     127              :  *
     128              :  * @param pg the database context
     129              :  * @return 0 on success
     130              :  */
     131              : static int
     132            1 : check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     133              : {
     134              :   struct TDB_Denom denom;
     135              :   struct TALER_CoinPublicInfo coin;
     136              :   struct TALER_CoinPublicInfo got;
     137              :   struct TALER_DenominationHashP h_denom_pub;
     138            1 :   struct TALER_DenominationPublicKey denom_pub = { 0 };
     139            1 :   struct TALER_DenominationSignature denom_sig = { 0 };
     140            1 :   uint64_t known_coin_id = 0;
     141            1 :   uint64_t id2 = 0;
     142              : 
     143            1 :   TDB_denom (pg,
     144              :              10,
     145              :              "5",
     146              :              "0.1",
     147              :              &denom);
     148            1 :   TDB_coin (pg,
     149              :             &denom,
     150              :             20,
     151              :             &coin,
     152              :             &known_coin_id);
     153            1 :   FAILIF_C (0 == known_coin_id,
     154              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     155              : 
     156            1 :   memset (&got,
     157              :           0,
     158              :           sizeof (got));
     159            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     160              :             TALER_EXCHANGEDB_get_known_coin (pg,
     161              :                                              &coin.coin_pub,
     162              :                                              &got),
     163              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     164            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.denom_pub_hash,
     165              :                                 &denom.h_denom_pub),
     166              :             TALER_denom_sig_free (&got.denom_sig);
     167              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     168            1 :   FAILIF_C (! got.no_age_commitment,
     169              :             TALER_denom_sig_free (&got.denom_sig);
     170              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     171            1 :   FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig,
     172              :                                       &coin.denom_sig),
     173              :             TALER_denom_sig_free (&got.denom_sig);
     174              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     175            1 :   TALER_denom_sig_free (&got.denom_sig);
     176              : 
     177            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     178              :             TALER_EXCHANGEDB_get_coin_denomination (pg,
     179              :                                                     &coin.coin_pub,
     180              :                                                     &id2,
     181              :                                                     &h_denom_pub),
     182              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     183            1 :   FAILIF_C (id2 != known_coin_id,
     184              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     185            1 :   FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
     186              :                                 &denom.h_denom_pub),
     187              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     188              : 
     189            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     190              :             TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
     191              :                                                            &coin.coin_pub,
     192              :                                                            &denom_pub,
     193              :                                                            &denom_sig),
     194              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     195            1 :   FAILIF_C (0 != TALER_denom_pub_cmp (&denom_pub,
     196              :                                       &denom.pub),
     197              :             TALER_denom_pub_free (&denom_pub);
     198              :             TALER_denom_sig_free (&denom_sig);
     199              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     200            1 :   FAILIF_C (0 != TALER_denom_sig_cmp (&denom_sig,
     201              :                                       &coin.denom_sig),
     202              :             TALER_denom_pub_free (&denom_pub);
     203              :             TALER_denom_sig_free (&denom_sig);
     204              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     205            1 :   TALER_denom_pub_free (&denom_pub);
     206            1 :   TALER_denom_sig_free (&denom_sig);
     207              : 
     208            1 :   FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
     209              :                                                          &denom.h_denom_pub),
     210              :             TDB_coin_free (&coin); TDB_denom_free (&denom));
     211            1 :   TDB_coin_free (&coin);
     212            1 :   TDB_denom_free (&denom);
     213            1 :   return 0;
     214              : }
     215              : 
     216              : 
     217              : /**
     218              :  * Re-inserting the same coin is a no-op; re-inserting it under a different
     219              :  * denomination or with an age commitment is a conflict.
     220              :  *
     221              :  * @param pg the database context
     222              :  * @return 0 on success
     223              :  */
     224              : static int
     225            1 : check_conflicts (struct TALER_EXCHANGEDB_PostgresContext *pg)
     226              : {
     227              :   struct TDB_Denom denom;
     228              :   struct TDB_Denom other;
     229              :   struct TALER_CoinPublicInfo coin;
     230              :   struct TALER_DenominationHashP dh;
     231              :   struct TALER_AgeCommitmentHashP hac;
     232            1 :   uint64_t known_coin_id = 0;
     233            1 :   uint64_t id2 = 0;
     234              : 
     235            1 :   TDB_denom (pg,
     236              :              10,
     237              :              "5",
     238              :              "0.1",
     239              :              &denom);
     240            1 :   TDB_denom (pg,
     241              :              11,
     242              :              "5",
     243              :              "0.1",
     244              :              &other);
     245            1 :   memset (&coin,
     246              :           0,
     247              :           sizeof (coin));
     248            1 :   TDB_FILL (coin.coin_pub,
     249              :             20);
     250            1 :   coin.denom_pub_hash = denom.h_denom_pub;
     251            1 :   coin.no_age_commitment = true;
     252            1 :   TDB_denom_sig (20,
     253              :                  &coin.denom_sig);
     254              : 
     255              :   /* the coin from the previous check: already present, no conflict */
     256            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT !=
     257              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     258              :                                                    &coin,
     259              :                                                    &known_coin_id,
     260              :                                                    &dh,
     261              :                                                    &hac),
     262              :             TALER_denom_sig_free (&coin.denom_sig);
     263              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     264            1 :   FAILIF_C (0 == known_coin_id,
     265              :             TALER_denom_sig_free (&coin.denom_sig);
     266              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     267              : 
     268              :   /* same coin key, different denomination: conflict, and the stored
     269              :      denomination is handed back so the caller can report it */
     270            1 :   coin.denom_pub_hash = other.h_denom_pub;
     271            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT !=
     272              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     273              :                                                    &coin,
     274              :                                                    &id2,
     275              :                                                    &dh,
     276              :                                                    &hac),
     277              :             TALER_denom_sig_free (&coin.denom_sig);
     278              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     279            1 :   FAILIF_C (0 != GNUNET_memcmp (&dh,
     280              :                                 &denom.h_denom_pub),
     281              :             TALER_denom_sig_free (&coin.denom_sig);
     282              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     283              : 
     284              :   /* same coin key and denomination, but now with an age commitment where
     285              :      the stored row has none: the caller should have passed NULL */
     286            1 :   coin.denom_pub_hash = denom.h_denom_pub;
     287            1 :   coin.no_age_commitment = false;
     288            1 :   TDB_FILL (coin.h_age_commitment,
     289              :             21);
     290            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL !=
     291              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     292              :                                                    &coin,
     293              :                                                    &id2,
     294              :                                                    &dh,
     295              :                                                    &hac),
     296              :             TALER_denom_sig_free (&coin.denom_sig);
     297              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     298            1 :   TALER_denom_sig_free (&coin.denom_sig);
     299              : 
     300              :   /* the conflicting attempts did not add anything */
     301            1 :   FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
     302              :                                                          &denom.h_denom_pub),
     303              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     304            1 :   FAILIF_C (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
     305              :                                                          &other.h_denom_pub),
     306              :             TDB_denom_free (&denom); TDB_denom_free (&other));
     307            1 :   TDB_denom_free (&denom);
     308            1 :   TDB_denom_free (&other);
     309            1 :   return 0;
     310              : }
     311              : 
     312              : 
     313              : /**
     314              :  * A coin with an age commitment round-trips, and the mirror-image
     315              :  * conflict is reported.
     316              :  *
     317              :  * @param pg the database context
     318              :  * @return 0 on success
     319              :  */
     320              : static int
     321            1 : check_age_commitment (struct TALER_EXCHANGEDB_PostgresContext *pg)
     322              : {
     323              :   struct TDB_Denom denom;
     324              :   struct TALER_CoinPublicInfo coin;
     325              :   struct TALER_CoinPublicInfo got;
     326              :   struct TALER_DenominationHashP dh;
     327              :   struct TALER_AgeCommitmentHashP hac;
     328            1 :   uint64_t known_coin_id = 0;
     329              : 
     330            1 :   TDB_denom (pg,
     331              :              10,
     332              :              "5",
     333              :              "0.1",
     334              :              &denom);
     335            1 :   memset (&coin,
     336              :           0,
     337              :           sizeof (coin));
     338            1 :   TDB_FILL (coin.coin_pub,
     339              :             30);
     340            1 :   coin.denom_pub_hash = denom.h_denom_pub;
     341            1 :   coin.no_age_commitment = false;
     342            1 :   TDB_FILL (coin.h_age_commitment,
     343              :             31);
     344            1 :   TDB_denom_sig (30,
     345              :                  &coin.denom_sig);
     346            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED !=
     347              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     348              :                                                    &coin,
     349              :                                                    &known_coin_id,
     350              :                                                    &dh,
     351              :                                                    &hac),
     352              :             TALER_denom_sig_free (&coin.denom_sig);
     353              :             TDB_denom_free (&denom));
     354            1 :   memset (&got,
     355              :           0,
     356              :           sizeof (got));
     357            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     358              :             TALER_EXCHANGEDB_get_known_coin (pg,
     359              :                                              &coin.coin_pub,
     360              :                                              &got),
     361              :             TALER_denom_sig_free (&coin.denom_sig);
     362              :             TDB_denom_free (&denom));
     363            1 :   FAILIF_C (got.no_age_commitment,
     364              :             TALER_denom_sig_free (&got.denom_sig);
     365              :             TALER_denom_sig_free (&coin.denom_sig);
     366              :             TDB_denom_free (&denom));
     367            1 :   FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment,
     368              :                                 &coin.h_age_commitment),
     369              :             TALER_denom_sig_free (&got.denom_sig);
     370              :             TALER_denom_sig_free (&coin.denom_sig);
     371              :             TDB_denom_free (&denom));
     372            1 :   TALER_denom_sig_free (&got.denom_sig);
     373              : 
     374              :   /* a different age commitment for the same coin is a conflict... */
     375            1 :   TDB_FILL (coin.h_age_commitment,
     376              :             32);
     377            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS !=
     378              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     379              :                                                    &coin,
     380              :                                                    &known_coin_id,
     381              :                                                    &dh,
     382              :                                                    &hac),
     383              :             TALER_denom_sig_free (&coin.denom_sig);
     384              :             TDB_denom_free (&denom));
     385              :   /* ...and so is no age commitment at all, where one is on file */
     386            1 :   coin.no_age_commitment = true;
     387            1 :   FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL !=
     388              :             TALER_EXCHANGEDB_do_insert_known_coin (pg,
     389              :                                                    &coin,
     390              :                                                    &known_coin_id,
     391              :                                                    &dh,
     392              :                                                    &hac),
     393              :             TALER_denom_sig_free (&coin.denom_sig);
     394              :             TDB_denom_free (&denom));
     395            1 :   TALER_denom_sig_free (&coin.denom_sig);
     396              : 
     397              :   /* two coins of this denomination by now */
     398            1 :   FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg,
     399              :                                                          &denom.h_denom_pub),
     400              :             TDB_denom_free (&denom));
     401            1 :   TDB_denom_free (&denom);
     402            1 :   return 0;
     403              : }
     404              : 
     405              : 
     406              : /**
     407              :  * The checks to run, in order.
     408              :  */
     409              : static const struct TDB_Test tests[] = {
     410              :   { "known-coins-empty",
     411              :     &check_empty },
     412              :   { "known-coins-unknown-denomination",
     413              :     &check_unknown_denomination },
     414              :   { "known-coins-insert-and-lookup",
     415              :     &check_insert_and_lookup },
     416              :   { "known-coins-conflicts",
     417              :     &check_conflicts },
     418              :   { "known-coins-age-commitment",
     419              :     &check_age_commitment },
     420              :   { NULL, NULL }
     421              : };
     422              : 
     423              : 
     424              : int
     425            1 : main (int argc,
     426              :       char *const *argv)
     427              : {
     428            1 :   return TDB_main (argc,
     429              :                    argv,
     430              :                    "test-known-coins",
     431              :                    "Tests for the exchangedb `known_coins' table",
     432              :                    tests);
     433              : }
     434              : 
     435              : 
     436              : /* end of test_known_coins.c */
        

Generated by: LCOV version 2.0-1