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_auditor_deposit_confirmation.c (13881B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2023 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_auditor_deposit_confirmation.c
     21  * @brief command for testing /deposit_confirmation.
     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_auditor_service.h"
     28 #include "taler/taler_testing_lib.h"
     29 #include "taler/taler_signatures.h"
     30 #include "taler/backoff.h"
     31 
     32 /**
     33  * How long do we wait AT MOST when retrying?
     34  */
     35 #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
     36           GNUNET_TIME_UNIT_MILLISECONDS, 100)
     37 
     38 /**
     39  * How often do we retry before giving up?
     40  */
     41 #define NUM_RETRIES 5
     42 
     43 
     44 /**
     45  * State for a "deposit confirmation" CMD.
     46  */
     47 struct DepositConfirmationState
     48 {
     49 
     50   /**
     51    * Reference to any command that is able to provide a deposit.
     52    */
     53   const char *deposit_reference;
     54 
     55   /**
     56    * What is the deposited amount without the fee (i.e. the
     57    * amount we expect in the deposit confirmation)?
     58    */
     59   const char *amount_without_fee;
     60 
     61   /**
     62    * How many coins were there in the @e deposit_reference?
     63    */
     64   unsigned int num_coins;
     65 
     66   /**
     67    * DepositConfirmation handle while operation is running.
     68    */
     69   struct TALER_AUDITOR_DepositConfirmationHandle *dc;
     70 
     71   /**
     72    * Interpreter state.
     73    */
     74   struct TALER_TESTING_Interpreter *is;
     75 
     76   /**
     77    * Task scheduled to try later.
     78    */
     79   struct GNUNET_SCHEDULER_Task *retry_task;
     80 
     81   /**
     82    * How long do we wait until we retry?
     83    */
     84   struct GNUNET_TIME_Relative backoff;
     85 
     86   /**
     87    * Expected HTTP response code.
     88    */
     89   unsigned int expected_response_code;
     90 
     91   /**
     92    * How often should we retry on (transient) failures?
     93    */
     94   unsigned int do_retry;
     95 
     96 };
     97 
     98 
     99 /**
    100  * Run the command.
    101  *
    102  * @param cls closure.
    103  * @param cmd the command to execute.
    104  * @param is the interpreter state.
    105  */
    106 static void
    107 deposit_confirmation_run (void *cls,
    108                           const struct TALER_TESTING_Command *cmd,
    109                           struct TALER_TESTING_Interpreter *is);
    110 
    111 
    112 /**
    113  * Task scheduled to re-try #deposit_confirmation_run.
    114  *
    115  * @param cls a `struct DepositConfirmationState`
    116  */
    117 static void
    118 do_retry (void *cls)
    119 {
    120   struct DepositConfirmationState *dcs = cls;
    121 
    122   dcs->retry_task = NULL;
    123   TALER_TESTING_touch_cmd (dcs->is);
    124   deposit_confirmation_run (dcs,
    125                             NULL,
    126                             dcs->is);
    127 }
    128 
    129 
    130 /**
    131  * Callback to analyze the /deposit-confirmation response, just used
    132  * to check if the response code is acceptable.
    133  *
    134  * @param cls closure.
    135  * @param dcr response details
    136  */
    137 static void
    138 deposit_confirmation_cb (
    139   void *cls,
    140   const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
    141 {
    142   struct DepositConfirmationState *dcs = cls;
    143   const struct TALER_AUDITOR_HttpResponse *hr = &dcr->hr;
    144 
    145   dcs->dc = NULL;
    146   if (dcs->expected_response_code != hr->http_status)
    147   {
    148     if (0 != dcs->do_retry)
    149     {
    150       dcs->do_retry--;
    151       if ( (0 == hr->http_status) ||
    152            (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
    153            (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
    154       {
    155         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    156                     "Retrying deposit confirmation failed with %u/%d\n",
    157                     hr->http_status,
    158                     (int) hr->ec);
    159         /* on DB conflicts, do not use backoff */
    160         if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
    161           dcs->backoff = GNUNET_TIME_UNIT_ZERO;
    162         else
    163           dcs->backoff = GNUNET_TIME_randomized_backoff (dcs->backoff,
    164                                                          MAX_BACKOFF);
    165         TALER_TESTING_inc_tries (dcs->is);
    166         dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff,
    167                                                         &do_retry,
    168                                                         dcs);
    169         return;
    170       }
    171     }
    172     TALER_TESTING_unexpected_status (dcs->is,
    173                                      hr->http_status,
    174                                      dcs->expected_response_code);
    175     return;
    176   }
    177   TALER_TESTING_interpreter_next (dcs->is);
    178 }
    179 
    180 
    181 /**
    182  * Run the command.
    183  *
    184  * @param cls closure.
    185  * @param cmd the command to execute.
    186  * @param is the interpreter state.
    187  */
    188 static void
    189 deposit_confirmation_run (void *cls,
    190                           const struct TALER_TESTING_Command *cmd,
    191                           struct TALER_TESTING_Interpreter *is)
    192 {
    193   static struct TALER_ExtensionPolicyHashP no_h_policy;
    194   struct DepositConfirmationState *dcs = cls;
    195   const struct TALER_TESTING_Command *deposit_cmd;
    196   struct TALER_MerchantWireHashP h_wire;
    197   struct TALER_PrivateContractHashP h_contract_terms;
    198   const struct GNUNET_TIME_Timestamp *exchange_timestamp = NULL;
    199   struct GNUNET_TIME_Timestamp timestamp;
    200   const struct GNUNET_TIME_Timestamp *wire_deadline;
    201   struct GNUNET_TIME_Timestamp refund_deadline
    202     = GNUNET_TIME_UNIT_ZERO_TS;
    203   struct TALER_Amount amount_without_fee;
    204   const struct TALER_Amount *cumulative_total;
    205   struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins];
    206   const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins];
    207   const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins];
    208   const struct TALER_MerchantPrivateKeyP *merchant_priv;
    209   struct TALER_MerchantPublicKeyP merchant_pub;
    210   const struct TALER_ExchangePublicKeyP *exchange_pub;
    211   const struct TALER_ExchangeSignatureP *exchange_sig;
    212   const json_t *wire_details;
    213   const json_t *contract_terms;
    214   const struct TALER_EXCHANGE_Keys *keys;
    215   const struct TALER_EXCHANGE_SigningPublicKey *spk;
    216   const char *auditor_url;
    217 
    218   (void) cmd;
    219   dcs->is = is;
    220   GNUNET_assert (NULL != dcs->deposit_reference);
    221   {
    222     const struct TALER_TESTING_Command *auditor_cmd;
    223 
    224     auditor_cmd
    225       = TALER_TESTING_interpreter_get_command (is,
    226                                                "auditor");
    227     if (NULL == auditor_cmd)
    228     {
    229       GNUNET_break (0);
    230       TALER_TESTING_interpreter_fail (is);
    231       return;
    232     }
    233     if (GNUNET_OK !=
    234         TALER_TESTING_get_trait_auditor_url (auditor_cmd,
    235                                              &auditor_url))
    236     {
    237       GNUNET_break (0);
    238       TALER_TESTING_interpreter_fail (is);
    239       return;
    240     }
    241   }
    242   deposit_cmd
    243     = TALER_TESTING_interpreter_lookup_command (is,
    244                                                 dcs->deposit_reference);
    245   if (NULL == deposit_cmd)
    246   {
    247     GNUNET_break (0);
    248     TALER_TESTING_interpreter_fail (is);
    249     return;
    250   }
    251 
    252   GNUNET_assert (GNUNET_OK ==
    253                  TALER_TESTING_get_trait_exchange_pub (deposit_cmd,
    254                                                        0,
    255                                                        &exchange_pub));
    256   GNUNET_assert (GNUNET_OK ==
    257                  TALER_TESTING_get_trait_exchange_sig (deposit_cmd,
    258                                                        0,
    259                                                        &exchange_sig));
    260   GNUNET_assert (GNUNET_OK ==
    261                  TALER_TESTING_get_trait_amount (deposit_cmd,
    262                                                  &cumulative_total));
    263   GNUNET_assert (GNUNET_OK ==
    264                  TALER_TESTING_get_trait_timestamp (deposit_cmd,
    265                                                     0,
    266                                                     &exchange_timestamp));
    267   GNUNET_assert (GNUNET_OK ==
    268                  TALER_TESTING_get_trait_wire_deadline (deposit_cmd,
    269                                                         0,
    270                                                         &wire_deadline));
    271   GNUNET_assert (NULL != exchange_timestamp);
    272   keys = TALER_TESTING_get_keys (is);
    273   GNUNET_assert (NULL != keys);
    274   spk = TALER_EXCHANGE_get_signing_key_info (keys,
    275                                              exchange_pub);
    276 
    277   GNUNET_assert (GNUNET_OK ==
    278                  TALER_TESTING_get_trait_contract_terms (deposit_cmd,
    279                                                          &contract_terms));
    280   /* Very unlikely to fail */
    281   GNUNET_assert (NULL != contract_terms);
    282   GNUNET_assert (GNUNET_OK ==
    283                  TALER_JSON_contract_hash (contract_terms,
    284                                            &h_contract_terms));
    285   GNUNET_assert (GNUNET_OK ==
    286                  TALER_TESTING_get_trait_wire_details (deposit_cmd,
    287                                                        &wire_details));
    288   GNUNET_assert (GNUNET_OK ==
    289                  TALER_JSON_merchant_wire_signature_hash (wire_details,
    290                                                           &h_wire));
    291 
    292   for (unsigned int i = 0; i<dcs->num_coins; i++)
    293   {
    294     const struct TALER_CoinSpendPrivateKeyP *coin_priv;
    295 
    296     GNUNET_assert (GNUNET_OK ==
    297                    TALER_TESTING_get_trait_coin_priv (deposit_cmd,
    298                                                       i,
    299                                                       &coin_priv));
    300     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
    301                                         &coin_pubs[i].eddsa_pub);
    302     coin_pubps[i] = &coin_pubs[i];
    303     GNUNET_assert (GNUNET_OK ==
    304                    TALER_TESTING_get_trait_coin_sig (deposit_cmd,
    305                                                      i,
    306                                                      &coin_sigps[i]));
    307   }
    308   GNUNET_assert (GNUNET_OK ==
    309                  TALER_TESTING_get_trait_merchant_priv (deposit_cmd,
    310                                                         &merchant_priv));
    311   GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
    312                                       &merchant_pub.eddsa_pub);
    313   GNUNET_assert (GNUNET_OK ==
    314                  TALER_string_to_amount (dcs->amount_without_fee,
    315                                          &amount_without_fee));
    316   {
    317     struct GNUNET_JSON_Specification spec[] = {
    318       /* timestamp is mandatory */
    319       GNUNET_JSON_spec_timestamp ("timestamp",
    320                                   &timestamp),
    321       GNUNET_JSON_spec_mark_optional (
    322         GNUNET_JSON_spec_timestamp ("refund_deadline",
    323                                     &refund_deadline),
    324         NULL),
    325       GNUNET_JSON_spec_end ()
    326     };
    327 
    328     if (GNUNET_OK !=
    329         GNUNET_JSON_parse (contract_terms,
    330                            spec,
    331                            NULL, NULL))
    332     {
    333       GNUNET_break (0);
    334       TALER_TESTING_interpreter_fail (is);
    335       return;
    336     }
    337     if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
    338       refund_deadline = timestamp;
    339   }
    340   if (-1 ==
    341       TALER_amount_cmp (cumulative_total,
    342                         &amount_without_fee))
    343   {
    344 
    345     /* Cumulative must not below the amount we deposited this time */
    346     GNUNET_break (0);
    347     TALER_TESTING_interpreter_fail (is);
    348     return;
    349   }
    350   dcs->dc = TALER_AUDITOR_deposit_confirmation (
    351     TALER_TESTING_interpreter_get_context (is),
    352     auditor_url,
    353     &h_wire,
    354     &no_h_policy,
    355     &h_contract_terms,
    356     *exchange_timestamp,
    357     *wire_deadline,
    358     refund_deadline,
    359     cumulative_total,
    360     dcs->num_coins,
    361     coin_pubps,
    362     coin_sigps,
    363     &merchant_pub,
    364     exchange_pub,
    365     exchange_sig,
    366     &keys->master_pub,
    367     spk->valid_from,
    368     spk->valid_until,
    369     spk->valid_legal,
    370     &spk->master_sig,
    371     &deposit_confirmation_cb,
    372     dcs);
    373 
    374   if (NULL == dcs->dc)
    375   {
    376     GNUNET_break (0);
    377     TALER_TESTING_interpreter_fail (is);
    378     return;
    379   }
    380   return;
    381 }
    382 
    383 
    384 /**
    385  * Free the state of a "deposit_confirmation" CMD, and possibly cancel a
    386  * pending operation thereof.
    387  *
    388  * @param cls closure, a `struct DepositConfirmationState`
    389  * @param cmd the command which is being cleaned up.
    390  */
    391 static void
    392 deposit_confirmation_cleanup (void *cls,
    393                               const struct TALER_TESTING_Command *cmd)
    394 {
    395   struct DepositConfirmationState *dcs = cls;
    396 
    397   if (NULL != dcs->dc)
    398   {
    399     TALER_TESTING_command_incomplete (dcs->is,
    400                                       cmd->label);
    401     TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc);
    402     dcs->dc = NULL;
    403   }
    404   if (NULL != dcs->retry_task)
    405   {
    406     GNUNET_SCHEDULER_cancel (dcs->retry_task);
    407     dcs->retry_task = NULL;
    408   }
    409   GNUNET_free (dcs);
    410 }
    411 
    412 
    413 struct TALER_TESTING_Command
    414 TALER_TESTING_cmd_deposit_confirmation (const char *label,
    415                                         const char *deposit_reference,
    416                                         unsigned int num_coins,
    417                                         const char *amount_without_fee,
    418                                         unsigned int expected_response_code)
    419 {
    420   struct DepositConfirmationState *dcs;
    421 
    422   dcs = GNUNET_new (struct DepositConfirmationState);
    423   dcs->deposit_reference = deposit_reference;
    424   dcs->num_coins = num_coins;
    425   dcs->amount_without_fee = amount_without_fee;
    426   dcs->expected_response_code = expected_response_code;
    427 
    428   {
    429     struct TALER_TESTING_Command cmd = {
    430       .cls = dcs,
    431       .label = label,
    432       .run = &deposit_confirmation_run,
    433       .cleanup = &deposit_confirmation_cleanup
    434     };
    435 
    436     return cmd;
    437   }
    438 }
    439 
    440 
    441 struct TALER_TESTING_Command
    442 TALER_TESTING_cmd_deposit_confirmation_with_retry (
    443   struct TALER_TESTING_Command cmd)
    444 {
    445   struct DepositConfirmationState *dcs;
    446 
    447   GNUNET_assert (&deposit_confirmation_run == cmd.run);
    448   dcs = cmd.cls;
    449   dcs->do_retry = NUM_RETRIES;
    450   return cmd;
    451 }
    452 
    453 
    454 /* end of testing_auditor_api_cmd_deposit_confirmation.c */