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_purse_merge.c (11971B)


      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_purse_merge.c
     21  * @brief command for testing /purses/$PID/merge
     22  * @author Christian Grothoff
     23  */
     24 #include "taler/platform.h"
     25 #include "taler/taler_json_lib.h"
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_testing_lib.h"
     28 #include "taler/taler_signatures.h"
     29 #include "taler/backoff.h"
     30 
     31 
     32 /**
     33  * State for a "purse create deposit" CMD.
     34  */
     35 struct PurseMergeState
     36 {
     37 
     38   /**
     39    * Merge time.
     40    */
     41   struct GNUNET_TIME_Timestamp merge_timestamp;
     42 
     43   /**
     44    * Reserve public key (to be merged into)
     45    */
     46   struct TALER_ReservePublicKeyP reserve_pub;
     47 
     48   /**
     49    * Reserve private key (useful especially if
     50    * @e reserve_ref is NULL).
     51    */
     52   struct TALER_ReservePrivateKeyP reserve_priv;
     53 
     54   /**
     55    * Handle while operation is running.
     56    */
     57   struct TALER_EXCHANGE_AccountMergeHandle *dh;
     58 
     59   /**
     60    * Reference to the merge capability.
     61    */
     62   const char *merge_ref;
     63 
     64   /**
     65    * Reference to the reserve, or NULL (!).
     66    */
     67   const char *reserve_ref;
     68 
     69   /**
     70    * Interpreter state.
     71    */
     72   struct TALER_TESTING_Interpreter *is;
     73 
     74   /**
     75    * Hash of the payto://-URI for the reserve we are
     76    * merging into.
     77    */
     78   struct TALER_NormalizedPaytoHashP h_payto;
     79 
     80   /**
     81    * Set to the KYC requirement row *if* the exchange replied with
     82    * a request for KYC.
     83    */
     84   uint64_t requirement_row;
     85 
     86   /**
     87    * Reserve history entry that corresponds to this operation.
     88    * Will be of type #TALER_EXCHANGE_RTT_MERGE.
     89    */
     90   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
     91 
     92   /**
     93    * Public key of the purse.
     94    */
     95   struct TALER_PurseContractPublicKeyP purse_pub;
     96 
     97   /**
     98    * Public key of the merge capability.
     99    */
    100   struct TALER_PurseMergePublicKeyP merge_pub;
    101 
    102   /**
    103    * Contract value.
    104    */
    105   struct TALER_Amount value_after_fees;
    106 
    107   /**
    108    * Hash of the contract.
    109    */
    110   struct TALER_PrivateContractHashP h_contract_terms;
    111 
    112   /**
    113    * When does the purse expire.
    114    */
    115   struct GNUNET_TIME_Timestamp purse_expiration;
    116 
    117   /**
    118    * Minimum age of deposits into the purse.
    119    */
    120   uint32_t min_age;
    121 
    122   /**
    123    * Expected HTTP response code.
    124    */
    125   unsigned int expected_response_code;
    126 
    127 };
    128 
    129 
    130 /**
    131  * Callback to analyze the /purses/$PID/merge response, just used to check if
    132  * the response code is acceptable.
    133  *
    134  * @param cls closure.
    135  * @param dr merge response details
    136  */
    137 static void
    138 merge_cb (void *cls,
    139           const struct TALER_EXCHANGE_AccountMergeResponse *dr)
    140 {
    141   struct PurseMergeState *ds = cls;
    142 
    143   ds->dh = NULL;
    144   switch (dr->hr.http_status)
    145   {
    146   case MHD_HTTP_OK:
    147     ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
    148     ds->reserve_history.amount = ds->value_after_fees;
    149     GNUNET_assert (GNUNET_OK ==
    150                    TALER_amount_set_zero (
    151                      ds->value_after_fees.currency,
    152                      &ds->reserve_history.details.merge_details.purse_fee));
    153     ds->reserve_history.details.merge_details.h_contract_terms
    154       = ds->h_contract_terms;
    155     ds->reserve_history.details.merge_details.merge_pub
    156       = ds->merge_pub;
    157     ds->reserve_history.details.merge_details.purse_pub
    158       = ds->purse_pub;
    159     ds->reserve_history.details.merge_details.reserve_sig
    160       = *dr->reserve_sig;
    161     ds->reserve_history.details.merge_details.merge_timestamp
    162       = ds->merge_timestamp;
    163     ds->reserve_history.details.merge_details.purse_expiration
    164       = ds->purse_expiration;
    165     ds->reserve_history.details.merge_details.min_age
    166       = ds->min_age;
    167     ds->reserve_history.details.merge_details.flags
    168       = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE;
    169     break;
    170   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    171     /* KYC required */
    172     ds->requirement_row =
    173       dr->details.unavailable_for_legal_reasons.requirement_row;
    174     GNUNET_break (0 ==
    175                   GNUNET_memcmp (
    176                     &ds->h_payto,
    177                     &dr->details.unavailable_for_legal_reasons.h_payto));
    178     break;
    179   }
    180 
    181 
    182   if (ds->expected_response_code != dr->hr.http_status)
    183   {
    184     TALER_TESTING_unexpected_status (ds->is,
    185                                      dr->hr.http_status,
    186                                      ds->expected_response_code);
    187     return;
    188   }
    189   TALER_TESTING_interpreter_next (ds->is);
    190 }
    191 
    192 
    193 /**
    194  * Run the command.
    195  *
    196  * @param cls closure.
    197  * @param cmd the command to execute.
    198  * @param is the interpreter state.
    199  */
    200 static void
    201 merge_run (void *cls,
    202            const struct TALER_TESTING_Command *cmd,
    203            struct TALER_TESTING_Interpreter *is)
    204 {
    205   struct PurseMergeState *ds = cls;
    206   const struct TALER_PurseMergePrivateKeyP *merge_priv;
    207   const json_t *ct;
    208   const struct TALER_TESTING_Command *ref;
    209 
    210   (void) cmd;
    211   ds->is = is;
    212   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
    213                                                   ds->merge_ref);
    214   GNUNET_assert (NULL != ref);
    215   if (GNUNET_OK !=
    216       TALER_TESTING_get_trait_merge_priv (ref,
    217                                           &merge_priv))
    218   {
    219     GNUNET_break (0);
    220     TALER_TESTING_interpreter_fail (ds->is);
    221     return;
    222   }
    223   {
    224     const struct TALER_PurseContractPublicKeyP *purse_pub;
    225 
    226     if (GNUNET_OK !=
    227         TALER_TESTING_get_trait_purse_pub (ref,
    228                                            &purse_pub))
    229     {
    230       GNUNET_break (0);
    231       TALER_TESTING_interpreter_fail (ds->is);
    232       return;
    233     }
    234     ds->purse_pub = *purse_pub;
    235   }
    236 
    237   if (GNUNET_OK !=
    238       TALER_TESTING_get_trait_contract_terms (ref,
    239                                               &ct))
    240   {
    241     GNUNET_break (0);
    242     TALER_TESTING_interpreter_fail (ds->is);
    243     return;
    244   }
    245   if (GNUNET_OK !=
    246       TALER_JSON_contract_hash (ct,
    247                                 &ds->h_contract_terms))
    248   {
    249     GNUNET_break (0);
    250     TALER_TESTING_interpreter_fail (ds->is);
    251     return;
    252   }
    253   {
    254     struct GNUNET_JSON_Specification spec[] = {
    255       GNUNET_JSON_spec_timestamp ("pay_deadline",
    256                                   &ds->purse_expiration),
    257       TALER_JSON_spec_amount_any ("amount",
    258                                   &ds->value_after_fees),
    259       GNUNET_JSON_spec_mark_optional (
    260         GNUNET_JSON_spec_uint32 ("minimum_age",
    261                                  &ds->min_age),
    262         NULL),
    263       GNUNET_JSON_spec_end ()
    264     };
    265 
    266     if (GNUNET_OK !=
    267         GNUNET_JSON_parse (ct,
    268                            spec,
    269                            NULL, NULL))
    270     {
    271       GNUNET_break (0);
    272       TALER_TESTING_interpreter_fail (ds->is);
    273       return;
    274     }
    275   }
    276 
    277   if (NULL == ds->reserve_ref)
    278   {
    279     GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv);
    280   }
    281   else
    282   {
    283     const struct TALER_ReservePrivateKeyP *rp;
    284 
    285     ref = TALER_TESTING_interpreter_lookup_command (ds->is,
    286                                                     ds->reserve_ref);
    287     GNUNET_assert (NULL != ref);
    288     if (GNUNET_OK !=
    289         TALER_TESTING_get_trait_reserve_priv (ref,
    290                                               &rp))
    291     {
    292       GNUNET_break (0);
    293       TALER_TESTING_interpreter_fail (ds->is);
    294       return;
    295     }
    296     ds->reserve_priv = *rp;
    297   }
    298   GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
    299                                       &ds->reserve_pub.eddsa_pub);
    300   {
    301     struct TALER_NormalizedPayto payto_uri;
    302     const char *exchange_url;
    303     const struct TALER_TESTING_Command *exchange_cmd;
    304 
    305     exchange_cmd = TALER_TESTING_interpreter_get_command (is,
    306                                                           "exchange");
    307     if (NULL == exchange_cmd)
    308     {
    309       GNUNET_break (0);
    310       TALER_TESTING_interpreter_fail (is);
    311       return;
    312     }
    313     GNUNET_assert (GNUNET_OK ==
    314                    TALER_TESTING_get_trait_exchange_url (exchange_cmd,
    315                                                          &exchange_url));
    316     payto_uri = TALER_reserve_make_payto (exchange_url,
    317                                           &ds->reserve_pub);
    318     TALER_normalized_payto_hash (payto_uri,
    319                                  &ds->h_payto);
    320     GNUNET_free (payto_uri.normalized_payto);
    321   }
    322   GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
    323                                       &ds->merge_pub.eddsa_pub);
    324   ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
    325   ds->dh = TALER_EXCHANGE_account_merge (
    326     TALER_TESTING_interpreter_get_context (is),
    327     TALER_TESTING_get_exchange_url (is),
    328     TALER_TESTING_get_keys (is),
    329     NULL, /* no wad */
    330     &ds->reserve_priv,
    331     &ds->purse_pub,
    332     merge_priv,
    333     &ds->h_contract_terms,
    334     ds->min_age,
    335     &ds->value_after_fees,
    336     ds->purse_expiration,
    337     ds->merge_timestamp,
    338     &merge_cb,
    339     ds);
    340   if (NULL == ds->dh)
    341   {
    342     GNUNET_break (0);
    343     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    344                 "Could not merge purse\n");
    345     TALER_TESTING_interpreter_fail (is);
    346     return;
    347   }
    348 }
    349 
    350 
    351 /**
    352  * Free the state of a "merge" CMD, and possibly cancel a
    353  * pending operation thereof.
    354  *
    355  * @param cls closure, must be a `struct PurseMergeState`.
    356  * @param cmd the command which is being cleaned up.
    357  */
    358 static void
    359 merge_cleanup (void *cls,
    360                const struct TALER_TESTING_Command *cmd)
    361 {
    362   struct PurseMergeState *ds = cls;
    363 
    364   if (NULL != ds->dh)
    365   {
    366     TALER_TESTING_command_incomplete (ds->is,
    367                                       cmd->label);
    368     TALER_EXCHANGE_account_merge_cancel (ds->dh);
    369     ds->dh = NULL;
    370   }
    371   GNUNET_free (ds);
    372 }
    373 
    374 
    375 /**
    376  * Offer internal data from a "merge" CMD, to other commands.
    377  *
    378  * @param cls closure.
    379  * @param[out] ret result.
    380  * @param trait name of the trait.
    381  * @param index index number of the object to offer.
    382  * @return #GNUNET_OK on success.
    383  */
    384 static enum GNUNET_GenericReturnValue
    385 merge_traits (void *cls,
    386               const void **ret,
    387               const char *trait,
    388               unsigned int index)
    389 {
    390   struct PurseMergeState *ds = cls;
    391   struct TALER_TESTING_Trait traits[] = {
    392     /* history entry MUST be first due to response code logic below! */
    393     TALER_TESTING_make_trait_reserve_history (0,
    394                                               &ds->reserve_history),
    395     TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
    396     TALER_TESTING_make_trait_timestamp (0,
    397                                         &ds->merge_timestamp),
    398     TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
    399     TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
    400     TALER_TESTING_trait_end ()
    401   };
    402 
    403   return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
    404                                   ? &traits[0]   /* we have reserve history */
    405                                   : &traits[1],  /* skip reserve history */
    406                                   ret,
    407                                   trait,
    408                                   index);
    409 }
    410 
    411 
    412 struct TALER_TESTING_Command
    413 TALER_TESTING_cmd_purse_merge (
    414   const char *label,
    415   unsigned int expected_http_status,
    416   const char *merge_ref,
    417   const char *reserve_ref)
    418 {
    419   struct PurseMergeState *ds;
    420 
    421   ds = GNUNET_new (struct PurseMergeState);
    422   ds->merge_ref = merge_ref;
    423   ds->reserve_ref = reserve_ref;
    424   ds->expected_response_code = expected_http_status;
    425 
    426   {
    427     struct TALER_TESTING_Command cmd = {
    428       .cls = ds,
    429       .label = label,
    430       .run = &merge_run,
    431       .cleanup = &merge_cleanup,
    432       .traits = &merge_traits
    433     };
    434 
    435     return cmd;
    436   }
    437 }
    438 
    439 
    440 /* end of testing_api_cmd_purse_merge.c */