exchange

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

do_insert_known_coins.c (9136B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2022-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/do_insert_known_coins.c
     18  * @brief Implementation of the do_insert_known_coins function for Postgres
     19  * @author Christian Grothoff
     20  * @author Özgür Kesim
     21  */
     22 #include "platform.h"
     23 #include "taler/taler_error_codes.h"
     24 #include "taler/taler_dbevents.h"
     25 #include "exchangedb_lib.h"
     26 #include "taler/taler_pq_lib.h"
     27 #include "exchange-database/do_insert_known_coins.h"
     28 #include "helper.h"
     29 
     30 
     31 /**
     32  * Closure for #known_coins_cb().
     33  */
     34 struct KnownCoinsContext
     35 {
     36   /**
     37    * The coins we asked about.
     38    */
     39   const struct TALER_CoinPublicInfo *const *coins;
     40 
     41   /**
     42    * Where to put the per-coin outcome.
     43    */
     44   struct TALER_EXCHANGEDB_CoinKnownResult *results;
     45 
     46   /**
     47    * Number of entries in @e coins and @e results.
     48    */
     49   unsigned int num_coins;
     50 
     51   /**
     52    * Number of rows we have processed so far.
     53    */
     54   unsigned int num_rows;
     55 
     56   /**
     57    * Set to true if a row could not be processed.
     58    */
     59   bool failed;
     60 };
     61 
     62 
     63 /**
     64  * Function called with one row per coin.  Fills the
     65  * result entry at the coin's input position.
     66  *
     67  * @param cls a `struct KnownCoinsContext *`
     68  * @param result the result
     69  * @param num_results number of rows in @a result
     70  */
     71 static void
     72 known_coins_cb (void *cls,
     73                 PGresult *result,
     74                 unsigned int num_results)
     75 {
     76   struct KnownCoinsContext *kcc = cls;
     77 
     78   for (unsigned int i = 0; i < num_results; i++)
     79   {
     80     uint64_t idx;
     81     bool existed;
     82     uint64_t known_coin_id;
     83     bool no_known_coin_id;
     84     struct TALER_DenominationHashP h_denom_pub;
     85     bool no_denom_pub_hash;
     86     struct TALER_AgeCommitmentHashP h_age_commitment;
     87     bool no_age_commitment_hash;
     88     struct GNUNET_PQ_ResultSpec rs[] = {
     89       GNUNET_PQ_result_spec_uint64 ("out_idx",
     90                                     &idx),
     91       GNUNET_PQ_result_spec_bool ("out_existed",
     92                                   &existed),
     93       GNUNET_PQ_result_spec_allow_null (
     94         GNUNET_PQ_result_spec_uint64 ("out_known_coin_id",
     95                                       &known_coin_id),
     96         &no_known_coin_id),
     97       GNUNET_PQ_result_spec_allow_null (
     98         GNUNET_PQ_result_spec_auto_from_type ("out_denom_pub_hash",
     99                                               &h_denom_pub),
    100         &no_denom_pub_hash),
    101       GNUNET_PQ_result_spec_allow_null (
    102         GNUNET_PQ_result_spec_auto_from_type ("out_age_commitment_hash",
    103                                               &h_age_commitment),
    104         &no_age_commitment_hash),
    105       GNUNET_PQ_result_spec_end
    106     };
    107     const struct TALER_CoinPublicInfo *coin;
    108     struct TALER_EXCHANGEDB_CoinKnownResult *res;
    109 
    110     if (GNUNET_OK !=
    111         GNUNET_PQ_extract_result (result,
    112                                   rs,
    113                                   i))
    114     {
    115       GNUNET_break (0);
    116       kcc->failed = true;
    117       return;
    118     }
    119     /* out_idx is the 1-based ordinality of the input arrays */
    120     if ( (0 == idx) ||
    121          (idx > kcc->num_coins) )
    122     {
    123       GNUNET_break (0);
    124       kcc->failed = true;
    125       return;
    126     }
    127     coin = kcc->coins[idx - 1];
    128     res = &kcc->results[idx - 1];
    129     if (no_known_coin_id)
    130     {
    131       /* neither inserted nor found: the denomination is unknown */
    132       GNUNET_break (0);
    133       kcc->failed = true;
    134       return;
    135     }
    136     res->known_coin_id = known_coin_id;
    137     kcc->num_rows++;
    138     if (! existed)
    139     {
    140       /* The row was inserted by this very statement, which the
    141          query cannot see yet: what is stored is what we passed. */
    142       res->status = TALER_EXCHANGEDB_CKS_ADDED;
    143       res->h_denom_pub = coin->denom_pub_hash;
    144       res->no_age_commitment = coin->no_age_commitment;
    145       if (! coin->no_age_commitment)
    146         res->h_age_commitment = coin->h_age_commitment;
    147       continue;
    148     }
    149     res->no_age_commitment = no_age_commitment_hash;
    150     if (! no_denom_pub_hash)
    151       res->h_denom_pub = h_denom_pub;
    152     if (! no_age_commitment_hash)
    153       res->h_age_commitment = h_age_commitment;
    154     if ( (! no_denom_pub_hash) &&
    155          (0 != GNUNET_memcmp (&h_denom_pub,
    156                               &coin->denom_pub_hash)) )
    157     {
    158       GNUNET_break_op (0);
    159       res->status = TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
    160       continue;
    161     }
    162     if (no_age_commitment_hash != coin->no_age_commitment)
    163     {
    164       GNUNET_break_op (0);
    165       res->status = no_age_commitment_hash
    166         ? TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL
    167         : TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL;
    168       continue;
    169     }
    170     if ( (! no_age_commitment_hash) &&
    171          (0 != GNUNET_memcmp (&h_age_commitment,
    172                               &coin->h_age_commitment)) )
    173     {
    174       GNUNET_break_op (0);
    175       res->status = TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS;
    176       continue;
    177     }
    178     res->status = TALER_EXCHANGEDB_CKS_PRESENT;
    179   }
    180 }
    181 
    182 
    183 enum GNUNET_DB_QueryStatus
    184 TALER_EXCHANGEDB_do_insert_known_coins (
    185   struct TALER_EXCHANGEDB_PostgresContext *pg,
    186   unsigned int num_coins,
    187   const struct TALER_CoinPublicInfo *const coins[static num_coins],
    188   struct TALER_EXCHANGEDB_CoinKnownResult results[static num_coins])
    189 {
    190   struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins];
    191   struct TALER_DenominationHashP denom_pub_hashes[num_coins];
    192   bool has_age_commitments[num_coins];
    193   struct TALER_AgeCommitmentHashP age_commitment_hashes[num_coins];
    194   struct TALER_DenominationSignature denom_sigs[num_coins];
    195   struct GNUNET_PQ_QueryParam params[] = {
    196     GNUNET_PQ_query_param_array_auto_from_type (num_coins,
    197                                                 coin_pubs,
    198                                                 pg->conn),
    199     GNUNET_PQ_query_param_array_auto_from_type (num_coins,
    200                                                 denom_pub_hashes,
    201                                                 pg->conn),
    202     GNUNET_PQ_query_param_array_bool (num_coins,
    203                                       has_age_commitments,
    204                                       pg->conn),
    205     GNUNET_PQ_query_param_array_auto_from_type (num_coins,
    206                                                 age_commitment_hashes,
    207                                                 pg->conn),
    208     TALER_PQ_query_param_array_denom_sig (num_coins,
    209                                           denom_sigs,
    210                                           pg->conn),
    211     GNUNET_PQ_query_param_end
    212   };
    213   struct KnownCoinsContext kcc = {
    214     .coins = coins,
    215     .results = results,
    216     .num_coins = num_coins
    217   };
    218   enum GNUNET_DB_QueryStatus qs;
    219 
    220   GNUNET_assert (0 < num_coins);
    221   /* Gather the columns; the arrays cannot carry NULL elements, so coins
    222      without age commitment get a zero hash and a false flag. */
    223   for (unsigned int i = 0; i < num_coins; i++)
    224   {
    225     const struct TALER_CoinPublicInfo *coin = coins[i];
    226 
    227     coin_pubs[i] = coin->coin_pub;
    228     denom_pub_hashes[i] = coin->denom_pub_hash;
    229     has_age_commitments[i] = ! coin->no_age_commitment;
    230     if (coin->no_age_commitment)
    231       memset (&age_commitment_hashes[i],
    232               0,
    233               sizeof (age_commitment_hashes[i]));
    234     else
    235       age_commitment_hashes[i] = coin->h_age_commitment;
    236     denom_sigs[i] = coin->denom_sig;
    237     /* A single INSERT cannot handle the same key twice. */
    238     for (unsigned int j = 0; j < i; j++)
    239     {
    240       if (0 == GNUNET_memcmp (&coin_pubs[i],
    241                               &coin_pubs[j]))
    242       {
    243         GNUNET_break (0);
    244         GNUNET_PQ_cleanup_query_params_closures (params);
    245         return GNUNET_DB_STATUS_HARD_ERROR;
    246       }
    247     }
    248   }
    249   memset (results,
    250           0,
    251           num_coins * sizeof (*results));
    252   PREPARE (pg,
    253            "do_insert_known_coins",
    254            "SELECT"
    255            " out_idx"
    256            ",out_existed"
    257            ",out_known_coin_id"
    258            ",out_denom_pub_hash"
    259            ",out_age_commitment_hash"
    260            " FROM exchange_do_insert_known_coins"
    261            " ($1, $2, $3, $4, $5);");
    262   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    263                                              "do_insert_known_coins",
    264                                              params,
    265                                              &known_coins_cb,
    266                                              &kcc);
    267   GNUNET_PQ_cleanup_query_params_closures (params);
    268   if (0 > qs)
    269     return qs;
    270   if ( (kcc.failed) ||
    271        (kcc.num_rows != num_coins) )
    272   {
    273     /* an unknown denomination or a malformed reply */
    274     GNUNET_break (0);
    275     return GNUNET_DB_STATUS_HARD_ERROR;
    276   }
    277   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    278 }