LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_recoup_refresh.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.1 % 189 140
Test Date: 2026-10-17 15:46:49 Functions: 85.7 % 7 6

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2018-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 testing/testing_api_cmd_recoup_refresh.c
      18              :  * @brief Implement the /recoup-refresh test command.
      19              :  * @author Marcello Stanisci
      20              :  * @author Christian Grothoff
      21              :  * @author Özgür Kesim
      22              :  */
      23              : #include "platform.h"
      24              : #include "taler/taler_json_lib.h"
      25              : #include <gnunet/gnunet_curl_lib.h>
      26              : #include "taler/taler_testing_lib.h"
      27              : 
      28              : 
      29              : /**
      30              :  * State for a "recoup-refresh" CMD.
      31              :  */
      32              : struct RecoupRefreshState
      33              : {
      34              :   /**
      35              :    * Expected HTTP status code.
      36              :    */
      37              :   unsigned int expected_response_code;
      38              : 
      39              :   /**
      40              :    * Label of the reveal-melt command the fresh coins came from.
      41              :    */
      42              :   char *reveal_reference;
      43              : 
      44              :   /**
      45              :    * Label of the melt command.
      46              :    */
      47              :   const char *melt_reference;
      48              : 
      49              :   /**
      50              :    * Indices (in the reveal command) of the coins to recoup.
      51              :    */
      52              :   unsigned int *indices;
      53              : 
      54              :   /**
      55              :    * Number of entries in @e indices, @e ches_new, @e ches_old
      56              :    * and @e coin_pubs.
      57              :    */
      58              :   size_t num_indices;
      59              : 
      60              :   /**
      61              :    * Amount we expect to be recouped per coin, invalid if
      62              :    * not specified.
      63              :    */
      64              :   struct TALER_Amount amount;
      65              : 
      66              :   /**
      67              :    * Interpreter state.
      68              :    */
      69              :   struct TALER_TESTING_Interpreter *is;
      70              : 
      71              :   /**
      72              :    * Handle to the request.
      73              :    */
      74              :   struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph;
      75              : 
      76              :   /**
      77              :    * Old coin that is credited.
      78              :    */
      79              :   struct TALER_CoinSpendPublicKeyP old_coin_pub;
      80              : 
      81              :   /**
      82              :    * Coin history entries we expect for the recouped coins.
      83              :    */
      84              :   struct TALER_EXCHANGE_CoinHistoryEntry *ches_new;
      85              : 
      86              :   /**
      87              :    * Coin history entries we expect for the old coin, one per
      88              :    * recouped coin.
      89              :    */
      90              :   struct TALER_EXCHANGE_CoinHistoryEntry *ches_old;
      91              : 
      92              :   /**
      93              :    * Public keys of the recouped coins.
      94              :    */
      95              :   struct TALER_CoinSpendPublicKeyP *coin_pubs;
      96              : };
      97              : 
      98              : 
      99              : /**
     100              :  * Check the response.
     101              :  *
     102              :  * @param cls closure, our `struct RecoupRefreshState`
     103              :  * @param rrr response details
     104              :  */
     105              : static void
     106           20 : recoup_refresh_cb (void *cls,
     107              :                    const struct TALER_EXCHANGE_PostRecoupRefreshResponse *rrr)
     108              : {
     109           20 :   struct RecoupRefreshState *rrs = cls;
     110           20 :   const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
     111           20 :   struct TALER_TESTING_Interpreter *is = rrs->is;
     112              : 
     113           20 :   rrs->ph = NULL;
     114           20 :   if (rrs->expected_response_code != hr->http_status)
     115              :   {
     116            0 :     TALER_TESTING_unexpected_status (is,
     117              :                                      hr->http_status,
     118              :                                      rrs->expected_response_code);
     119            0 :     return;
     120              :   }
     121           20 :   switch (hr->http_status)
     122              :   {
     123           18 :   case MHD_HTTP_OK:
     124           18 :     if (0 != GNUNET_memcmp (&rrr->details.ok.old_coin_pub,
     125              :                             &rrs->old_coin_pub))
     126              :     {
     127            0 :       GNUNET_break (0);
     128            0 :       TALER_TESTING_interpreter_fail (is);
     129            0 :       return;
     130              :     }
     131           18 :     if (rrr->details.ok.num_recoups != rrs->num_indices)
     132              :     {
     133            0 :       GNUNET_break (0);
     134            0 :       TALER_TESTING_interpreter_fail (is);
     135            0 :       return;
     136              :     }
     137           36 :     for (size_t i = 0; i < rrs->num_indices; i++)
     138              :     {
     139           18 :       const struct TALER_RecoupedCoin *rc = &rrr->details.ok.recoups[i];
     140              : 
     141           18 :       if (0 != GNUNET_memcmp (&rc->coin_pub,
     142              :                               &rrs->coin_pubs[i]))
     143              :       {
     144            0 :         GNUNET_break (0);
     145            0 :         TALER_TESTING_interpreter_fail (is);
     146            0 :         return;
     147              :       }
     148           18 :       if ( (GNUNET_OK ==
     149           36 :             TALER_amount_is_valid (&rrs->amount)) &&
     150           18 :            (0 != TALER_amount_cmp (&rc->amount,
     151           18 :                                    &rrs->amount)) )
     152              :       {
     153            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     154              :                     "Recouped %s for coin %u, expected %s\n",
     155              :                     TALER_amount2s (&rc->amount),
     156              :                     rrs->indices[i],
     157              :                     TALER_amount2s (&rrs->amount));
     158            0 :         GNUNET_break (0);
     159            0 :         TALER_TESTING_interpreter_fail (is);
     160            0 :         return;
     161              :       }
     162           18 :       rrs->ches_new[i].amount = rc->amount;
     163           18 :       rrs->ches_old[i].amount = rc->amount;
     164              :     }
     165           18 :     break;
     166            2 :   default:
     167            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     168              :                 "Recoup-refresh failed as expected with HTTP status %u/%d\n",
     169              :                 hr->http_status,
     170              :                 (int) hr->ec);
     171            2 :     break;
     172              :   }
     173           20 :   TALER_TESTING_interpreter_next (is);
     174              : }
     175              : 
     176              : 
     177              : /**
     178              :  * Run the command.
     179              :  *
     180              :  * @param cls closure.
     181              :  * @param cmd the command to execute.
     182              :  * @param is the interpreter state.
     183              :  */
     184              : static void
     185           20 : recoup_refresh_run (void *cls,
     186              :                     const struct TALER_TESTING_Command *cmd,
     187              :                     struct TALER_TESTING_Interpreter *is)
     188              : {
     189           20 :   struct RecoupRefreshState *rrs = cls;
     190              :   const struct TALER_TESTING_Command *reveal_cmd;
     191              :   const struct TALER_TESTING_Command *melt_cmd;
     192              :   const struct TALER_CoinSpendPublicKeyP *old_coin_pub;
     193              :   const struct TALER_RefreshCommitmentP *rc;
     194           20 :   const struct TALER_BlindingMasterSeedP *blinding_seed = NULL;
     195              :   const unsigned int *num_coins_p;
     196              :   size_t num_coins;
     197              : 
     198              :   (void) cmd;
     199           20 :   rrs->is = is;
     200           20 :   reveal_cmd = TALER_TESTING_interpreter_lookup_command (is,
     201           20 :                                                          rrs->reveal_reference);
     202           20 :   melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
     203              :                                                        rrs->melt_reference);
     204           20 :   if ( (NULL == reveal_cmd) ||
     205              :        (NULL == melt_cmd) )
     206              :   {
     207            0 :     GNUNET_break (0);
     208            0 :     TALER_TESTING_interpreter_fail (is);
     209            0 :     return;
     210              :   }
     211           20 :   if ( (GNUNET_OK !=
     212           20 :         TALER_TESTING_get_trait_coin_pub (melt_cmd,
     213              :                                           0,
     214           20 :                                           &old_coin_pub)) ||
     215              :        (GNUNET_OK !=
     216           20 :         TALER_TESTING_get_trait_refresh_commitment (melt_cmd,
     217           20 :                                                     &rc)) ||
     218              :        (GNUNET_OK !=
     219           20 :         TALER_TESTING_get_trait_array_length (reveal_cmd,
     220              :                                               &num_coins_p)) )
     221              :   {
     222            0 :     GNUNET_break (0);
     223            0 :     TALER_TESTING_interpreter_fail (is);
     224            0 :     return;
     225              :   }
     226              :   /* NULL if the melt used no CS denomination */
     227           20 :   if (GNUNET_OK !=
     228           20 :       TALER_TESTING_get_trait_blinding_seed (melt_cmd,
     229              :                                              &blinding_seed))
     230            0 :     blinding_seed = NULL;
     231           20 :   rrs->old_coin_pub = *old_coin_pub;
     232           20 :   num_coins = *num_coins_p;
     233           20 :   if (0 == num_coins)
     234              :   {
     235            0 :     GNUNET_break (0);
     236            0 :     TALER_TESTING_interpreter_fail (is);
     237            0 :     return;
     238              :   }
     239           40 :   for (size_t i = 0; i < rrs->num_indices; i++)
     240           20 :     if (rrs->indices[i] >= num_coins)
     241              :     {
     242            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     243              :                   "Coin index %u out of range for command %s (%u coins)\n",
     244              :                   rrs->indices[i],
     245              :                   rrs->reveal_reference,
     246              :                   (unsigned int) num_coins);
     247            0 :       GNUNET_break (0);
     248            0 :       TALER_TESTING_interpreter_fail (is);
     249            0 :       return;
     250              :     }
     251           20 :   {
     252           20 :     struct TALER_EXCHANGE_RecoupCoin coins[num_coins];
     253           20 :     size_t k = 0;
     254              : 
     255           20 :     memset (coins,
     256              :             0,
     257              :             sizeof (coins));
     258           88 :     for (size_t i = 0; i < num_coins; i++)
     259              :     {
     260           68 :       struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
     261           68 :       const struct TALER_AgeCommitmentHashP *hac = NULL;
     262           68 :       bool recoup = false;
     263              : 
     264           68 :       if ( (GNUNET_OK !=
     265           68 :             TALER_TESTING_get_trait_denom_pub (reveal_cmd,
     266              :                                                (unsigned int) i,
     267           68 :                                                &c->pk)) ||
     268              :            (GNUNET_OK !=
     269           68 :             TALER_TESTING_get_trait_planchet_secrets (reveal_cmd,
     270              :                                                       (unsigned int) i,
     271           68 :                                                       &c->ps)) ||
     272              :            (GNUNET_OK !=
     273           68 :             TALER_TESTING_get_trait_exchange_blinding_values (
     274              :               melt_cmd,
     275              :               (unsigned int) i,
     276              :               &c->exchange_vals)) )
     277              :       {
     278            0 :         GNUNET_break (0);
     279            0 :         TALER_TESTING_interpreter_fail (is);
     280            0 :         return;
     281              :       }
     282              :       /* only denominations with age restriction have a commitment */
     283           68 :       if ( (0 != c->pk->key.age_mask.bits) &&
     284              :            (GNUNET_OK ==
     285            0 :             TALER_TESTING_get_trait_h_age_commitment (reveal_cmd,
     286              :                                                       (unsigned int) i,
     287              :                                                       &hac)) )
     288            0 :         c->h_age_commitment = hac; /* may still be NULL */
     289          136 :       for (size_t j = 0; j < rrs->num_indices; j++)
     290           68 :         if (rrs->indices[j] == i)
     291           20 :           recoup = true;
     292           68 :       if (! recoup)
     293           48 :         continue;
     294           20 :       if (GNUNET_OK !=
     295           20 :           TALER_TESTING_get_trait_denom_sig (reveal_cmd,
     296              :                                              (unsigned int) i,
     297              :                                              &c->denom_sig))
     298              :       {
     299            0 :         GNUNET_break (0);
     300            0 :         TALER_TESTING_interpreter_fail (is);
     301            0 :         return;
     302              :       }
     303              :       /* what the histories should show for this coin and the old coin */
     304              :       {
     305           20 :         struct TALER_EXCHANGE_CoinHistoryEntry *che_new = &rrs->ches_new[k];
     306           20 :         struct TALER_EXCHANGE_CoinHistoryEntry *che_old = &rrs->ches_old[k];
     307              :         struct TALER_CoinSpendPrivateKeyP coin_priv;
     308           20 :         const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals;
     309              : 
     310           20 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     311              :                     "Trying to recoup-refresh coin %u of denomination '%s'\n",
     312              :                     (unsigned int) i,
     313              :                     TALER_B2S (&c->pk->h_key));
     314           20 :         TALER_planchet_setup_coin_priv (c->ps,
     315              :                                         vals,
     316              :                                         &coin_priv);
     317           20 :         GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
     318           20 :                                             &rrs->coin_pubs[k].eddsa_pub);
     319           20 :         che_new->type = TALER_EXCHANGE_CTT_RECOUP_REFRESH;
     320           20 :         che_new->amount = rrs->amount;
     321           20 :         che_new->details.recoup_refresh.old_coin_pub = rrs->old_coin_pub;
     322           20 :         che_new->details.recoup_refresh.rc = *rc;
     323           20 :         che_new->details.recoup_refresh.coin_index = (uint32_t) i;
     324           20 :         TALER_planchet_blinding_secret_create (
     325              :           c->ps,
     326              :           vals,
     327              :           &che_new->details.recoup_refresh.coin_bks);
     328              :         /* exercise the documented contract: the request needs no
     329              :            exchange values for RSA denominations */
     330           20 :         if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher)
     331           10 :           c->exchange_vals = NULL;
     332           20 :         TALER_wallet_recoup_refresh_sign (
     333           20 :           &c->pk->h_key,
     334           20 :           &che_new->details.recoup_refresh.coin_bks,
     335              :           &coin_priv,
     336              :           &che_new->details.recoup_refresh.coin_sig);
     337           20 :         che_old->type = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP;
     338           20 :         che_old->amount = rrs->amount;
     339           20 :         che_old->details.old_coin_recoup.new_coin_pub = rrs->coin_pubs[k];
     340           20 :         che_old->details.old_coin_recoup.rc = *rc;
     341           20 :         k++;
     342              :       }
     343              :     }
     344           20 :     rrs->ph = TALER_EXCHANGE_post_recoup_refresh_create (
     345              :       TALER_TESTING_interpreter_get_context (is),
     346              :       TALER_TESTING_get_exchange_url (is),
     347              :       TALER_TESTING_get_keys (is),
     348           20 :       &rrs->old_coin_pub,
     349              :       rc,
     350              :       blinding_seed,
     351              :       num_coins,
     352              :       coins);
     353              :   }
     354           20 :   if (NULL == rrs->ph)
     355              :   {
     356            0 :     GNUNET_break (0);
     357            0 :     TALER_TESTING_interpreter_fail (is);
     358            0 :     return;
     359              :   }
     360           20 :   GNUNET_assert (TALER_EC_NONE ==
     361              :                  TALER_EXCHANGE_post_recoup_refresh_start (rrs->ph,
     362              :                                                            &recoup_refresh_cb,
     363              :                                                            rrs));
     364              : }
     365              : 
     366              : 
     367              : /**
     368              :  * Cleanup the state.
     369              :  *
     370              :  * @param cls closure, must be a `struct RecoupRefreshState`.
     371              :  * @param cmd the command which is being cleaned up.
     372              :  */
     373              : static void
     374           20 : recoup_refresh_cleanup (void *cls,
     375              :                         const struct TALER_TESTING_Command *cmd)
     376              : {
     377           20 :   struct RecoupRefreshState *rrs = cls;
     378              : 
     379              :   (void) cmd;
     380           20 :   if (NULL != rrs->ph)
     381              :   {
     382            0 :     TALER_EXCHANGE_post_recoup_refresh_cancel (rrs->ph);
     383            0 :     rrs->ph = NULL;
     384              :   }
     385           20 :   GNUNET_free (rrs->reveal_reference);
     386           20 :   GNUNET_free (rrs->indices);
     387           20 :   GNUNET_free (rrs->ches_new);
     388           20 :   GNUNET_free (rrs->ches_old);
     389           20 :   GNUNET_free (rrs->coin_pubs);
     390           20 :   GNUNET_free (rrs);
     391           20 : }
     392              : 
     393              : 
     394              : /**
     395              :  * Offer internal data to other commands: for index k below the
     396              :  * number of recouped coins, the recouped coin and its history
     397              :  * entry; for the following indices, the old coin and its history
     398              :  * entry for the respective recoup.
     399              :  *
     400              :  * @param cls closure
     401              :  * @param[out] ret result (could be anything)
     402              :  * @param trait name of the trait
     403              :  * @param index index number of the object to extract.
     404              :  * @return #GNUNET_OK on success
     405              :  */
     406              : static enum GNUNET_GenericReturnValue
     407           72 : recoup_refresh_traits (void *cls,
     408              :                        const void **ret,
     409              :                        const char *trait,
     410              :                        unsigned int index)
     411              : {
     412           72 :   struct RecoupRefreshState *rrs = cls;
     413              :   size_t k;
     414              :   bool old;
     415              : 
     416           72 :   if (index >= 2 * rrs->num_indices)
     417           18 :     return GNUNET_NO;
     418           54 :   old = (index >= rrs->num_indices);
     419           54 :   k = old ? index - rrs->num_indices : index;
     420           54 :   if (GNUNET_OK !=
     421           54 :       TALER_amount_is_valid (&rrs->ches_new[k].amount))
     422            0 :     return GNUNET_SYSERR; /* request did not succeed: no traits */
     423              :   {
     424              :     struct TALER_TESTING_Trait traits[] = {
     425          108 :       TALER_TESTING_make_trait_coin_history (index,
     426              :                                              old
     427           18 :                                              ? &rrs->ches_old[k]
     428           36 :                                              : &rrs->ches_new[k]),
     429           90 :       TALER_TESTING_make_trait_coin_pub (index,
     430              :                                          old
     431              :                                          ? &rrs->old_coin_pub
     432           36 :                                          : &rrs->coin_pubs[k]),
     433           54 :       TALER_TESTING_trait_end ()
     434              :     };
     435              : 
     436           54 :     return TALER_TESTING_get_trait (traits,
     437              :                                     ret,
     438              :                                     trait,
     439              :                                     index);
     440              :   }
     441              : }
     442              : 
     443              : 
     444              : /**
     445              :  * Create the command state shared by both constructors.
     446              :  *
     447              :  * @param label the command label
     448              :  * @param expected_response_code expected HTTP status code
     449              :  * @param reveal_reference label of the reveal-melt command
     450              :  * @param indices coins to recoup, becomes owned by the state
     451              :  * @param num_indices length of @a indices
     452              :  * @param melt_reference label of the melt command
     453              :  * @param amount expected amount per coin, NULL for none
     454              :  * @return the command
     455              :  */
     456              : static struct TALER_TESTING_Command
     457           20 : make_recoup_refresh_cmd (const char *label,
     458              :                          unsigned int expected_response_code,
     459              :                          const char *reveal_reference,
     460              :                          unsigned int *indices,
     461              :                          size_t num_indices,
     462              :                          const char *melt_reference,
     463              :                          const char *amount)
     464              : {
     465              :   struct RecoupRefreshState *rrs;
     466              : 
     467           20 :   rrs = GNUNET_new (struct RecoupRefreshState);
     468           20 :   rrs->expected_response_code = expected_response_code;
     469           20 :   rrs->reveal_reference = GNUNET_strdup (reveal_reference);
     470           20 :   rrs->melt_reference = melt_reference;
     471           20 :   rrs->indices = indices;
     472           20 :   rrs->num_indices = num_indices;
     473           20 :   rrs->ches_new = GNUNET_new_array (num_indices,
     474              :                                     struct TALER_EXCHANGE_CoinHistoryEntry);
     475           20 :   rrs->ches_old = GNUNET_new_array (num_indices,
     476              :                                     struct TALER_EXCHANGE_CoinHistoryEntry);
     477           20 :   rrs->coin_pubs = GNUNET_new_array (num_indices,
     478              :                                      struct TALER_CoinSpendPublicKeyP);
     479           40 :   if ( (NULL != amount) &&
     480              :        (GNUNET_OK !=
     481           20 :         TALER_string_to_amount (amount,
     482              :                                 &rrs->amount)) )
     483              :   {
     484            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     485              :                 "Failed to parse amount `%s' at %s\n",
     486              :                 amount,
     487              :                 label);
     488            0 :     GNUNET_assert (0);
     489              :   }
     490              :   {
     491           20 :     struct TALER_TESTING_Command cmd = {
     492              :       .cls = rrs,
     493              :       .label = label,
     494              :       .run = &recoup_refresh_run,
     495              :       .cleanup = &recoup_refresh_cleanup,
     496              :       .traits = &recoup_refresh_traits
     497              :     };
     498              : 
     499           20 :     return cmd;
     500              :   }
     501              : }
     502              : 
     503              : 
     504              : struct TALER_TESTING_Command
     505           20 : TALER_TESTING_cmd_recoup_refresh (const char *label,
     506              :                                   unsigned int expected_response_code,
     507              :                                   const char *coin_reference,
     508              :                                   const char *melt_reference,
     509              :                                   const char *amount)
     510              : {
     511              :   char *cref;
     512              :   unsigned int idx;
     513              :   unsigned int *indices;
     514              :   struct TALER_TESTING_Command cmd;
     515              : 
     516           20 :   GNUNET_assert (GNUNET_OK ==
     517              :                  TALER_TESTING_parse_coin_reference (coin_reference,
     518              :                                                      &cref,
     519              :                                                      &idx));
     520           20 :   indices = GNUNET_new_array (1,
     521              :                               unsigned int);
     522           20 :   indices[0] = idx;
     523           20 :   cmd = make_recoup_refresh_cmd (label,
     524              :                                  expected_response_code,
     525              :                                  cref,
     526              :                                  indices,
     527              :                                  1,
     528              :                                  melt_reference,
     529              :                                  amount);
     530           20 :   GNUNET_free (cref);
     531           20 :   return cmd;
     532              : }
     533              : 
     534              : 
     535              : struct TALER_TESTING_Command
     536            0 : TALER_TESTING_cmd_recoup_refresh_batch (const char *label,
     537              :                                         unsigned int expected_response_code,
     538              :                                         const char *reveal_reference,
     539              :                                         const char *indices,
     540              :                                         const char *melt_reference,
     541              :                                         const char *amount)
     542              : {
     543              :   unsigned int *idx;
     544              :   size_t num;
     545              : 
     546            0 :   idx = TALER_TESTING_parse_index_list (indices,
     547              :                                         &num);
     548            0 :   GNUNET_assert (NULL != idx);
     549            0 :   return make_recoup_refresh_cmd (label,
     550              :                                   expected_response_code,
     551              :                                   reveal_reference,
     552              :                                   idx,
     553              :                                   num,
     554              :                                   melt_reference,
     555              :                                   amount);
     556              : }
     557              : 
     558              : 
     559              : /* end of testing_api_cmd_recoup_refresh.c */
        

Generated by: LCOV version 2.0-1