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


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