LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_purse_create_deposit.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 105 122 86.1 %
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 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_create_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             :    * index of the specific coin in the traits of @e command_ref.
      43             :    */
      44             :   unsigned int coin_index;
      45             : 
      46             :   /**
      47             :    * Public key of the deposited coin.
      48             :    */
      49             :   struct TALER_CoinSpendPublicKeyP coin_pub;
      50             : 
      51             :   /**
      52             :    * Amount to deposit (with fee).
      53             :    */
      54             :   struct TALER_Amount deposit_with_fee;
      55             : 
      56             :   /**
      57             :    * Entry in the coin's history generated by this operation.
      58             :    */
      59             :   struct TALER_EXCHANGE_CoinHistoryEntry che;
      60             : 
      61             : };
      62             : 
      63             : 
      64             : /**
      65             :  * State for a "purse create deposit" CMD.
      66             :  */
      67             : struct PurseCreateDepositState
      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             :    * JSON string describing what a proposal is about.
      82             :    */
      83             :   json_t *contract_terms;
      84             : 
      85             :   /**
      86             :    * Purse expiration time.
      87             :    */
      88             :   struct GNUNET_TIME_Timestamp purse_expiration;
      89             : 
      90             :   /**
      91             :    * Relative purse expiration time.
      92             :    */
      93             :   struct GNUNET_TIME_Relative rel_expiration;
      94             : 
      95             :   /**
      96             :    * Set (by the interpreter) to a fresh private key.  This
      97             :    * key will be used to create the purse.
      98             :    */
      99             :   struct TALER_PurseContractPrivateKeyP purse_priv;
     100             : 
     101             :   /**
     102             :    * Set (by the interpreter) to a fresh private key.  This
     103             :    * key will be used to merge the purse.
     104             :    */
     105             :   struct TALER_PurseMergePrivateKeyP merge_priv;
     106             : 
     107             :   /**
     108             :    * Set (by the interpreter) to a fresh private key.  This
     109             :    * key will be used to decrypt the contract.
     110             :    */
     111             :   struct TALER_ContractDiffiePrivateP contract_priv;
     112             : 
     113             :   /**
     114             :    * Signing key used by the exchange to sign the
     115             :    * deposit confirmation.
     116             :    */
     117             :   struct TALER_ExchangePublicKeyP exchange_pub;
     118             : 
     119             :   /**
     120             :    * Signature from the exchange on the
     121             :    * deposit confirmation.
     122             :    */
     123             :   struct TALER_ExchangeSignatureP exchange_sig;
     124             : 
     125             :   /**
     126             :    * Set (by the interpreter) to a public key corresponding
     127             :    * to @e purse_priv.
     128             :    */
     129             :   struct TALER_PurseContractPublicKeyP purse_pub;
     130             : 
     131             :   /**
     132             :    * PurseCreateDeposit handle while operation is running.
     133             :    */
     134             :   struct TALER_EXCHANGE_PurseCreateDepositHandle *dh;
     135             : 
     136             :   /**
     137             :    * Interpreter state.
     138             :    */
     139             :   struct TALER_TESTING_Interpreter *is;
     140             : 
     141             :   /**
     142             :    * Expected HTTP response code.
     143             :    */
     144             :   unsigned int expected_response_code;
     145             : 
     146             :   /**
     147             :    * Length of the @e coin_references array.
     148             :    */
     149             :   unsigned int num_coin_references;
     150             : 
     151             :   /**
     152             :    * Should we upload the contract?
     153             :    */
     154             :   bool upload_contract;
     155             : 
     156             : };
     157             : 
     158             : 
     159             : /**
     160             :  * Callback to analyze the /purses/$PID/create response, just used to check if
     161             :  * the response code is acceptable.
     162             :  *
     163             :  * @param cls closure.
     164             :  * @param dr deposit response details
     165             :  */
     166             : static void
     167          11 : deposit_cb (void *cls,
     168             :             const struct TALER_EXCHANGE_PurseCreateDepositResponse *dr)
     169             : {
     170          11 :   struct PurseCreateDepositState *ds = cls;
     171             : 
     172          11 :   ds->dh = NULL;
     173          11 :   if (ds->expected_response_code != dr->hr.http_status)
     174             :   {
     175           0 :     TALER_TESTING_unexpected_status (ds->is,
     176             :                                      dr->hr.http_status,
     177             :                                      ds->expected_response_code);
     178           0 :     return;
     179             :   }
     180          11 :   if (MHD_HTTP_OK == dr->hr.http_status)
     181             :   {
     182           9 :     ds->exchange_pub = dr->details.ok.exchange_pub;
     183           9 :     ds->exchange_sig = dr->details.ok.exchange_sig;
     184             :   }
     185          11 :   TALER_TESTING_interpreter_next (ds->is);
     186             : }
     187             : 
     188             : 
     189             : /**
     190             :  * Run the command.
     191             :  *
     192             :  * @param cls closure.
     193             :  * @param cmd the command to execute.
     194             :  * @param is the interpreter state.
     195             :  */
     196             : static void
     197          11 : deposit_run (void *cls,
     198             :              const struct TALER_TESTING_Command *cmd,
     199             :              struct TALER_TESTING_Interpreter *is)
     200          11 : {
     201          11 :   struct PurseCreateDepositState *ds = cls;
     202          11 :   struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
     203             : 
     204             :   (void) cmd;
     205          11 :   ds->is = is;
     206          11 :   GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv);
     207          11 :   GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv);
     208          11 :   GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv);
     209          11 :   GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv,
     210             :                                       &ds->purse_pub.eddsa_pub);
     211             : 
     212          22 :   for (unsigned int i = 0; i<ds->num_coin_references; i++)
     213             :   {
     214          11 :     struct Coin *cr = &ds->coin_references[i];
     215          11 :     struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
     216             :     const struct TALER_TESTING_Command *coin_cmd;
     217             :     const struct TALER_CoinSpendPrivateKeyP *coin_priv;
     218          11 :     const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
     219             :     const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
     220             :     const struct TALER_DenominationSignature *denom_pub_sig;
     221             : 
     222          11 :     coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
     223          11 :                                                          cr->command_ref);
     224          11 :     if (NULL == coin_cmd)
     225             :     {
     226           0 :       GNUNET_break (0);
     227           0 :       TALER_TESTING_interpreter_fail (is);
     228           0 :       return;
     229             :     }
     230             : 
     231          11 :     if ( (GNUNET_OK !=
     232          11 :           TALER_TESTING_get_trait_coin_priv (coin_cmd,
     233             :                                              cr->coin_index,
     234          11 :                                              &coin_priv)) ||
     235             :          (GNUNET_OK !=
     236          11 :           TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
     237             :                                                         cr->coin_index,
     238             :                                                         &age_commitment_proof))
     239          11 :          ||
     240             :          (GNUNET_OK !=
     241          11 :           TALER_TESTING_get_trait_denom_pub (coin_cmd,
     242             :                                              cr->coin_index,
     243          11 :                                              &denom_pub)) ||
     244             :          (GNUNET_OK !=
     245          11 :           TALER_TESTING_get_trait_denom_sig (coin_cmd,
     246             :                                              cr->coin_index,
     247             :                                              &denom_pub_sig)) )
     248             :     {
     249           0 :       GNUNET_break (0);
     250           0 :       TALER_TESTING_interpreter_fail (is);
     251           0 :       return;
     252             :     }
     253          11 :     pd->age_commitment_proof = age_commitment_proof;
     254          11 :     pd->denom_sig = *denom_pub_sig;
     255          11 :     pd->coin_priv = *coin_priv;
     256          11 :     pd->amount = cr->deposit_with_fee;
     257          11 :     pd->h_denom_pub = denom_pub->h_key;
     258          11 :     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
     259             :                                         &cr->coin_pub.eddsa_pub);
     260          11 :     cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
     261          11 :     cr->che.amount = cr->deposit_with_fee;
     262          11 :     GNUNET_CRYPTO_eddsa_key_get_public (
     263          11 :       &ds->purse_priv.eddsa_priv,
     264             :       &cr->che.details.purse_deposit.purse_pub.eddsa_pub);
     265             :     cr->che.details.purse_deposit.exchange_base_url
     266          11 :       = TALER_TESTING_get_exchange_url (is);
     267          11 :     TALER_age_commitment_hash (
     268          11 :       &age_commitment_proof->commitment,
     269             :       &cr->che.details.purse_deposit.phac);
     270             :   }
     271             : 
     272             :   ds->purse_expiration =
     273          11 :     GNUNET_TIME_absolute_to_timestamp (
     274             :       GNUNET_TIME_relative_to_absolute (ds->rel_expiration));
     275          11 :   GNUNET_assert (0 ==
     276             :                  json_object_set_new (
     277             :                    ds->contract_terms,
     278             :                    "pay_deadline",
     279             :                    GNUNET_JSON_from_timestamp (ds->purse_expiration)));
     280          11 :   ds->dh = TALER_EXCHANGE_purse_create_with_deposit (
     281             :     TALER_TESTING_interpreter_get_context (is),
     282             :     TALER_TESTING_get_exchange_url (is),
     283             :     TALER_TESTING_get_keys (is),
     284          11 :     &ds->purse_priv,
     285          11 :     &ds->merge_priv,
     286          11 :     &ds->contract_priv,
     287          11 :     ds->contract_terms,
     288             :     ds->num_coin_references,
     289             :     deposits,
     290          11 :     ds->upload_contract,
     291             :     &deposit_cb,
     292             :     ds);
     293          11 :   if (NULL == ds->dh)
     294             :   {
     295           0 :     GNUNET_break (0);
     296           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     297             :                 "Could not create purse with deposit\n");
     298           0 :     TALER_TESTING_interpreter_fail (is);
     299           0 :     return;
     300             :   }
     301             : }
     302             : 
     303             : 
     304             : /**
     305             :  * Free the state of a "deposit" CMD, and possibly cancel a
     306             :  * pending operation thereof.
     307             :  *
     308             :  * @param cls closure, must be a `struct PurseCreateDepositState`.
     309             :  * @param cmd the command which is being cleaned up.
     310             :  */
     311             : static void
     312          11 : deposit_cleanup (void *cls,
     313             :                  const struct TALER_TESTING_Command *cmd)
     314             : {
     315          11 :   struct PurseCreateDepositState *ds = cls;
     316             : 
     317          11 :   if (NULL != ds->dh)
     318             :   {
     319           0 :     TALER_TESTING_command_incomplete (ds->is,
     320             :                                       cmd->label);
     321           0 :     TALER_EXCHANGE_purse_create_with_deposit_cancel (ds->dh);
     322           0 :     ds->dh = NULL;
     323             :   }
     324          22 :   for (unsigned int i = 0; i<ds->num_coin_references; i++)
     325          11 :     GNUNET_free (ds->coin_references[i].command_ref);
     326          11 :   json_decref (ds->contract_terms);
     327          11 :   GNUNET_free (ds->coin_references);
     328          11 :   GNUNET_free (ds);
     329          11 : }
     330             : 
     331             : 
     332             : /**
     333             :  * Offer internal data from a "deposit" CMD, to other commands.
     334             :  *
     335             :  * @param cls closure.
     336             :  * @param[out] ret result.
     337             :  * @param trait name of the trait.
     338             :  * @param index index number of the object to offer.
     339             :  * @return #GNUNET_OK on success.
     340             :  */
     341             : static enum GNUNET_GenericReturnValue
     342          32 : deposit_traits (void *cls,
     343             :                 const void **ret,
     344             :                 const char *trait,
     345             :                 unsigned int index)
     346             : {
     347          32 :   struct PurseCreateDepositState *ds = cls;
     348          32 :   if (index >= ds->num_coin_references)
     349           2 :     return GNUNET_NO;
     350             : 
     351             :   {
     352          30 :     const struct Coin *co = &ds->coin_references[index];
     353             :     struct TALER_TESTING_Trait traits[] = {
     354          30 :       TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
     355          30 :       TALER_TESTING_make_trait_contract_priv (&ds->contract_priv),
     356          30 :       TALER_TESTING_make_trait_coin_history (index,
     357             :                                              &co->che),
     358          30 :       TALER_TESTING_make_trait_coin_pub (index,
     359             :                                          &co->coin_pub),
     360          30 :       TALER_TESTING_make_trait_purse_priv (&ds->purse_priv),
     361          30 :       TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
     362          30 :       TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
     363          30 :       TALER_TESTING_make_trait_deposit_amount (0,
     364          30 :                                                &ds->target_amount),
     365          30 :       TALER_TESTING_make_trait_timestamp (index,
     366          30 :                                           &ds->purse_expiration),
     367          30 :       TALER_TESTING_trait_end ()
     368             :     };
     369             : 
     370          30 :     return TALER_TESTING_get_trait (traits,
     371             :                                     ret,
     372             :                                     trait,
     373             :                                     index);
     374             :   }
     375             : }
     376             : 
     377             : 
     378             : struct TALER_TESTING_Command
     379          11 : TALER_TESTING_cmd_purse_create_with_deposit (
     380             :   const char *label,
     381             :   unsigned int expected_http_status,
     382             :   const char *contract_terms,
     383             :   bool upload_contract,
     384             :   struct GNUNET_TIME_Relative purse_expiration,
     385             :   ...)
     386             : {
     387             :   struct PurseCreateDepositState *ds;
     388             : 
     389          11 :   ds = GNUNET_new (struct PurseCreateDepositState);
     390          11 :   ds->rel_expiration = purse_expiration;
     391          11 :   ds->upload_contract = upload_contract;
     392          11 :   ds->expected_response_code = expected_http_status;
     393          11 :   ds->contract_terms = json_loads (contract_terms,
     394             :                                    JSON_REJECT_DUPLICATES,
     395             :                                    NULL);
     396          11 :   if (NULL == ds->contract_terms)
     397             :   {
     398           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     399             :                 "Failed to parse contract terms `%s' for CMD `%s'\n",
     400             :                 contract_terms,
     401             :                 label);
     402           0 :     GNUNET_assert (0);
     403             :   }
     404             :   {
     405             :     va_list ap;
     406             :     unsigned int i;
     407             :     const char *ref;
     408             :     const char *val;
     409             : 
     410          11 :     va_start (ap, purse_expiration);
     411          33 :     while (NULL != (va_arg (ap, const char *)))
     412          22 :       ds->num_coin_references++;
     413          11 :     va_end (ap);
     414          11 :     GNUNET_assert (0 == (ds->num_coin_references % 2));
     415          11 :     ds->num_coin_references /= 2;
     416          11 :     ds->coin_references = GNUNET_new_array (ds->num_coin_references,
     417             :                                             struct Coin);
     418          11 :     i = 0;
     419          11 :     va_start (ap, purse_expiration);
     420          22 :     while (NULL != (ref = va_arg (ap, const char *)))
     421             :     {
     422          11 :       struct Coin *c = &ds->coin_references[i++];
     423             : 
     424          11 :       GNUNET_assert (NULL != (val = va_arg (ap, const char *)));
     425          11 :       GNUNET_assert (GNUNET_OK ==
     426             :                      TALER_TESTING_parse_coin_reference (
     427             :                        ref,
     428             :                        &c->command_ref,
     429             :                        &c->coin_index));
     430          11 :       GNUNET_assert (GNUNET_OK ==
     431             :                      TALER_string_to_amount (val,
     432             :                                              &c->deposit_with_fee));
     433             :     }
     434          11 :     va_end (ap);
     435             :   }
     436             :   {
     437          11 :     struct TALER_TESTING_Command cmd = {
     438             :       .cls = ds,
     439             :       .label = label,
     440             :       .run = &deposit_run,
     441             :       .cleanup = &deposit_cleanup,
     442             :       .traits = &deposit_traits
     443             :     };
     444             : 
     445          11 :     return cmd;
     446             :   }
     447             : }
     448             : 
     449             : 
     450             : /* end of testing_api_cmd_purse_create_deposit.c */

Generated by: LCOV version 1.16