LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_purse_deposit.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 119 146 81.5 %
Date: 2025-06-05 21:03:14 Functions: 5 5 100.0 %

          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 "platform.h"
      25             : #include "taler_json_lib.h"
      26             : #include <gnunet/gnunet_curl_lib.h>
      27             : #include "taler_testing_lib.h"
      28             : #include "taler_signatures.h"
      29             : #include "backoff.h"
      30             : 
      31             : /**
      32             :  * Information we keep per deposited coin.
      33             :  */
      34             : struct Coin
      35             : {
      36             :   /**
      37             :    * Reference to the respective command.
      38             :    */
      39             :   char *command_ref;
      40             : 
      41             :   /**
      42             :    * Entry in the coin's history generated by this operation.
      43             :    */
      44             :   struct TALER_EXCHANGE_CoinHistoryEntry che;
      45             : 
      46             :   /**
      47             :    * Public key of the deposited coin.
      48             :    */
      49             :   struct TALER_CoinSpendPublicKeyP coin_pub;
      50             : 
      51             :   /**
      52             :    * index of the specific coin in the traits of @e command_ref.
      53             :    */
      54             :   unsigned int coin_index;
      55             : 
      56             :   /**
      57             :    * Amount to deposit (with fee).
      58             :    */
      59             :   struct TALER_Amount deposit_with_fee;
      60             : 
      61             : };
      62             : 
      63             : 
      64             : /**
      65             :  * State for a "purse deposit" CMD.
      66             :  */
      67             : struct PurseDepositState
      68             : {
      69             : 
      70             :   /**
      71             :    * Total purse target amount without fees.
      72             :    */
      73             :   struct TALER_Amount target_amount;
      74             : 
      75             :   /**
      76             :    * Reference to any command that is able to provide a coin.
      77             :    */
      78             :   struct Coin *coin_references;
      79             : 
      80             :   /**
      81             :    * The purse's public key.
      82             :    */
      83             :   struct TALER_PurseContractPublicKeyP purse_pub;
      84             : 
      85             :   /**
      86             :    * The reserve we are being deposited into.
      87             :    * Set as a trait once we know the reserve.
      88             :    */
      89             :   struct TALER_ReservePublicKeyP reserve_pub;
      90             : 
      91             :   /**
      92             :    * PurseDeposit handle while operation is running.
      93             :    */
      94             :   struct TALER_EXCHANGE_PurseDepositHandle *dh;
      95             : 
      96             :   /**
      97             :    * Interpreter state.
      98             :    */
      99             :   struct TALER_TESTING_Interpreter *is;
     100             : 
     101             :   /**
     102             :    * Reference to the command that established the purse.
     103             :    */
     104             :   const char *purse_ref;
     105             : 
     106             :   /**
     107             :    * Reserve history entry that corresponds to this operation.
     108             :    * Will be of type #TALER_EXCHANGE_RTT_MERGE.
     109             :    * Only valid if @e purse_complete is true.
     110             :    */
     111             :   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
     112             :   /**
     113             :    * Expected HTTP response code.
     114             :    */
     115             :   unsigned int expected_response_code;
     116             : 
     117             :   /**
     118             :    * Length of the @e coin_references array.
     119             :    */
     120             :   unsigned int num_coin_references;
     121             : 
     122             :   /**
     123             :    * Minimum age to apply to all deposits.
     124             :    */
     125             :   uint8_t min_age;
     126             : 
     127             :   /**
     128             :    * Set to true if this deposit filled the purse.
     129             :    */
     130             :   bool purse_complete;
     131             : };
     132             : 
     133             : 
     134             : /**
     135             :  * Callback to analyze the /purses/$PID/deposit response, just used to check if
     136             :  * the response code is acceptable.
     137             :  *
     138             :  * @param cls closure.
     139             :  * @param dr deposit response details
     140             :  */
     141             : static void
     142           7 : deposit_cb (void *cls,
     143             :             const struct TALER_EXCHANGE_PurseDepositResponse *dr)
     144             : {
     145           7 :   struct PurseDepositState *ds = cls;
     146             : 
     147           7 :   ds->dh = NULL;
     148           7 :   if (ds->expected_response_code != dr->hr.http_status)
     149             :   {
     150           0 :     TALER_TESTING_unexpected_status (ds->is,
     151             :                                      dr->hr.http_status,
     152             :                                      ds->expected_response_code);
     153           0 :     return;
     154             :   }
     155           7 :   if (MHD_HTTP_OK == dr->hr.http_status)
     156             :   {
     157           5 :     if (-1 !=
     158           5 :         TALER_amount_cmp (&dr->details.ok.total_deposited,
     159             :                           &dr->details.ok.purse_value_after_fees))
     160             :     {
     161             :       const struct TALER_TESTING_Command *purse_cmd;
     162             :       const struct TALER_ReserveSignatureP *reserve_sig;
     163             :       const struct TALER_ReservePublicKeyP *reserve_pub;
     164             :       const struct GNUNET_TIME_Timestamp *merge_timestamp;
     165             :       const struct TALER_PurseMergePublicKeyP *merge_pub;
     166             : 
     167           5 :       purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is,
     168             :                                                             ds->purse_ref);
     169           5 :       GNUNET_assert (NULL != purse_cmd);
     170           5 :       if (GNUNET_OK !=
     171           5 :           TALER_TESTING_get_trait_reserve_sig (purse_cmd,
     172             :                                                &reserve_sig))
     173             :       {
     174           0 :         GNUNET_break (0);
     175           0 :         TALER_TESTING_interpreter_fail (ds->is);
     176           0 :         return;
     177             :       }
     178           5 :       if (GNUNET_OK !=
     179           5 :           TALER_TESTING_get_trait_reserve_pub (purse_cmd,
     180             :                                                &reserve_pub))
     181             :       {
     182           0 :         GNUNET_break (0);
     183           0 :         TALER_TESTING_interpreter_fail (ds->is);
     184           0 :         return;
     185             :       }
     186           5 :       if (GNUNET_OK !=
     187           5 :           TALER_TESTING_get_trait_merge_pub (purse_cmd,
     188             :                                              &merge_pub))
     189             :       {
     190           0 :         GNUNET_break (0);
     191           0 :         TALER_TESTING_interpreter_fail (ds->is);
     192           0 :         return;
     193             :       }
     194           5 :       ds->reserve_pub = *reserve_pub;
     195           5 :       if (GNUNET_OK !=
     196           5 :           TALER_TESTING_get_trait_timestamp (purse_cmd,
     197             :                                              0,
     198             :                                              &merge_timestamp))
     199             :       {
     200           0 :         GNUNET_break (0);
     201           0 :         TALER_TESTING_interpreter_fail (ds->is);
     202           0 :         return;
     203             :       }
     204             : 
     205             :       /* Deposits complete, create trait! */
     206           5 :       ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
     207             :       {
     208             :         struct TALER_EXCHANGE_Keys *keys;
     209             :         const struct TALER_EXCHANGE_GlobalFee *gf;
     210             : 
     211           5 :         keys = TALER_TESTING_get_keys (ds->is);
     212           5 :         GNUNET_assert (NULL != keys);
     213           5 :         gf = TALER_EXCHANGE_get_global_fee (keys,
     214             :                                             *merge_timestamp);
     215           5 :         GNUNET_assert (NULL != gf);
     216             : 
     217             :         /* Note: change when flags below changes! */
     218             :         ds->reserve_history.amount
     219           5 :           = dr->details.ok.purse_value_after_fees;
     220             :         if (true)
     221             :         {
     222           5 :           ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse;
     223             :         }
     224             :         else
     225             :         {
     226             :           TALER_amount_set_zero (
     227             :             ds->reserve_history.amount.currency,
     228             :             &ds->reserve_history.details.merge_details.purse_fee);
     229             :         }
     230             :       }
     231             :       ds->reserve_history.details.merge_details.h_contract_terms
     232           5 :         = dr->details.ok.h_contract_terms;
     233             :       ds->reserve_history.details.merge_details.merge_pub
     234           5 :         = *merge_pub;
     235             :       ds->reserve_history.details.merge_details.purse_pub
     236           5 :         = ds->purse_pub;
     237             :       ds->reserve_history.details.merge_details.reserve_sig
     238           5 :         = *reserve_sig;
     239             :       ds->reserve_history.details.merge_details.merge_timestamp
     240           5 :         = *merge_timestamp;
     241             :       ds->reserve_history.details.merge_details.purse_expiration
     242           5 :         = dr->details.ok.purse_expiration;
     243             :       ds->reserve_history.details.merge_details.min_age
     244           5 :         = ds->min_age;
     245             :       ds->reserve_history.details.merge_details.flags
     246           5 :         = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
     247           5 :       ds->purse_complete = true;
     248             :     }
     249             :   }
     250           7 :   TALER_TESTING_interpreter_next (ds->is);
     251             : }
     252             : 
     253             : 
     254             : /**
     255             :  * Run the command.
     256             :  *
     257             :  * @param cls closure.
     258             :  * @param cmd the command to execute.
     259             :  * @param is the interpreter state.
     260             :  */
     261             : static void
     262           7 : deposit_run (void *cls,
     263             :              const struct TALER_TESTING_Command *cmd,
     264             :              struct TALER_TESTING_Interpreter *is)
     265           7 : {
     266           7 :   struct PurseDepositState *ds = cls;
     267           7 :   struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
     268             :   const struct TALER_PurseContractPublicKeyP *purse_pub;
     269             :   const struct TALER_TESTING_Command *purse_cmd;
     270             : 
     271             :   (void) cmd;
     272           7 :   ds->is = is;
     273           7 :   purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
     274             :                                                         ds->purse_ref);
     275           7 :   GNUNET_assert (NULL != purse_cmd);
     276           7 :   if (GNUNET_OK !=
     277           7 :       TALER_TESTING_get_trait_purse_pub (purse_cmd,
     278             :                                          &purse_pub))
     279             :   {
     280           0 :     GNUNET_break (0);
     281           0 :     TALER_TESTING_interpreter_fail (is);
     282           0 :     return;
     283             :   }
     284           7 :   ds->purse_pub = *purse_pub;
     285          14 :   for (unsigned int i = 0; i<ds->num_coin_references; i++)
     286             :   {
     287           7 :     struct Coin *cr = &ds->coin_references[i];
     288           7 :     struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
     289             :     const struct TALER_TESTING_Command *coin_cmd;
     290             :     const struct TALER_CoinSpendPrivateKeyP *coin_priv;
     291           7 :     const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
     292             :     const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
     293             :     const struct TALER_DenominationSignature *denom_pub_sig;
     294             : 
     295           7 :     coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
     296           7 :                                                          cr->command_ref);
     297           7 :     GNUNET_assert (NULL != coin_cmd);
     298           7 :     if ( (GNUNET_OK !=
     299           7 :           TALER_TESTING_get_trait_coin_priv (coin_cmd,
     300             :                                              cr->coin_index,
     301           7 :                                              &coin_priv)) ||
     302             :          (GNUNET_OK !=
     303           7 :           TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
     304             :                                                         cr->coin_index,
     305             :                                                         &age_commitment_proof))
     306           7 :          ||
     307             :          (GNUNET_OK !=
     308           7 :           TALER_TESTING_get_trait_denom_pub (coin_cmd,
     309             :                                              cr->coin_index,
     310           7 :                                              &denom_pub)) ||
     311             :          (GNUNET_OK !=
     312           7 :           TALER_TESTING_get_trait_denom_sig (coin_cmd,
     313             :                                              cr->coin_index,
     314             :                                              &denom_pub_sig)) )
     315             :     {
     316           0 :       GNUNET_break (0);
     317           0 :       TALER_TESTING_interpreter_fail (is);
     318           0 :       return;
     319             :     }
     320           7 :     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
     321             :                                         &cr->coin_pub.eddsa_pub);
     322           7 :     cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
     323           7 :     cr->che.amount = cr->deposit_with_fee;
     324           7 :     cr->che.details.purse_deposit.purse_pub = *purse_pub;
     325             :     cr->che.details.purse_deposit.exchange_base_url
     326           7 :       = TALER_TESTING_get_exchange_url (is);
     327           7 :     TALER_age_commitment_hash (
     328           7 :       &age_commitment_proof->commitment,
     329             :       &cr->che.details.purse_deposit.phac);
     330           7 :     pd->age_commitment_proof = age_commitment_proof;
     331           7 :     pd->denom_sig = *denom_pub_sig;
     332           7 :     pd->coin_priv = *coin_priv;
     333           7 :     pd->amount = cr->deposit_with_fee;
     334           7 :     pd->h_denom_pub = denom_pub->h_key;
     335             :   }
     336             : 
     337           7 :   ds->dh = TALER_EXCHANGE_purse_deposit (
     338             :     TALER_TESTING_interpreter_get_context (is),
     339             :     TALER_TESTING_get_exchange_url (is),
     340             :     TALER_TESTING_get_keys (is),
     341             :     NULL, /* FIXME #7271: WADs support: purse exchange URL */
     342           7 :     &ds->purse_pub,
     343           7 :     ds->min_age,
     344             :     ds->num_coin_references,
     345             :     deposits,
     346             :     &deposit_cb,
     347             :     ds);
     348           7 :   if (NULL == ds->dh)
     349             :   {
     350           0 :     GNUNET_break (0);
     351           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     352             :                 "Could not deposit into purse\n");
     353           0 :     TALER_TESTING_interpreter_fail (is);
     354           0 :     return;
     355             :   }
     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_purse_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 1.16