exchange

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

testing_api_cmd_reserve_purse.c (10874B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022, 2024 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_reserve_purse.c
     21  * @brief command for testing /reserves/$PID/purse
     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 /**
     31  * State for a "purse create with merge" CMD.
     32  */
     33 struct ReservePurseState
     34 {
     35 
     36   /**
     37    * Merge time (local time when the command was
     38    * executed).
     39    */
     40   struct GNUNET_TIME_Timestamp merge_timestamp;
     41 
     42   /**
     43    * Account (reserve) private key.
     44    */
     45   union TALER_AccountPrivateKeyP account_priv;
     46 
     47   /**
     48    * Account (reserve) public key.
     49    */
     50   union TALER_AccountPublicKeyP account_pub;
     51 
     52   /**
     53    * Reserve signature generated for the request
     54    * (client-side).
     55    */
     56   struct TALER_ReserveSignatureP reserve_sig;
     57 
     58   /**
     59    * Private key of the purse.
     60    */
     61   struct TALER_PurseContractPrivateKeyP purse_priv;
     62 
     63   /**
     64    * Public key of the purse.
     65    */
     66   struct TALER_PurseContractPublicKeyP purse_pub;
     67 
     68   /**
     69    * Private key with the merge capability.
     70    */
     71   struct TALER_PurseMergePrivateKeyP merge_priv;
     72 
     73   /**
     74    * Public key of the merge capability.
     75    */
     76   struct TALER_PurseMergePublicKeyP merge_pub;
     77 
     78   /**
     79    * Private key to decrypt the contract.
     80    */
     81   struct TALER_ContractDiffiePrivateP contract_priv;
     82 
     83   /**
     84    * Handle while operation is running.
     85    */
     86   struct TALER_EXCHANGE_PostReservesPurseHandle *dh;
     87 
     88   /**
     89    * When will the purse expire?
     90    */
     91   struct GNUNET_TIME_Relative expiration_rel;
     92 
     93   /**
     94    * When will the purse expire?
     95    */
     96   struct GNUNET_TIME_Timestamp purse_expiration;
     97 
     98   /**
     99    * Hash of the payto://-URI for the reserve we are
    100    * merging into.
    101    */
    102   struct TALER_NormalizedPaytoHashP h_payto;
    103 
    104   /**
    105    * Set to the KYC requirement row *if* the exchange replied with
    106    * a request for KYC.
    107    */
    108   uint64_t requirement_row;
    109 
    110   /**
    111    * Contract terms for the purse.
    112    */
    113   json_t *contract_terms;
    114 
    115   /**
    116    * Reference to the reserve, or NULL (!).
    117    */
    118   const char *reserve_ref;
    119 
    120   /**
    121    * Interpreter state.
    122    */
    123   struct TALER_TESTING_Interpreter *is;
    124 
    125   /**
    126    * Expected HTTP response code.
    127    */
    128   unsigned int expected_response_code;
    129 
    130   /**
    131    * True to pay the purse fee.
    132    */
    133   bool pay_purse_fee;
    134 };
    135 
    136 
    137 /**
    138  * Callback to analyze the /reserves/$PID/purse response, just used to check if
    139  * the response code is acceptable.
    140  *
    141  * @param cls closure.
    142  * @param dr purse response details
    143  */
    144 static void
    145 purse_cb (void *cls,
    146           const struct TALER_EXCHANGE_PostReservesPurseResponse *dr)
    147 {
    148   struct ReservePurseState *ds = cls;
    149 
    150   ds->dh = NULL;
    151   ds->reserve_sig = *dr->reserve_sig;
    152   if (ds->expected_response_code != dr->hr.http_status)
    153   {
    154     TALER_TESTING_unexpected_status (ds->is,
    155                                      dr->hr.http_status,
    156                                      ds->expected_response_code);
    157     return;
    158   }
    159   switch (dr->hr.http_status)
    160   {
    161   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    162     /* KYC required */
    163     ds->requirement_row =
    164       dr->details.unavailable_for_legal_reasons.requirement_row;
    165     GNUNET_break (0 ==
    166                   GNUNET_memcmp (
    167                     &ds->h_payto,
    168                     &dr->details.unavailable_for_legal_reasons.h_payto));
    169     break;
    170   }
    171   TALER_TESTING_interpreter_next (ds->is);
    172 }
    173 
    174 
    175 /**
    176  * Run the command.
    177  *
    178  * @param cls closure.
    179  * @param cmd the command to execute.
    180  * @param is the interpreter state.
    181  */
    182 static void
    183 purse_run (void *cls,
    184            const struct TALER_TESTING_Command *cmd,
    185            struct TALER_TESTING_Interpreter *is)
    186 {
    187   struct ReservePurseState *ds = cls;
    188   const struct TALER_ReservePrivateKeyP *reserve_priv;
    189   const struct TALER_TESTING_Command *ref;
    190 
    191   (void) cmd;
    192   ds->is = is;
    193   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
    194                                                   ds->reserve_ref);
    195   GNUNET_assert (NULL != ref);
    196   if (GNUNET_OK !=
    197       TALER_TESTING_get_trait_reserve_priv (ref,
    198                                             &reserve_priv))
    199   {
    200     GNUNET_break (0);
    201     TALER_TESTING_interpreter_fail (ds->is);
    202     return;
    203   }
    204   ds->account_priv.reserve_priv = *reserve_priv;
    205   GNUNET_CRYPTO_eddsa_key_create (
    206     &ds->purse_priv.eddsa_priv);
    207   GNUNET_CRYPTO_eddsa_key_get_public (
    208     &ds->purse_priv.eddsa_priv,
    209     &ds->purse_pub.eddsa_pub);
    210   GNUNET_CRYPTO_eddsa_key_get_public (
    211     &ds->account_priv.reserve_priv.eddsa_priv,
    212     &ds->account_pub.reserve_pub.eddsa_pub);
    213   GNUNET_CRYPTO_eddsa_key_create (
    214     &ds->merge_priv.eddsa_priv);
    215   GNUNET_CRYPTO_eddsa_key_get_public (
    216     &ds->merge_priv.eddsa_priv,
    217     &ds->merge_pub.eddsa_pub);
    218   GNUNET_CRYPTO_ecdhe_key_create (
    219     &ds->contract_priv.ecdhe_priv);
    220   ds->purse_expiration
    221     = GNUNET_TIME_absolute_to_timestamp (
    222         GNUNET_TIME_relative_to_absolute (
    223           ds->expiration_rel));
    224 
    225   {
    226     struct TALER_NormalizedPayto payto_uri;
    227     const char *exchange_url;
    228     const struct TALER_TESTING_Command *exchange_cmd;
    229 
    230     exchange_cmd = TALER_TESTING_interpreter_get_command (is,
    231                                                           "exchange");
    232     if (NULL == exchange_cmd)
    233     {
    234       GNUNET_break (0);
    235       TALER_TESTING_interpreter_fail (is);
    236       return;
    237     }
    238     GNUNET_assert (
    239       GNUNET_OK ==
    240       TALER_TESTING_get_trait_exchange_url (
    241         exchange_cmd,
    242         &exchange_url));
    243     payto_uri
    244       = TALER_reserve_make_payto (
    245           exchange_url,
    246           &ds->account_pub.reserve_pub);
    247     TALER_normalized_payto_hash (payto_uri,
    248                                  &ds->h_payto);
    249     GNUNET_free (payto_uri.normalized_payto);
    250   }
    251 
    252   GNUNET_assert (0 ==
    253                  json_object_set_new (
    254                    ds->contract_terms,
    255                    "pay_deadline",
    256                    GNUNET_JSON_from_timestamp (ds->purse_expiration)));
    257   ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
    258   ds->dh = TALER_EXCHANGE_post_reserves_purse_create (
    259     TALER_TESTING_interpreter_get_context (is),
    260     TALER_TESTING_get_exchange_url (is),
    261     TALER_TESTING_get_keys (is),
    262     &ds->account_priv.reserve_priv,
    263     &ds->purse_priv,
    264     &ds->merge_priv,
    265     &ds->contract_priv,
    266     ds->contract_terms,
    267     ds->pay_purse_fee,
    268     ds->merge_timestamp);
    269   if (NULL == ds->dh)
    270   {
    271     GNUNET_break (0);
    272     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    273                 "Could not purse reserve\n");
    274     TALER_TESTING_interpreter_fail (is);
    275     return;
    276   }
    277   GNUNET_assert (GNUNET_OK ==
    278                  TALER_EXCHANGE_post_reserves_purse_set_options (
    279                    ds->dh,
    280                    TALER_EXCHANGE_post_reserves_purse_option_upload_contract ())
    281                  );
    282   GNUNET_assert (TALER_EC_NONE ==
    283                  TALER_EXCHANGE_post_reserves_purse_start (ds->dh,
    284                                                            &purse_cb,
    285                                                            ds));
    286 }
    287 
    288 
    289 /**
    290  * Free the state of a "purse" CMD, and possibly cancel a
    291  * pending operation thereof.
    292  *
    293  * @param cls closure, must be a `struct ReservePurseState`.
    294  * @param cmd the command which is being cleaned up.
    295  */
    296 static void
    297 purse_cleanup (void *cls,
    298                const struct TALER_TESTING_Command *cmd)
    299 {
    300   struct ReservePurseState *ds = cls;
    301 
    302   if (NULL != ds->dh)
    303   {
    304     TALER_TESTING_command_incomplete (ds->is,
    305                                       cmd->label);
    306     TALER_EXCHANGE_post_reserves_purse_cancel (ds->dh);
    307     ds->dh = NULL;
    308   }
    309   json_decref (ds->contract_terms);
    310   GNUNET_free (ds);
    311 }
    312 
    313 
    314 /**
    315  * Offer internal data from a "purse" CMD, to other commands.
    316  *
    317  * @param cls closure.
    318  * @param[out] ret result.
    319  * @param trait name of the trait.
    320  * @param index index number of the object to offer.
    321  * @return #GNUNET_OK on success.
    322  */
    323 static enum GNUNET_GenericReturnValue
    324 purse_traits (void *cls,
    325               const void **ret,
    326               const char *trait,
    327               unsigned int index)
    328 {
    329   struct ReservePurseState *ds = cls;
    330   struct TALER_TESTING_Trait traits[] = {
    331     TALER_TESTING_make_trait_timestamp (
    332       0,
    333       &ds->merge_timestamp),
    334     TALER_TESTING_make_trait_contract_terms (
    335       ds->contract_terms),
    336     TALER_TESTING_make_trait_purse_priv (
    337       &ds->purse_priv),
    338     TALER_TESTING_make_trait_purse_pub (
    339       &ds->purse_pub),
    340     TALER_TESTING_make_trait_merge_priv (
    341       &ds->merge_priv),
    342     TALER_TESTING_make_trait_merge_pub (
    343       &ds->merge_pub),
    344     TALER_TESTING_make_trait_contract_priv (
    345       &ds->contract_priv),
    346     TALER_TESTING_make_trait_account_priv (
    347       &ds->account_priv),
    348     TALER_TESTING_make_trait_account_pub (
    349       &ds->account_pub),
    350     TALER_TESTING_make_trait_reserve_priv (
    351       &ds->account_priv.reserve_priv),
    352     TALER_TESTING_make_trait_reserve_pub (
    353       &ds->account_pub.reserve_pub),
    354     TALER_TESTING_make_trait_reserve_sig (
    355       &ds->reserve_sig),
    356     TALER_TESTING_make_trait_legi_requirement_row (
    357       &ds->requirement_row),
    358     TALER_TESTING_make_trait_h_normalized_payto (
    359       &ds->h_payto),
    360     TALER_TESTING_trait_end ()
    361   };
    362 
    363   return TALER_TESTING_get_trait (traits,
    364                                   ret,
    365                                   trait,
    366                                   index);
    367 }
    368 
    369 
    370 struct TALER_TESTING_Command
    371 TALER_TESTING_cmd_purse_create_with_reserve (
    372   const char *label,
    373   unsigned int expected_http_status,
    374   const char *contract_terms,
    375   bool upload_contract,
    376   bool pay_purse_fee,
    377   struct GNUNET_TIME_Relative expiration,
    378   const char *reserve_ref)
    379 {
    380   struct ReservePurseState *ds;
    381   json_error_t err;
    382 
    383   ds = GNUNET_new (struct ReservePurseState);
    384   ds->expiration_rel = expiration;
    385   ds->contract_terms = json_loads (contract_terms,
    386                                    0 /* flags */,
    387                                    &err);
    388   GNUNET_assert (NULL != ds->contract_terms);
    389   ds->pay_purse_fee = pay_purse_fee;
    390   ds->reserve_ref = reserve_ref;
    391   ds->expected_response_code = expected_http_status;
    392 
    393   {
    394     struct TALER_TESTING_Command cmd = {
    395       .cls = ds,
    396       .label = label,
    397       .run = &purse_run,
    398       .cleanup = &purse_cleanup,
    399       .traits = &purse_traits
    400     };
    401 
    402     return cmd;
    403   }
    404 }
    405 
    406 
    407 /* end of testing_api_cmd_reserve_purse.c */