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


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