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_bank_admin_add_incoming.c (16541B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2021 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_bank_admin_add_incoming.c
     21  * @brief implementation of a bank /admin/add-incoming command
     22  * @author Christian Grothoff
     23  * @author Marcello Stanisci
     24  */
     25 #include "taler/platform.h"
     26 #include "taler/backoff.h"
     27 #include "taler/taler_json_lib.h"
     28 #include <gnunet/gnunet_curl_lib.h>
     29 #include "taler/taler_bank_service.h"
     30 #include "taler/taler_signatures.h"
     31 #include "taler/taler_testing_lib.h"
     32 
     33 /**
     34  * How long do we wait AT MOST when retrying?
     35  */
     36 #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
     37           GNUNET_TIME_UNIT_MILLISECONDS, 100)
     38 
     39 
     40 /**
     41  * How often do we retry before giving up?
     42  */
     43 #define NUM_RETRIES 5
     44 
     45 
     46 /**
     47  * State for a "bank transfer" CMD.
     48  */
     49 struct AdminAddIncomingState
     50 {
     51 
     52   /**
     53    * Label of any command that can trait-offer a reserve priv.
     54    */
     55   const char *reserve_reference;
     56 
     57   /**
     58    * Wire transfer amount.
     59    */
     60   struct TALER_Amount amount;
     61 
     62   /**
     63    * Base URL of the credited account.
     64    */
     65   const char *exchange_credit_url;
     66 
     67   /**
     68    * Money sender payto URL.
     69    */
     70   struct TALER_FullPayto payto_debit_account;
     71 
     72   /**
     73    * Username to use for authentication.
     74    */
     75   struct TALER_BANK_AuthenticationData auth;
     76 
     77   /**
     78    * Set (by the interpreter) to the reserve's private key
     79    * we used to make a wire transfer subject line with.
     80    */
     81   union TALER_AccountPrivateKeyP account_priv;
     82 
     83   /**
     84    * Whether we know the private key or not.
     85    */
     86   bool reserve_priv_known;
     87 
     88   /**
     89    * Account public key matching @e account_priv.
     90    */
     91   union TALER_AccountPublicKeyP account_pub;
     92 
     93   /**
     94    * Handle to the pending request at the bank.
     95    */
     96   struct TALER_BANK_AdminAddIncomingHandle *aih;
     97 
     98   /**
     99    * Interpreter state.
    100    */
    101   struct TALER_TESTING_Interpreter *is;
    102 
    103   /**
    104    * Reserve history entry that corresponds to this operation.
    105    * Will be of type #TALER_EXCHANGE_RTT_CREDIT.  Note that
    106    * the "sender_url" field is set to a 'const char *' and
    107    * MUST NOT be free()'ed.
    108    */
    109   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
    110 
    111   /**
    112    * Set to the wire transfer's unique ID.
    113    */
    114   uint64_t serial_id;
    115 
    116   /**
    117    * Timestamp of the transaction (as returned from the bank).
    118    */
    119   struct GNUNET_TIME_Timestamp timestamp;
    120 
    121   /**
    122    * Task scheduled to try later.
    123    */
    124   struct GNUNET_SCHEDULER_Task *retry_task;
    125 
    126   /**
    127    * How long do we wait until we retry?
    128    */
    129   struct GNUNET_TIME_Relative backoff;
    130 
    131   /**
    132    * Was this command modified via
    133    * #TALER_TESTING_cmd_admin_add_incoming_with_retry to
    134    * enable retries? If so, how often should we still retry?
    135    */
    136   unsigned int do_retry;
    137 
    138   /**
    139    * Expected HTTP status code.
    140    */
    141   unsigned int expected_http_status;
    142 };
    143 
    144 
    145 /**
    146  * Run the "bank transfer" CMD.
    147  *
    148  * @param cls closure.
    149  * @param cmd CMD being run.
    150  * @param is interpreter state.
    151  */
    152 static void
    153 admin_add_incoming_run (
    154   void *cls,
    155   const struct TALER_TESTING_Command *cmd,
    156   struct TALER_TESTING_Interpreter *is);
    157 
    158 
    159 /**
    160  * Task scheduled to re-try #admin_add_incoming_run.
    161  *
    162  * @param cls a `struct AdminAddIncomingState`
    163  */
    164 static void
    165 do_retry (void *cls)
    166 {
    167   struct AdminAddIncomingState *fts = cls;
    168 
    169   fts->retry_task = NULL;
    170   TALER_TESTING_touch_cmd (fts->is);
    171   admin_add_incoming_run (fts,
    172                           NULL,
    173                           fts->is);
    174 }
    175 
    176 
    177 /**
    178  * This callback will process the bank response to the wire
    179  * transfer.  It just checks whether the HTTP response code is
    180  * acceptable.
    181  *
    182  * @param cls closure with the interpreter state
    183  * @param air response details
    184  */
    185 static void
    186 confirmation_cb (void *cls,
    187                  const struct TALER_BANK_AdminAddIncomingResponse *air)
    188 {
    189   struct AdminAddIncomingState *fts = cls;
    190   struct TALER_TESTING_Interpreter *is = fts->is;
    191 
    192   fts->aih = NULL;
    193   /**
    194    * Test case not caring about the HTTP status code.
    195    * That helps when fakebank and Libeufin diverge in
    196    * the response status code.  An example is the
    197    * /admin/add-incoming: libeufin return ALWAYS '200 OK'
    198    * (see note below) whereas the fakebank responds with
    199    * '409 Conflict' upon a duplicate reserve public key.
    200    *
    201    * Note: this decision aims at avoiding to put Taler
    202    * logic into the Sandbox; that's because banks DO allow
    203    * their customers to wire the same subject multiple
    204    * times.  Hence, instead of triggering any error, libeufin
    205    * bounces the payment back in the same way it does for
    206    * malformed reserve public keys.
    207    */
    208   if (-1 == (int) fts->expected_http_status)
    209   {
    210     TALER_TESTING_interpreter_next (is);
    211     return;
    212   }
    213   if (air->http_status != fts->expected_http_status)
    214   {
    215     TALER_TESTING_unexpected_status (is,
    216                                      air->http_status,
    217                                      fts->expected_http_status);
    218     return;
    219   }
    220   switch (air->http_status)
    221   {
    222   case MHD_HTTP_OK:
    223     fts->reserve_history.details.in_details.timestamp
    224       = air->details.ok.timestamp;
    225     fts->reserve_history.details.in_details.wire_reference
    226       = air->details.ok.serial_id;
    227     fts->serial_id
    228       = air->details.ok.serial_id;
    229     fts->timestamp
    230       = air->details.ok.timestamp;
    231     TALER_TESTING_interpreter_next (is);
    232     return;
    233   case MHD_HTTP_UNAUTHORIZED:
    234     switch (fts->auth.method)
    235     {
    236     case TALER_BANK_AUTH_NONE:
    237       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    238                   "Authentication required, but none configure.\n");
    239       break;
    240     case TALER_BANK_AUTH_BASIC:
    241       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    242                   "Basic authentication (%s) failed.\n",
    243                   fts->auth.details.basic.username);
    244       break;
    245     case TALER_BANK_AUTH_BEARER:
    246       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    247                   "Bearer authentication (%s) failed.\n",
    248                   fts->auth.details.bearer.token);
    249       break;
    250     }
    251     break;
    252   case MHD_HTTP_CONFLICT:
    253     TALER_TESTING_interpreter_next (is);
    254     return;
    255   default:
    256     if (0 != fts->do_retry)
    257     {
    258       fts->do_retry--;
    259       if ( (0 == air->http_status) ||
    260            (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec) ||
    261            (MHD_HTTP_INTERNAL_SERVER_ERROR == air->http_status) )
    262       {
    263         GNUNET_log (
    264           GNUNET_ERROR_TYPE_INFO,
    265           "Retrying bank transfer failed with %u/%d\n",
    266           air->http_status,
    267           (int) air->ec);
    268         /* on DB conflicts, do not use backoff */
    269         if (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec)
    270           fts->backoff = GNUNET_TIME_UNIT_ZERO;
    271         else
    272           fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
    273                                                          MAX_BACKOFF);
    274         TALER_TESTING_inc_tries (fts->is);
    275         fts->retry_task = GNUNET_SCHEDULER_add_delayed (
    276           fts->backoff,
    277           &do_retry,
    278           fts);
    279         return;
    280       }
    281     }
    282     break;
    283   }
    284   GNUNET_break (0);
    285   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    286               "Bank returned HTTP status %u/%d\n",
    287               air->http_status,
    288               (int) air->ec);
    289   TALER_TESTING_interpreter_fail (is);
    290 }
    291 
    292 
    293 static void
    294 admin_add_incoming_run (
    295   void *cls,
    296   const struct TALER_TESTING_Command *cmd,
    297   struct TALER_TESTING_Interpreter *is)
    298 {
    299   struct AdminAddIncomingState *fts = cls;
    300   bool have_public = false;
    301 
    302   (void) cmd;
    303   fts->is = is;
    304   /* Use reserve public key as subject */
    305   if (NULL != fts->reserve_reference)
    306   {
    307     const struct TALER_TESTING_Command *ref;
    308     const struct TALER_ReservePrivateKeyP *reserve_priv;
    309     const struct TALER_ReservePublicKeyP *reserve_pub;
    310 
    311     ref = TALER_TESTING_interpreter_lookup_command (
    312       is,
    313       fts->reserve_reference);
    314     if (NULL == ref)
    315     {
    316       GNUNET_break (0);
    317       TALER_TESTING_interpreter_fail (is);
    318       return;
    319     }
    320     if (GNUNET_OK !=
    321         TALER_TESTING_get_trait_reserve_priv (ref,
    322                                               &reserve_priv))
    323     {
    324       if (GNUNET_OK !=
    325           TALER_TESTING_get_trait_reserve_pub (ref,
    326                                                &reserve_pub))
    327       {
    328         GNUNET_break (0);
    329         TALER_TESTING_interpreter_fail (is);
    330         return;
    331       }
    332       have_public = true;
    333       fts->account_pub.reserve_pub.eddsa_pub
    334         = reserve_pub->eddsa_pub;
    335       fts->reserve_priv_known = false;
    336     }
    337     else
    338     {
    339       fts->account_priv.reserve_priv.eddsa_priv
    340         = reserve_priv->eddsa_priv;
    341       fts->reserve_priv_known = true;
    342     }
    343   }
    344   else
    345   {
    346     /* No referenced reserve to take priv
    347      * from, no explicit subject given: create new key! */
    348     GNUNET_CRYPTO_eddsa_key_create (
    349       &fts->account_priv.reserve_priv.eddsa_priv);
    350     fts->reserve_priv_known = true;
    351   }
    352   if (! have_public)
    353     GNUNET_CRYPTO_eddsa_key_get_public (
    354       &fts->account_priv.reserve_priv.eddsa_priv,
    355       &fts->account_pub.reserve_pub.eddsa_pub);
    356   fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
    357   fts->reserve_history.amount = fts->amount;
    358   fts->reserve_history.details.in_details.sender_url
    359     = fts->payto_debit_account; /* remember to NOT free this one... */
    360   fts->aih
    361     = TALER_BANK_admin_add_incoming (
    362         TALER_TESTING_interpreter_get_context (is),
    363         &fts->auth,
    364         &fts->account_pub.reserve_pub,
    365         &fts->amount,
    366         fts->payto_debit_account,
    367         &confirmation_cb,
    368         fts);
    369   if (NULL == fts->aih)
    370   {
    371     GNUNET_break (0);
    372     TALER_TESTING_interpreter_fail (is);
    373     return;
    374   }
    375 }
    376 
    377 
    378 /**
    379  * Free the state of a "/admin/add-incoming" CMD, and possibly
    380  * cancel a pending operation thereof.
    381  *
    382  * @param cls closure
    383  * @param cmd current CMD being cleaned up.
    384  */
    385 static void
    386 admin_add_incoming_cleanup (
    387   void *cls,
    388   const struct TALER_TESTING_Command *cmd)
    389 {
    390   struct AdminAddIncomingState *fts = cls;
    391 
    392   if (NULL != fts->aih)
    393   {
    394     TALER_TESTING_command_incomplete (fts->is,
    395                                       cmd->label);
    396     TALER_BANK_admin_add_incoming_cancel (fts->aih);
    397     fts->aih = NULL;
    398   }
    399   if (NULL != fts->retry_task)
    400   {
    401     GNUNET_SCHEDULER_cancel (fts->retry_task);
    402     fts->retry_task = NULL;
    403   }
    404   GNUNET_free (fts);
    405 }
    406 
    407 
    408 /**
    409  * Offer internal data from a "/admin/add-incoming" CMD to other
    410  * commands.
    411  *
    412  * @param cls closure.
    413  * @param[out] ret result
    414  * @param trait name of the trait.
    415  * @param index index number of the object to offer.
    416  * @return #GNUNET_OK on success.
    417  */
    418 static enum GNUNET_GenericReturnValue
    419 admin_add_incoming_traits (void *cls,
    420                            const void **ret,
    421                            const char *trait,
    422                            unsigned int index)
    423 {
    424   struct AdminAddIncomingState *fts = cls;
    425   static struct TALER_FullPayto void_uri = {
    426     .full_payto = (char *) "payto://void/the-exchange?receiver=name=exchange"
    427   };
    428 
    429   if (MHD_HTTP_OK !=
    430       fts->expected_http_status)
    431     return GNUNET_NO; /* requests that failed generate no history */
    432   if (fts->reserve_priv_known)
    433   {
    434     struct TALER_TESTING_Trait traits[] = {
    435       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
    436       TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
    437       TALER_TESTING_make_trait_full_payto_uri (&fts->payto_debit_account),
    438       /* Used as a marker, content does not matter */
    439       TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
    440       TALER_TESTING_make_trait_exchange_bank_account_url (
    441         fts->exchange_credit_url),
    442       TALER_TESTING_make_trait_amount (&fts->amount),
    443       TALER_TESTING_make_trait_timestamp (0,
    444                                           &fts->timestamp),
    445       TALER_TESTING_make_trait_reserve_priv (
    446         &fts->account_priv.reserve_priv),
    447       TALER_TESTING_make_trait_reserve_pub (
    448         &fts->account_pub.reserve_pub),
    449       TALER_TESTING_make_trait_account_priv (
    450         &fts->account_priv),
    451       TALER_TESTING_make_trait_account_pub (
    452         &fts->account_pub),
    453       TALER_TESTING_make_trait_reserve_history (0,
    454                                                 &fts->reserve_history),
    455       TALER_TESTING_trait_end ()
    456     };
    457 
    458     return TALER_TESTING_get_trait (traits,
    459                                     ret,
    460                                     trait,
    461                                     index);
    462   }
    463   else
    464   {
    465     struct TALER_TESTING_Trait traits[] = {
    466       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
    467       TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
    468       /* Used as a marker, content does not matter */
    469       TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
    470       TALER_TESTING_make_trait_exchange_bank_account_url (
    471         fts->exchange_credit_url),
    472       TALER_TESTING_make_trait_amount (&fts->amount),
    473       TALER_TESTING_make_trait_timestamp (0,
    474                                           &fts->timestamp),
    475       TALER_TESTING_make_trait_reserve_pub (
    476         &fts->account_pub.reserve_pub),
    477       TALER_TESTING_make_trait_account_pub (
    478         &fts->account_pub),
    479       TALER_TESTING_make_trait_reserve_history (
    480         0,
    481         &fts->reserve_history),
    482       TALER_TESTING_trait_end ()
    483     };
    484 
    485     return TALER_TESTING_get_trait (traits,
    486                                     ret,
    487                                     trait,
    488                                     index);
    489   }
    490 }
    491 
    492 
    493 /**
    494  * Create internal state for "/admin/add-incoming" CMD.
    495  *
    496  * @param amount the amount to transfer.
    497  * @param payto_debit_account which account sends money
    498  * @param auth authentication data
    499  * @return the internal state
    500  */
    501 static struct AdminAddIncomingState *
    502 make_fts (const char *amount,
    503           const struct TALER_BANK_AuthenticationData *auth,
    504           const struct TALER_FullPayto payto_debit_account)
    505 {
    506   struct AdminAddIncomingState *fts;
    507 
    508   fts = GNUNET_new (struct AdminAddIncomingState);
    509   fts->exchange_credit_url = auth->wire_gateway_url;
    510   fts->payto_debit_account = payto_debit_account;
    511   fts->auth = *auth;
    512   fts->expected_http_status = MHD_HTTP_OK;
    513   if (GNUNET_OK !=
    514       TALER_string_to_amount (amount,
    515                               &fts->amount))
    516   {
    517     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    518                 "Failed to parse amount `%s'\n",
    519                 amount);
    520     GNUNET_assert (0);
    521   }
    522   return fts;
    523 }
    524 
    525 
    526 /**
    527  * Helper function to create admin/add-incoming command.
    528  *
    529  * @param label command label.
    530  * @param fts internal state to use
    531  * @return the command.
    532  */
    533 static struct TALER_TESTING_Command
    534 make_command (const char *label,
    535               struct AdminAddIncomingState *fts)
    536 {
    537   struct TALER_TESTING_Command cmd = {
    538     .cls = fts,
    539     .label = label,
    540     .run = &admin_add_incoming_run,
    541     .cleanup = &admin_add_incoming_cleanup,
    542     .traits = &admin_add_incoming_traits
    543   };
    544 
    545   return cmd;
    546 }
    547 
    548 
    549 struct TALER_TESTING_Command
    550 TALER_TESTING_cmd_admin_add_incoming (
    551   const char *label,
    552   const char *amount,
    553   const struct TALER_BANK_AuthenticationData *auth,
    554   const struct TALER_FullPayto payto_debit_account)
    555 {
    556   return make_command (label,
    557                        make_fts (amount,
    558                                  auth,
    559                                  payto_debit_account));
    560 }
    561 
    562 
    563 struct TALER_TESTING_Command
    564 TALER_TESTING_cmd_admin_add_incoming_with_ref (
    565   const char *label,
    566   const char *amount,
    567   const struct TALER_BANK_AuthenticationData *auth,
    568   const struct TALER_FullPayto payto_debit_account,
    569   const char *ref,
    570   unsigned int http_status)
    571 {
    572   struct AdminAddIncomingState *fts;
    573 
    574   fts = make_fts (amount,
    575                   auth,
    576                   payto_debit_account);
    577   fts->reserve_reference = ref;
    578   fts->expected_http_status = http_status;
    579   return make_command (label,
    580                        fts);
    581 }
    582 
    583 
    584 struct TALER_TESTING_Command
    585 TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd)
    586 {
    587   struct AdminAddIncomingState *fts;
    588 
    589   GNUNET_assert (&admin_add_incoming_run == cmd.run);
    590   fts = cmd.cls;
    591   fts->do_retry = NUM_RETRIES;
    592   return cmd;
    593 }
    594 
    595 
    596 /* end of testing_api_cmd_bank_admin_add_incoming.c */