LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_admin_add_incoming.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 52.9 % 172 91
Test Date: 2026-01-18 12:54:31 Functions: 80.0 % 10 8

            Line data    Source code
       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            0 : do_retry (void *cls)
     166              : {
     167            0 :   struct AdminAddIncomingState *fts = cls;
     168              : 
     169            0 :   fts->retry_task = NULL;
     170            0 :   TALER_TESTING_touch_cmd (fts->is);
     171            0 :   admin_add_incoming_run (fts,
     172              :                           NULL,
     173              :                           fts->is);
     174            0 : }
     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           60 : confirmation_cb (void *cls,
     187              :                  const struct TALER_BANK_AdminAddIncomingResponse *air)
     188              : {
     189           60 :   struct AdminAddIncomingState *fts = cls;
     190           60 :   struct TALER_TESTING_Interpreter *is = fts->is;
     191              : 
     192           60 :   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           60 :   if (-1 == (int) fts->expected_http_status)
     209              :   {
     210            2 :     TALER_TESTING_interpreter_next (is);
     211            2 :     return;
     212              :   }
     213           58 :   if (air->http_status != fts->expected_http_status)
     214              :   {
     215            0 :     TALER_TESTING_unexpected_status (is,
     216              :                                      air->http_status,
     217              :                                      fts->expected_http_status);
     218            0 :     return;
     219              :   }
     220           58 :   switch (air->http_status)
     221              :   {
     222           58 :   case MHD_HTTP_OK:
     223              :     fts->reserve_history.details.in_details.timestamp
     224           58 :       = air->details.ok.timestamp;
     225              :     fts->reserve_history.details.in_details.wire_reference
     226           58 :       = air->details.ok.serial_id;
     227              :     fts->serial_id
     228           58 :       = air->details.ok.serial_id;
     229              :     fts->timestamp
     230           58 :       = air->details.ok.timestamp;
     231           58 :     TALER_TESTING_interpreter_next (is);
     232           58 :     return;
     233            0 :   case MHD_HTTP_UNAUTHORIZED:
     234            0 :     switch (fts->auth.method)
     235              :     {
     236            0 :     case TALER_BANK_AUTH_NONE:
     237            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     238              :                   "Authentication required, but none configure.\n");
     239            0 :       break;
     240            0 :     case TALER_BANK_AUTH_BASIC:
     241            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     242              :                   "Basic authentication (%s) failed.\n",
     243              :                   fts->auth.details.basic.username);
     244            0 :       break;
     245            0 :     case TALER_BANK_AUTH_BEARER:
     246            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     247              :                   "Bearer authentication (%s) failed.\n",
     248              :                   fts->auth.details.bearer.token);
     249            0 :       break;
     250              :     }
     251            0 :     break;
     252            0 :   case MHD_HTTP_CONFLICT:
     253            0 :     TALER_TESTING_interpreter_next (is);
     254            0 :     return;
     255            0 :   default:
     256            0 :     if (0 != fts->do_retry)
     257              :     {
     258            0 :       fts->do_retry--;
     259            0 :       if ( (0 == air->http_status) ||
     260            0 :            (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec) ||
     261            0 :            (MHD_HTTP_INTERNAL_SERVER_ERROR == air->http_status) )
     262              :       {
     263            0 :         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            0 :         if (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec)
     270            0 :           fts->backoff = GNUNET_TIME_UNIT_ZERO;
     271              :         else
     272            0 :           fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
     273              :                                                          MAX_BACKOFF);
     274            0 :         TALER_TESTING_inc_tries (fts->is);
     275            0 :         fts->retry_task = GNUNET_SCHEDULER_add_delayed (
     276              :           fts->backoff,
     277              :           &do_retry,
     278              :           fts);
     279            0 :         return;
     280              :       }
     281              :     }
     282            0 :     break;
     283              :   }
     284            0 :   GNUNET_break (0);
     285            0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     286              :               "Bank returned HTTP status %u/%d\n",
     287              :               air->http_status,
     288              :               (int) air->ec);
     289            0 :   TALER_TESTING_interpreter_fail (is);
     290              : }
     291              : 
     292              : 
     293              : static void
     294           60 : admin_add_incoming_run (
     295              :   void *cls,
     296              :   const struct TALER_TESTING_Command *cmd,
     297              :   struct TALER_TESTING_Interpreter *is)
     298              : {
     299           60 :   struct AdminAddIncomingState *fts = cls;
     300           60 :   bool have_public = false;
     301              : 
     302              :   (void) cmd;
     303           60 :   fts->is = is;
     304              :   /* Use reserve public key as subject */
     305           60 :   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            2 :     ref = TALER_TESTING_interpreter_lookup_command (
     312              :       is,
     313              :       fts->reserve_reference);
     314            2 :     if (NULL == ref)
     315              :     {
     316            0 :       GNUNET_break (0);
     317            0 :       TALER_TESTING_interpreter_fail (is);
     318            0 :       return;
     319              :     }
     320            2 :     if (GNUNET_OK !=
     321            2 :         TALER_TESTING_get_trait_reserve_priv (ref,
     322              :                                               &reserve_priv))
     323              :     {
     324            0 :       if (GNUNET_OK !=
     325            0 :           TALER_TESTING_get_trait_reserve_pub (ref,
     326              :                                                &reserve_pub))
     327              :       {
     328            0 :         GNUNET_break (0);
     329            0 :         TALER_TESTING_interpreter_fail (is);
     330            0 :         return;
     331              :       }
     332            0 :       have_public = true;
     333              :       fts->account_pub.reserve_pub.eddsa_pub
     334            0 :         = reserve_pub->eddsa_pub;
     335            0 :       fts->reserve_priv_known = false;
     336              :     }
     337              :     else
     338              :     {
     339              :       fts->account_priv.reserve_priv.eddsa_priv
     340            2 :         = reserve_priv->eddsa_priv;
     341            2 :       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           58 :     GNUNET_CRYPTO_eddsa_key_create (
     349              :       &fts->account_priv.reserve_priv.eddsa_priv);
     350           58 :     fts->reserve_priv_known = true;
     351              :   }
     352           60 :   if (! have_public)
     353           60 :     GNUNET_CRYPTO_eddsa_key_get_public (
     354           60 :       &fts->account_priv.reserve_priv.eddsa_priv,
     355              :       &fts->account_pub.reserve_pub.eddsa_pub);
     356           60 :   fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
     357           60 :   fts->reserve_history.amount = fts->amount;
     358              :   fts->reserve_history.details.in_details.sender_url
     359           60 :     = fts->payto_debit_account; /* remember to NOT free this one... */
     360              :   fts->aih
     361           60 :     = TALER_BANK_admin_add_incoming (
     362              :         TALER_TESTING_interpreter_get_context (is),
     363           60 :         &fts->auth,
     364           60 :         &fts->account_pub.reserve_pub,
     365           60 :         &fts->amount,
     366              :         fts->payto_debit_account,
     367              :         &confirmation_cb,
     368              :         fts);
     369           60 :   if (NULL == fts->aih)
     370              :   {
     371            0 :     GNUNET_break (0);
     372            0 :     TALER_TESTING_interpreter_fail (is);
     373            0 :     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           60 : admin_add_incoming_cleanup (
     387              :   void *cls,
     388              :   const struct TALER_TESTING_Command *cmd)
     389              : {
     390           60 :   struct AdminAddIncomingState *fts = cls;
     391              : 
     392           60 :   if (NULL != fts->aih)
     393              :   {
     394            0 :     TALER_TESTING_command_incomplete (fts->is,
     395              :                                       cmd->label);
     396            0 :     TALER_BANK_admin_add_incoming_cancel (fts->aih);
     397            0 :     fts->aih = NULL;
     398              :   }
     399           60 :   if (NULL != fts->retry_task)
     400              :   {
     401            0 :     GNUNET_SCHEDULER_cancel (fts->retry_task);
     402            0 :     fts->retry_task = NULL;
     403              :   }
     404           60 :   GNUNET_free (fts);
     405           60 : }
     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          325 : admin_add_incoming_traits (void *cls,
     420              :                            const void **ret,
     421              :                            const char *trait,
     422              :                            unsigned int index)
     423              : {
     424          325 :   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          325 :   if (MHD_HTTP_OK !=
     430          325 :       fts->expected_http_status)
     431            6 :     return GNUNET_NO; /* requests that failed generate no history */
     432          319 :   if (fts->reserve_priv_known)
     433              :   {
     434              :     struct TALER_TESTING_Trait traits[] = {
     435          319 :       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
     436          319 :       TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
     437          319 :       TALER_TESTING_make_trait_full_payto_uri (&fts->payto_debit_account),
     438              :       /* Used as a marker, content does not matter */
     439          319 :       TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
     440          319 :       TALER_TESTING_make_trait_exchange_bank_account_url (
     441              :         fts->exchange_credit_url),
     442          319 :       TALER_TESTING_make_trait_amount (&fts->amount),
     443          319 :       TALER_TESTING_make_trait_timestamp (0,
     444          319 :                                           &fts->timestamp),
     445          319 :       TALER_TESTING_make_trait_reserve_priv (
     446          319 :         &fts->account_priv.reserve_priv),
     447          319 :       TALER_TESTING_make_trait_reserve_pub (
     448          319 :         &fts->account_pub.reserve_pub),
     449          319 :       TALER_TESTING_make_trait_account_priv (
     450          319 :         &fts->account_priv),
     451          319 :       TALER_TESTING_make_trait_account_pub (
     452          319 :         &fts->account_pub),
     453          319 :       TALER_TESTING_make_trait_reserve_history (0,
     454          319 :                                                 &fts->reserve_history),
     455          319 :       TALER_TESTING_trait_end ()
     456              :     };
     457              : 
     458          319 :     return TALER_TESTING_get_trait (traits,
     459              :                                     ret,
     460              :                                     trait,
     461              :                                     index);
     462              :   }
     463              :   else
     464              :   {
     465              :     struct TALER_TESTING_Trait traits[] = {
     466            0 :       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
     467            0 :       TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
     468              :       /* Used as a marker, content does not matter */
     469            0 :       TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
     470            0 :       TALER_TESTING_make_trait_exchange_bank_account_url (
     471              :         fts->exchange_credit_url),
     472            0 :       TALER_TESTING_make_trait_amount (&fts->amount),
     473            0 :       TALER_TESTING_make_trait_timestamp (0,
     474            0 :                                           &fts->timestamp),
     475            0 :       TALER_TESTING_make_trait_reserve_pub (
     476            0 :         &fts->account_pub.reserve_pub),
     477            0 :       TALER_TESTING_make_trait_account_pub (
     478            0 :         &fts->account_pub),
     479            0 :       TALER_TESTING_make_trait_reserve_history (
     480              :         0,
     481            0 :         &fts->reserve_history),
     482            0 :       TALER_TESTING_trait_end ()
     483              :     };
     484              : 
     485            0 :     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           60 : 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           60 :   fts = GNUNET_new (struct AdminAddIncomingState);
     509           60 :   fts->exchange_credit_url = auth->wire_gateway_url;
     510           60 :   fts->payto_debit_account = payto_debit_account;
     511           60 :   fts->auth = *auth;
     512           60 :   fts->expected_http_status = MHD_HTTP_OK;
     513           60 :   if (GNUNET_OK !=
     514           60 :       TALER_string_to_amount (amount,
     515              :                               &fts->amount))
     516              :   {
     517            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     518              :                 "Failed to parse amount `%s'\n",
     519              :                 amount);
     520            0 :     GNUNET_assert (0);
     521              :   }
     522           60 :   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           60 : make_command (const char *label,
     535              :               struct AdminAddIncomingState *fts)
     536              : {
     537           60 :   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           60 :   return cmd;
     546              : }
     547              : 
     548              : 
     549              : struct TALER_TESTING_Command
     550           58 : 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           58 :   return make_command (label,
     557              :                        make_fts (amount,
     558              :                                  auth,
     559              :                                  payto_debit_account));
     560              : }
     561              : 
     562              : 
     563              : struct TALER_TESTING_Command
     564            2 : 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            2 :   fts = make_fts (amount,
     575              :                   auth,
     576              :                   payto_debit_account);
     577            2 :   fts->reserve_reference = ref;
     578            2 :   fts->expected_http_status = http_status;
     579            2 :   return make_command (label,
     580              :                        fts);
     581              : }
     582              : 
     583              : 
     584              : struct TALER_TESTING_Command
     585            0 : TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd)
     586              : {
     587              :   struct AdminAddIncomingState *fts;
     588              : 
     589            0 :   GNUNET_assert (&admin_add_incoming_run == cmd.run);
     590            0 :   fts = cmd.cls;
     591            0 :   fts->do_retry = NUM_RETRIES;
     592            0 :   return cmd;
     593              : }
     594              : 
     595              : 
     596              : /* end of testing_api_cmd_bank_admin_add_incoming.c */
        

Generated by: LCOV version 2.0-1