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


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