LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_admin_add_incoming.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 91 172 52.9 %
Date: 2025-06-05 21:03:14 Functions: 8 10 80.0 %

          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 "platform.h"
      26             : #include "backoff.h"
      27             : #include "taler_json_lib.h"
      28             : #include <gnunet/gnunet_curl_lib.h>
      29             : #include "taler_bank_service.h"
      30             : #include "taler_signatures.h"
      31             : #include "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 1.16