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 (12043B)


      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_PostPursesMergeHandle *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_PostPursesMergeResponse *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_post_purses_merge_create (
    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   GNUNET_assert (NULL != ds->dh);
    339   GNUNET_assert (TALER_EC_NONE ==
    340                  TALER_EXCHANGE_post_purses_merge_start (ds->dh,
    341                                                          &merge_cb,
    342                                                          ds));
    343 }
    344 
    345 
    346 /**
    347  * Free the state of a "merge" CMD, and possibly cancel a
    348  * pending operation thereof.
    349  *
    350  * @param cls closure, must be a `struct PurseMergeState`.
    351  * @param cmd the command which is being cleaned up.
    352  */
    353 static void
    354 merge_cleanup (void *cls,
    355                const struct TALER_TESTING_Command *cmd)
    356 {
    357   struct PurseMergeState *ds = cls;
    358 
    359   if (NULL != ds->dh)
    360   {
    361     TALER_TESTING_command_incomplete (ds->is,
    362                                       cmd->label);
    363     TALER_EXCHANGE_post_purses_merge_cancel (ds->dh);
    364     ds->dh = NULL;
    365   }
    366   GNUNET_free (ds);
    367 }
    368 
    369 
    370 /**
    371  * Offer internal data from a "merge" CMD, to other commands.
    372  *
    373  * @param cls closure.
    374  * @param[out] ret result.
    375  * @param trait name of the trait.
    376  * @param index index number of the object to offer.
    377  * @return #GNUNET_OK on success.
    378  */
    379 static enum GNUNET_GenericReturnValue
    380 merge_traits (void *cls,
    381               const void **ret,
    382               const char *trait,
    383               unsigned int index)
    384 {
    385   struct PurseMergeState *ds = cls;
    386   struct TALER_TESTING_Trait traits[] = {
    387     /* history entry MUST be first due to response code logic below! */
    388     TALER_TESTING_make_trait_reserve_history (0,
    389                                               &ds->reserve_history),
    390     TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
    391     TALER_TESTING_make_trait_timestamp (0,
    392                                         &ds->merge_timestamp),
    393     TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
    394     TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
    395     TALER_TESTING_trait_end ()
    396   };
    397 
    398   return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
    399                                   ? &traits[0]   /* we have reserve history */
    400                                   : &traits[1],  /* skip reserve history */
    401                                   ret,
    402                                   trait,
    403                                   index);
    404 }
    405 
    406 
    407 struct TALER_TESTING_Command
    408 TALER_TESTING_cmd_purse_merge (
    409   const char *label,
    410   unsigned int expected_http_status,
    411   const char *merge_ref,
    412   const char *reserve_ref)
    413 {
    414   struct PurseMergeState *ds;
    415 
    416   ds = GNUNET_new (struct PurseMergeState);
    417   ds->merge_ref = merge_ref;
    418   ds->reserve_ref = reserve_ref;
    419   ds->expected_response_code = expected_http_status;
    420 
    421   {
    422     struct TALER_TESTING_Command cmd = {
    423       .cls = ds,
    424       .label = label,
    425       .run = &merge_run,
    426       .cleanup = &merge_cleanup,
    427       .traits = &merge_traits
    428     };
    429 
    430     return cmd;
    431   }
    432 }
    433 
    434 
    435 /* end of testing_api_cmd_purse_merge.c */