LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_purse_deposit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 81.6 % 147 120
Test Date: 2026-04-14 15:39:31 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2022, 2023 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it
       6              :   under the terms of the GNU General Public License as published by
       7              :   the Free Software Foundation; either version 3, or (at your
       8              :   option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful, but
      11              :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13              :   General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not, see
      17              :   <http://www.gnu.org/licenses/>
      18              : */
      19              : /**
      20              :  * @file testing/testing_api_cmd_purse_deposit.c
      21              :  * @brief command for testing /purses/$PID/create
      22              :  * @author Christian Grothoff
      23              :  */
      24              : #include "taler/taler_json_lib.h"
      25              : #include <gnunet/gnunet_curl_lib.h>
      26              : #include "taler/taler_testing_lib.h"
      27              : #include "taler/taler_signatures.h"
      28              : 
      29              : /**
      30              :  * Information we keep per deposited coin.
      31              :  */
      32              : struct Coin
      33              : {
      34              :   /**
      35              :    * Reference to the respective command.
      36              :    */
      37              :   char *command_ref;
      38              : 
      39              :   /**
      40              :    * Entry in the coin's history generated by this operation.
      41              :    */
      42              :   struct TALER_EXCHANGE_CoinHistoryEntry che;
      43              : 
      44              :   /**
      45              :    * Public key of the deposited coin.
      46              :    */
      47              :   struct TALER_CoinSpendPublicKeyP coin_pub;
      48              : 
      49              :   /**
      50              :    * index of the specific coin in the traits of @e command_ref.
      51              :    */
      52              :   unsigned int coin_index;
      53              : 
      54              :   /**
      55              :    * Amount to deposit (with fee).
      56              :    */
      57              :   struct TALER_Amount deposit_with_fee;
      58              : 
      59              : };
      60              : 
      61              : 
      62              : /**
      63              :  * State for a "purse deposit" CMD.
      64              :  */
      65              : struct PurseDepositState
      66              : {
      67              : 
      68              :   /**
      69              :    * Total purse target amount without fees.
      70              :    */
      71              :   struct TALER_Amount target_amount;
      72              : 
      73              :   /**
      74              :    * Reference to any command that is able to provide a coin.
      75              :    */
      76              :   struct Coin *coin_references;
      77              : 
      78              :   /**
      79              :    * The purse's public key.
      80              :    */
      81              :   struct TALER_PurseContractPublicKeyP purse_pub;
      82              : 
      83              :   /**
      84              :    * The reserve we are being deposited into.
      85              :    * Set as a trait once we know the reserve.
      86              :    */
      87              :   struct TALER_ReservePublicKeyP reserve_pub;
      88              : 
      89              :   /**
      90              :    * PurseDeposit handle while operation is running.
      91              :    */
      92              :   struct TALER_EXCHANGE_PostPursesDepositHandle *dh;
      93              : 
      94              :   /**
      95              :    * Interpreter state.
      96              :    */
      97              :   struct TALER_TESTING_Interpreter *is;
      98              : 
      99              :   /**
     100              :    * Reference to the command that established the purse.
     101              :    */
     102              :   const char *purse_ref;
     103              : 
     104              :   /**
     105              :    * Reserve history entry that corresponds to this operation.
     106              :    * Will be of type #TALER_EXCHANGE_RTT_MERGE.
     107              :    * Only valid if @e purse_complete is true.
     108              :    */
     109              :   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
     110              :   /**
     111              :    * Expected HTTP response code.
     112              :    */
     113              :   unsigned int expected_response_code;
     114              : 
     115              :   /**
     116              :    * Length of the @e coin_references array.
     117              :    */
     118              :   unsigned int num_coin_references;
     119              : 
     120              :   /**
     121              :    * Minimum age to apply to all deposits.
     122              :    */
     123              :   uint8_t min_age;
     124              : 
     125              :   /**
     126              :    * Set to true if this deposit filled the purse.
     127              :    */
     128              :   bool purse_complete;
     129              : };
     130              : 
     131              : 
     132              : /**
     133              :  * Callback to analyze the /purses/$PID/deposit response, just used to check if
     134              :  * the response code is acceptable.
     135              :  *
     136              :  * @param cls closure.
     137              :  * @param dr deposit response details
     138              :  */
     139              : static void
     140            7 : deposit_cb (void *cls,
     141              :             const struct TALER_EXCHANGE_PostPursesDepositResponse *dr)
     142              : {
     143            7 :   struct PurseDepositState *ds = cls;
     144              : 
     145            7 :   ds->dh = NULL;
     146            7 :   if (ds->expected_response_code != dr->hr.http_status)
     147              :   {
     148            0 :     TALER_TESTING_unexpected_status (ds->is,
     149              :                                      dr->hr.http_status,
     150              :                                      ds->expected_response_code);
     151            0 :     return;
     152              :   }
     153            7 :   if (MHD_HTTP_OK == dr->hr.http_status)
     154              :   {
     155            5 :     if (-1 !=
     156            5 :         TALER_amount_cmp (&dr->details.ok.total_deposited,
     157              :                           &dr->details.ok.purse_value_after_fees))
     158              :     {
     159              :       const struct TALER_TESTING_Command *purse_cmd;
     160              :       const struct TALER_ReserveSignatureP *reserve_sig;
     161              :       const struct TALER_ReservePublicKeyP *reserve_pub;
     162              :       const struct GNUNET_TIME_Timestamp *merge_timestamp;
     163              :       const struct TALER_PurseMergePublicKeyP *merge_pub;
     164              : 
     165            5 :       purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is,
     166              :                                                             ds->purse_ref);
     167            5 :       GNUNET_assert (NULL != purse_cmd);
     168            5 :       if (GNUNET_OK !=
     169            5 :           TALER_TESTING_get_trait_reserve_sig (purse_cmd,
     170              :                                                &reserve_sig))
     171              :       {
     172            0 :         GNUNET_break (0);
     173            0 :         TALER_TESTING_interpreter_fail (ds->is);
     174            0 :         return;
     175              :       }
     176            5 :       if (GNUNET_OK !=
     177            5 :           TALER_TESTING_get_trait_reserve_pub (purse_cmd,
     178              :                                                &reserve_pub))
     179              :       {
     180            0 :         GNUNET_break (0);
     181            0 :         TALER_TESTING_interpreter_fail (ds->is);
     182            0 :         return;
     183              :       }
     184            5 :       if (GNUNET_OK !=
     185            5 :           TALER_TESTING_get_trait_merge_pub (purse_cmd,
     186              :                                              &merge_pub))
     187              :       {
     188            0 :         GNUNET_break (0);
     189            0 :         TALER_TESTING_interpreter_fail (ds->is);
     190            0 :         return;
     191              :       }
     192            5 :       ds->reserve_pub = *reserve_pub;
     193            5 :       if (GNUNET_OK !=
     194            5 :           TALER_TESTING_get_trait_timestamp (purse_cmd,
     195              :                                              0,
     196              :                                              &merge_timestamp))
     197              :       {
     198            0 :         GNUNET_break (0);
     199            0 :         TALER_TESTING_interpreter_fail (ds->is);
     200            0 :         return;
     201              :       }
     202              : 
     203              :       /* Deposits complete, create trait! */
     204            5 :       ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
     205              :       {
     206              :         struct TALER_EXCHANGE_Keys *keys;
     207              :         const struct TALER_EXCHANGE_GlobalFee *gf;
     208              : 
     209            5 :         keys = TALER_TESTING_get_keys (ds->is);
     210            5 :         GNUNET_assert (NULL != keys);
     211            5 :         gf = TALER_EXCHANGE_get_global_fee (keys,
     212              :                                             *merge_timestamp);
     213            5 :         GNUNET_assert (NULL != gf);
     214              : 
     215              :         /* Note: change when flags below changes! */
     216              :         ds->reserve_history.amount
     217            5 :           = dr->details.ok.purse_value_after_fees;
     218              :         if (true)
     219              :         {
     220            5 :           ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse;
     221              :         }
     222              :         else
     223              :         {
     224              :           TALER_amount_set_zero (
     225              :             ds->reserve_history.amount.currency,
     226              :             &ds->reserve_history.details.merge_details.purse_fee);
     227              :         }
     228              :       }
     229              :       ds->reserve_history.details.merge_details.h_contract_terms
     230            5 :         = dr->details.ok.h_contract_terms;
     231              :       ds->reserve_history.details.merge_details.merge_pub
     232            5 :         = *merge_pub;
     233              :       ds->reserve_history.details.merge_details.purse_pub
     234            5 :         = ds->purse_pub;
     235              :       ds->reserve_history.details.merge_details.reserve_sig
     236            5 :         = *reserve_sig;
     237              :       ds->reserve_history.details.merge_details.merge_timestamp
     238            5 :         = *merge_timestamp;
     239              :       ds->reserve_history.details.merge_details.purse_expiration
     240            5 :         = dr->details.ok.purse_expiration;
     241              :       ds->reserve_history.details.merge_details.min_age
     242            5 :         = ds->min_age;
     243              :       ds->reserve_history.details.merge_details.flags
     244            5 :         = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
     245            5 :       ds->purse_complete = true;
     246              :     }
     247              :   }
     248            7 :   TALER_TESTING_interpreter_next (ds->is);
     249              : }
     250              : 
     251              : 
     252              : /**
     253              :  * Run the command.
     254              :  *
     255              :  * @param cls closure.
     256              :  * @param cmd the command to execute.
     257              :  * @param is the interpreter state.
     258              :  */
     259              : static void
     260            7 : deposit_run (void *cls,
     261              :              const struct TALER_TESTING_Command *cmd,
     262              :              struct TALER_TESTING_Interpreter *is)
     263            7 : {
     264            7 :   struct PurseDepositState *ds = cls;
     265            7 :   struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
     266              :   const struct TALER_PurseContractPublicKeyP *purse_pub;
     267              :   const struct TALER_TESTING_Command *purse_cmd;
     268              : 
     269              :   (void) cmd;
     270            7 :   ds->is = is;
     271            7 :   purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
     272              :                                                         ds->purse_ref);
     273            7 :   GNUNET_assert (NULL != purse_cmd);
     274            7 :   if (GNUNET_OK !=
     275            7 :       TALER_TESTING_get_trait_purse_pub (purse_cmd,
     276              :                                          &purse_pub))
     277              :   {
     278            0 :     GNUNET_break (0);
     279            0 :     TALER_TESTING_interpreter_fail (is);
     280            0 :     return;
     281              :   }
     282            7 :   ds->purse_pub = *purse_pub;
     283           14 :   for (unsigned int i = 0; i<ds->num_coin_references; i++)
     284              :   {
     285            7 :     struct Coin *cr = &ds->coin_references[i];
     286            7 :     struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
     287              :     const struct TALER_TESTING_Command *coin_cmd;
     288              :     const struct TALER_CoinSpendPrivateKeyP *coin_priv;
     289            7 :     const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
     290              :     const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
     291              :     const struct TALER_DenominationSignature *denom_pub_sig;
     292              : 
     293            7 :     coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
     294            7 :                                                          cr->command_ref);
     295            7 :     GNUNET_assert (NULL != coin_cmd);
     296            7 :     if ( (GNUNET_OK !=
     297            7 :           TALER_TESTING_get_trait_coin_priv (coin_cmd,
     298              :                                              cr->coin_index,
     299            7 :                                              &coin_priv)) ||
     300              :          (GNUNET_OK !=
     301            7 :           TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
     302              :                                                         cr->coin_index,
     303              :                                                         &age_commitment_proof))
     304            7 :          ||
     305              :          (GNUNET_OK !=
     306            7 :           TALER_TESTING_get_trait_denom_pub (coin_cmd,
     307              :                                              cr->coin_index,
     308            7 :                                              &denom_pub)) ||
     309              :          (GNUNET_OK !=
     310            7 :           TALER_TESTING_get_trait_denom_sig (coin_cmd,
     311              :                                              cr->coin_index,
     312              :                                              &denom_pub_sig)) )
     313              :     {
     314            0 :       GNUNET_break (0);
     315            0 :       TALER_TESTING_interpreter_fail (is);
     316            0 :       return;
     317              :     }
     318            7 :     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
     319              :                                         &cr->coin_pub.eddsa_pub);
     320            7 :     cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
     321            7 :     cr->che.amount = cr->deposit_with_fee;
     322            7 :     cr->che.details.purse_deposit.purse_pub = *purse_pub;
     323              :     cr->che.details.purse_deposit.exchange_base_url
     324            7 :       = TALER_TESTING_get_exchange_url (is);
     325            7 :     TALER_age_commitment_hash (
     326            7 :       &age_commitment_proof->commitment,
     327              :       &cr->che.details.purse_deposit.phac);
     328            7 :     pd->age_commitment_proof = age_commitment_proof;
     329            7 :     pd->denom_sig = *denom_pub_sig;
     330            7 :     pd->coin_priv = *coin_priv;
     331            7 :     pd->amount = cr->deposit_with_fee;
     332            7 :     pd->h_denom_pub = denom_pub->h_key;
     333              :   }
     334              : 
     335            7 :   ds->dh = TALER_EXCHANGE_post_purses_deposit_create (
     336              :     TALER_TESTING_interpreter_get_context (is),
     337              :     TALER_TESTING_get_exchange_url (is),
     338              :     TALER_TESTING_get_keys (is),
     339              :     NULL, /* FIXME #7271: WADs support: purse exchange URL */
     340            7 :     &ds->purse_pub,
     341            7 :     ds->min_age,
     342              :     ds->num_coin_references,
     343              :     deposits);
     344            7 :   if (NULL == ds->dh)
     345              :   {
     346            0 :     GNUNET_break (0);
     347            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     348              :                 "Could not deposit into purse\n");
     349            0 :     TALER_TESTING_interpreter_fail (is);
     350            0 :     return;
     351              :   }
     352            7 :   GNUNET_assert (TALER_EC_NONE ==
     353              :                  TALER_EXCHANGE_post_purses_deposit_start (ds->dh,
     354              :                                                            &deposit_cb,
     355              :                                                            ds));
     356              : }
     357              : 
     358              : 
     359              : /**
     360              :  * Free the state of a "deposit" CMD, and possibly cancel a
     361              :  * pending operation thereof.
     362              :  *
     363              :  * @param cls closure, must be a `struct PurseDepositState`.
     364              :  * @param cmd the command which is being cleaned up.
     365              :  */
     366              : static void
     367            7 : deposit_cleanup (void *cls,
     368              :                  const struct TALER_TESTING_Command *cmd)
     369              : {
     370            7 :   struct PurseDepositState *ds = cls;
     371              : 
     372            7 :   if (NULL != ds->dh)
     373              :   {
     374            0 :     TALER_TESTING_command_incomplete (ds->is,
     375              :                                       cmd->label);
     376            0 :     TALER_EXCHANGE_post_purses_deposit_cancel (ds->dh);
     377            0 :     ds->dh = NULL;
     378              :   }
     379           14 :   for (unsigned int i = 0; i<ds->num_coin_references; i++)
     380            7 :     GNUNET_free (ds->coin_references[i].command_ref);
     381            7 :   GNUNET_free (ds->coin_references);
     382            7 :   GNUNET_free (ds);
     383            7 : }
     384              : 
     385              : 
     386              : /**
     387              :  * Offer internal data from a "deposit" CMD, to other commands.
     388              :  *
     389              :  * @param cls closure.
     390              :  * @param[out] ret result.
     391              :  * @param trait name of the trait.
     392              :  * @param index index number of the object to offer.
     393              :  * @return #GNUNET_OK on success.
     394              :  */
     395              : static enum GNUNET_GenericReturnValue
     396            9 : deposit_traits (void *cls,
     397              :                 const void **ret,
     398              :                 const char *trait,
     399              :                 unsigned int index)
     400              : {
     401            9 :   struct PurseDepositState *ds = cls;
     402              : 
     403            9 :   if (index >= ds->num_coin_references)
     404            1 :     return GNUNET_NO;
     405              :   {
     406            8 :     const struct Coin *co = &ds->coin_references[index];
     407              :     struct TALER_TESTING_Trait traits[] = {
     408              :       /* history entry MUST be first due to response code logic below! */
     409            8 :       TALER_TESTING_make_trait_reserve_history (0,
     410            8 :                                                 &ds->reserve_history),
     411            8 :       TALER_TESTING_make_trait_coin_history (index,
     412              :                                              &co->che),
     413            8 :       TALER_TESTING_make_trait_coin_pub (index,
     414              :                                          &co->coin_pub),
     415            8 :       TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
     416            8 :       TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
     417            8 :       TALER_TESTING_trait_end ()
     418              :     };
     419              : 
     420            8 :     return TALER_TESTING_get_trait (ds->purse_complete
     421              :                                     ? &traits[0]   /* we have reserve history */
     422              :                                     : &traits[1],  /* skip reserve history */
     423              :                                     ret,
     424              :                                     trait,
     425              :                                     index);
     426              :   }
     427              : }
     428              : 
     429              : 
     430              : struct TALER_TESTING_Command
     431            7 : TALER_TESTING_cmd_purse_deposit_coins (
     432              :   const char *label,
     433              :   unsigned int expected_http_status,
     434              :   uint8_t min_age,
     435              :   const char *purse_ref,
     436              :   ...)
     437              : {
     438              :   struct PurseDepositState *ds;
     439              : 
     440            7 :   ds = GNUNET_new (struct PurseDepositState);
     441            7 :   ds->expected_response_code = expected_http_status;
     442            7 :   ds->min_age = min_age;
     443            7 :   ds->purse_ref = purse_ref;
     444              :   {
     445              :     va_list ap;
     446              :     unsigned int i;
     447              :     const char *ref;
     448              :     const char *val;
     449              : 
     450            7 :     va_start (ap, purse_ref);
     451           21 :     while (NULL != (va_arg (ap, const char *)))
     452           14 :       ds->num_coin_references++;
     453            7 :     va_end (ap);
     454            7 :     GNUNET_assert (0 == (ds->num_coin_references % 2));
     455            7 :     ds->num_coin_references /= 2;
     456            7 :     ds->coin_references = GNUNET_new_array (ds->num_coin_references,
     457              :                                             struct Coin);
     458            7 :     i = 0;
     459            7 :     va_start (ap, purse_ref);
     460           14 :     while (NULL != (ref = va_arg (ap, const char *)))
     461              :     {
     462            7 :       struct Coin *c = &ds->coin_references[i++];
     463              : 
     464            7 :       GNUNET_assert (NULL != (val = va_arg (ap,
     465              :                                             const char *)));
     466            7 :       GNUNET_assert (GNUNET_OK ==
     467              :                      TALER_TESTING_parse_coin_reference (
     468              :                        ref,
     469              :                        &c->command_ref,
     470              :                        &c->coin_index));
     471            7 :       GNUNET_assert (GNUNET_OK ==
     472              :                      TALER_string_to_amount (val,
     473              :                                              &c->deposit_with_fee));
     474              :     }
     475            7 :     va_end (ap);
     476              :   }
     477              :   {
     478            7 :     struct TALER_TESTING_Command cmd = {
     479              :       .cls = ds,
     480              :       .label = label,
     481              :       .run = &deposit_run,
     482              :       .cleanup = &deposit_cleanup,
     483              :       .traits = &deposit_traits
     484              :     };
     485              : 
     486            7 :     return cmd;
     487              :   }
     488              : }
     489              : 
     490              : 
     491              : /* end of testing_api_cmd_purse_deposit.c */
        

Generated by: LCOV version 2.0-1