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

Generated by: LCOV version 2.0-1