diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-01-12 20:44:33 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-01-12 20:44:33 +0100 |
commit | 1788ca2be11b92f9c92d8b7ad31383f663608ac0 (patch) | |
tree | 92e5159f5ea21e802c6dcc5a5ff38c4e186337f3 /src/lib | |
parent | c75157e8caae542845cce2f9ff967d2b3943ea56 (diff) | |
download | exchange-1788ca2be11b92f9c92d8b7ad31383f663608ac0.tar.xz |
reorganization of file structure
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Makefile.am | 145 | ||||
-rw-r--r-- | src/lib/bank.conf | 13 | ||||
-rw-r--r-- | src/lib/bank_twisted.conf | 39 | ||||
-rw-r--r-- | src/lib/test_bank_api_twisted.c | 222 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_auditor_deposit_confirmation.c (renamed from src/lib/testing_auditor_api_cmd_deposit_confirmation.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_auditor_exchanges.c (renamed from src/lib/testing_auditor_api_cmd_exchanges.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_auditor_exec_auditor.c (renamed from src/lib/testing_auditor_api_cmd_exec_auditor.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_auditor_exec_auditor_dbinit.c (renamed from src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_auditor_exec_wire_auditor.c (renamed from src/lib/testing_auditor_api_cmd_exec_wire_auditor.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_bank_admin_add_incoming.c | 609 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_bank_history_credit.c | 765 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_bank_history_debit.c | 766 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_bank_transfer.c | 394 | ||||
-rw-r--r-- | src/lib/testing_api_helpers_auditor.c (renamed from src/lib/testing_auditor_api_helpers.c) | 0 | ||||
-rw-r--r-- | src/lib/testing_api_helpers_bank.c | 361 | ||||
-rw-r--r-- | src/lib/testing_api_helpers_exchange.c (renamed from src/lib/testing_api_helpers.c) | 0 |
16 files changed, 3258 insertions, 56 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index d46506600..231e049a0 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -9,8 +9,7 @@ endif lib_LTLIBRARIES = \ libtalerauditor.la \ libtalerexchange.la \ - libtalertesting.la \ - libtalerauditortesting.la + libtalertesting.la libtalerexchange_la_LDFLAGS = \ -version-info 4:0:0 \ @@ -72,46 +71,57 @@ libtalertesting_la_LDFLAGS = \ -no-undefined libtalertesting_la_SOURCES = \ exchange_api_curl_defaults.c \ + testing_api_cmd_auditor_deposit_confirmation.c \ + testing_api_cmd_auditor_exchanges.c \ + testing_api_cmd_auditor_exec_auditor.c \ + testing_api_cmd_auditor_exec_auditor_dbinit.c \ + testing_api_cmd_auditor_exec_wire_auditor.c \ + testing_api_cmd_bank_admin_add_incoming.c \ + testing_api_cmd_bank_check.c \ + testing_api_cmd_bank_check_empty.c \ + testing_api_cmd_bank_history_credit.c \ + testing_api_cmd_bank_history_debit.c \ + testing_api_cmd_bank_transfer.c \ + testing_api_cmd_batch.c \ + testing_api_cmd_check_keys.c \ + testing_api_cmd_deposit.c \ testing_api_cmd_exec_aggregator.c \ testing_api_cmd_exec_wirewatch.c \ testing_api_cmd_exec_keyup.c \ testing_api_cmd_exec_auditor-sign.c \ - testing_api_cmd_withdraw.c \ - testing_api_cmd_wire.c \ + testing_api_cmd_payback.c \ testing_api_cmd_refund.c \ - testing_api_cmd_status.c \ - testing_api_cmd_deposit.c \ - testing_api_cmd_sleep.c \ testing_api_cmd_refresh.c \ - testing_api_cmd_track.c \ - testing_api_cmd_bank_check.c \ - testing_api_cmd_bank_check_empty.c \ - testing_api_cmd_payback.c \ - testing_api_cmd_signal.c \ - testing_api_cmd_check_keys.c \ - testing_api_cmd_batch.c \ testing_api_cmd_serialize_keys.c \ - testing_api_helpers.c \ + testing_api_cmd_signal.c \ + testing_api_cmd_sleep.c \ + testing_api_cmd_status.c \ + testing_api_cmd_track.c \ + testing_api_cmd_wire.c \ + testing_api_cmd_withdraw.c \ + testing_api_helpers_auditor.c \ + testing_api_helpers_bank.c \ + testing_api_helpers_exchange.c \ testing_api_loop.c \ testing_api_traits.c \ + testing_api_trait_amount.c \ testing_api_trait_blinding_key.c \ + testing_api_trait_cmd.c \ testing_api_trait_coin_priv.c \ testing_api_trait_denom_pub.c \ testing_api_trait_denom_sig.c \ testing_api_trait_exchange_pub.c \ testing_api_trait_exchange_sig.c \ + testing_api_trait_fresh_coin.c \ testing_api_trait_json.c \ + testing_api_trait_key_peer.c \ + testing_api_trait_number.c \ testing_api_trait_process.c \ testing_api_trait_reserve_pub.c \ testing_api_trait_reserve_priv.c \ - testing_api_trait_number.c \ - testing_api_trait_fresh_coin.c \ testing_api_trait_string.c \ - testing_api_trait_key_peer.c \ - testing_api_trait_wtid.c \ - testing_api_trait_amount.c \ - testing_api_trait_cmd.c \ - testing_api_trait_time.c + testing_api_trait_time.c \ + testing_api_trait_wtid.c libtalertesting_la_LIBADD = \ libtalerexchange.la \ $(top_builddir)/src/wire/libtalerwire.la \ @@ -125,30 +135,6 @@ libtalertesting_la_LIBADD = \ -ljansson \ $(XLIB) -libtalerauditortesting_la_LDFLAGS = \ - -version-info 0:0:0 \ - -no-undefined -libtalerauditortesting_la_SOURCES = \ - testing_auditor_api_helpers.c \ - testing_auditor_api_cmd_deposit_confirmation.c \ - testing_auditor_api_cmd_exchanges.c \ - testing_auditor_api_cmd_exec_auditor.c \ - testing_auditor_api_cmd_exec_auditor_dbinit.c \ - testing_auditor_api_cmd_exec_wire_auditor.c -libtalerauditortesting_la_LIBADD = \ - libtalerauditor.la \ - libtalerexchange.la \ - libtalertesting.la \ - $(top_builddir)/src/wire/libtalerwire.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetjson \ - -lgnunetutil \ - -ljansson \ - $(XLIB) - - if HAVE_LIBCURL libtalerexchange_la_LIBADD += -lcurl else @@ -158,15 +144,36 @@ endif endif check_PROGRAMS = \ - test_exchange_api_keys_cherry_picking \ - test_exchange_api_overlapping_keys_bug \ - test_exchange_api \ test_auditor_api_version \ - test_auditor_api + test_auditor_api \ + test_bank_api_with_fakebank \ + test_bank_api_with_pybank \ + test_exchange_api \ + test_exchange_api_keys_cherry_picking \ + test_exchange_api_overlapping_keys_bug if HAVE_TWISTER check_PROGRAMS += \ - test_exchange_api_twisted + test_exchange_api_twisted \ + test_bank_api_with_pybank_twisted \ + test_bank_api_with_fakebank_twisted +endif + +test_bank_api_with_pybank_SOURCES = \ + test_bank_api.c +test_bank_api_with_pybank_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + -lgnunetutil \ + $(top_builddir)/src/bank-lib/libtalerbank.la + +test_bank_api_with_fakebank_SOURCES = \ + test_bank_api.c +test_bank_api_with_fakebank_LDADD = \ + $(top_builddir)/src/lib/libtalertesting.la \ + -ltalerexchange \ + -lgnunetutil \ + libtalerbank.la test_exchange_api_twisted_SOURCES = \ test_exchange_api_twisted.c @@ -175,7 +182,6 @@ test_exchange_api_twisted_LDADD = \ libtalertesting.la \ libtalerexchange.la \ $(top_builddir)/src/bank-lib/libtalerfakebank.la \ - $(top_builddir)/src/bank-lib/libtalerbanktesting.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ @@ -185,7 +191,35 @@ test_exchange_api_twisted_LDADD = \ -lgnunetutil \ -ljansson -endif +test_bank_api_with_fakebank_twisted_SOURCES = \ + test_bank_api_twisted.c +test_bank_api_with_fakebank_twisted_LDADD = \ + $(top_builddir)/src/lib/libtalertesting.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/lib/libtalerexchange.la \ + $(top_builddir)/src/json/libtalerjson.la \ + -ltalertwistertesting \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_bank_api_with_pybank_twisted_SOURCES = \ + test_bank_api_twisted.c +test_bank_api_with_pybank_twisted_LDADD = \ + $(top_builddir)/src/lib/libtalertesting.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/lib/libtalerexchange.la \ + $(top_builddir)/src/json/libtalerjson.la \ + -ltalertwistertesting \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + + AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; @@ -200,7 +234,6 @@ test_exchange_api_LDADD = \ $(LIBGCRYPT_LIBS) \ $(top_builddir)/src/bank-lib/libtalerfakebank.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/bank-lib/libtalerbanktesting.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ -lgnunetcurl \ @@ -236,14 +269,12 @@ test_exchange_api_keys_cherry_picking_LDADD = \ test_auditor_api_SOURCES = \ test_auditor_api.c test_auditor_api_LDADD = \ - libtalerauditortesting.la \ libtalerauditor.la \ libtalertesting.la \ libtalerexchange.la \ $(LIBGCRYPT_LIBS) \ $(top_builddir)/src/bank-lib/libtalerfakebank.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/bank-lib/libtalerbanktesting.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ -lgnunetcurl \ @@ -264,6 +295,8 @@ test_auditor_api_version_LDADD = \ EXTRA_DIST = \ + bank.conf \ + bank_twisted.conf \ test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ test_exchange_api_home/.config/taler/account-2.json \ test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json \ diff --git a/src/lib/bank.conf b/src/lib/bank.conf new file mode 100644 index 000000000..906b95fc5 --- /dev/null +++ b/src/lib/bank.conf @@ -0,0 +1,13 @@ +[taler] +currency = KUDOS + +[account-1] +URL = payto://x-taler-bank/localhost:8081/1 + +[bank] +SERVE = http +HTTP_PORT = 8081 +DATABASE = postgres:///talercheck + +[exchange-wire-test] +bank_url = http://localhost:8081/ diff --git a/src/lib/bank_twisted.conf b/src/lib/bank_twisted.conf new file mode 100644 index 000000000..71bcaee00 --- /dev/null +++ b/src/lib/bank_twisted.conf @@ -0,0 +1,39 @@ + +[twister] +# HTTP listen port for twister +HTTP_PORT = 8888 +SERVE = tcp + +# HTTP Destination for twister. The test-Webserver needs +# to listen on the port used here. Note: no trailing '/'! +DESTINATION_BASE_URL = "http://localhost:8081" + +# Control port for TCP +# PORT = 8889 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +# Control port for UNIX +UNIXPATH = /tmp/taler-service-twister.sock +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES + +# Launching of twister by ARM +# BINARY = taler-service-twister +# AUTOSTART = NO +# FORCESTART = NO + +[taler] +currency = KUDOS + +[bank] +serve = http +http_port = 8081 +database = postgres:///talercheck + +[account-1] +URL = payto://x-taler-bank/localhost:8081/1 + +[exchange-wire-test] +bank_url = http://localhost:8081/ diff --git a/src/lib/test_bank_api_twisted.c b/src/lib/test_bank_api_twisted.c new file mode 100644 index 000000000..ad8fd71d4 --- /dev/null +++ b/src/lib/test_bank_api_twisted.c @@ -0,0 +1,222 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange/test_bank_api_with_fakebank_twisted.c + * @author Marcello Stanisci + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" +#include <taler/taler_twister_testing_lib.h> +#include "taler_testing_bank_lib.h" +#include <taler/taler_twister_service.h> + +/** + * Configuration file we use. One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "bank_twisted.conf" + +/** + * True when the test runs against Fakebank. + */ +static int with_fakebank; + +/** + * (real) Twister URL. Used at startup time to check if it runs. + */ +static char *twister_url; + +/** + * Account URL of the twister where all the connections to the + * bank that have to be proxied should be addressed to. + */ +static char *twisted_account_url; + +/** + * Authentication data to use. + */ +static struct TALER_BANK_AuthenticationData auth; + +/** + * URL of the bank. + */ +static char *bank_url; + +/** + * Twister process. + */ +static struct GNUNET_OS_Process *twisterd; + +/** + * Python bank process handle. + */ +static struct GNUNET_OS_Process *bankd; + + +/** + * Main function that will tell + * the interpreter what commands to run. + * + * @param cls closure + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + struct TALER_TESTING_Command commands[] = { + /** + * Can't use the "wait service" CMD here because the + * fakebank runs inside the same process of the test. + */ + TALER_TESTING_cmd_wait_service ("wait-service", + twister_url), + TALER_TESTING_cmd_bank_credits ("history-0", + twisted_account_url, + &auth, + NULL, + 5), + TALER_TESTING_cmd_end () + }; + + GNUNET_asprintf (&twisted_account_url, + "%s/%s", + twister_url, + "alice"); + // FIXME: init 'auth'! + if (GNUNET_YES == with_fakebank) + TALER_TESTING_run_with_fakebank (is, + commands, + bank_url); + else + TALER_TESTING_run (is, + commands); +} + + +/** + * Kill, wait, and destroy convenience function. + * + * @param process process to purge. + */ +static void +purge_process (struct GNUNET_OS_Process *process) +{ + GNUNET_OS_process_kill (process, SIGINT); + GNUNET_OS_process_wait (process); + GNUNET_OS_process_destroy (process); +} + + +int +main (int argc, + char *const *argv) +{ + unsigned int ret; + + /* These environment variables get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-bank-api-with-(fake)bank-twisted", + "DEBUG", + NULL); + if (NULL == (twister_url = TALER_TESTING_prepare_twister + (CONFIG_FILE))) + { + GNUNET_break (0); + return 77; + } + if (NULL == (twisterd = TALER_TESTING_run_twister (CONFIG_FILE))) + { + GNUNET_break (0); + GNUNET_free (twister_url); + return 77; + } + + with_fakebank = TALER_TESTING_has_in_name (argv[0], + "_with_fakebank"); + + if (GNUNET_YES == with_fakebank) + { + TALER_LOG_DEBUG ("Running against the Fakebank.\n"); + if (NULL == (bank_url = TALER_TESTING_prepare_fakebank + (CONFIG_FILE, + "account-1"))) + { + GNUNET_break (0); + GNUNET_free (twister_url); + return 77; + } + } + else + { + TALER_LOG_DEBUG ("Running against the Pybank.\n"); + if (NULL == (bank_url = TALER_TESTING_prepare_bank + (CONFIG_FILE))) + { + GNUNET_break (0); + GNUNET_free (twister_url); + return 77; + } + + if (NULL == (bankd = TALER_TESTING_run_bank (CONFIG_FILE, + bank_url))) + { + GNUNET_break (0); + GNUNET_free (twister_url); + GNUNET_free (bank_url); + return 77; + } + } + + ret = TALER_TESTING_setup (&run, + NULL, + CONFIG_FILE, + NULL, + GNUNET_NO); + purge_process (twisterd); + + if (GNUNET_NO == with_fakebank) + { + GNUNET_OS_process_kill (bankd, + SIGKILL); + GNUNET_OS_process_wait (bankd); + GNUNET_OS_process_destroy (bankd); + } + + GNUNET_free (twister_url); + GNUNET_free (bank_url); + + if (GNUNET_OK == ret) + return 0; + + return 1; +} + + +/* end of test_bank_api_twisted.c */ diff --git a/src/lib/testing_auditor_api_cmd_deposit_confirmation.c b/src/lib/testing_api_cmd_auditor_deposit_confirmation.c index 3ea6390d8..3ea6390d8 100644 --- a/src/lib/testing_auditor_api_cmd_deposit_confirmation.c +++ b/src/lib/testing_api_cmd_auditor_deposit_confirmation.c diff --git a/src/lib/testing_auditor_api_cmd_exchanges.c b/src/lib/testing_api_cmd_auditor_exchanges.c index 9cc07553d..9cc07553d 100644 --- a/src/lib/testing_auditor_api_cmd_exchanges.c +++ b/src/lib/testing_api_cmd_auditor_exchanges.c diff --git a/src/lib/testing_auditor_api_cmd_exec_auditor.c b/src/lib/testing_api_cmd_auditor_exec_auditor.c index cff234691..cff234691 100644 --- a/src/lib/testing_auditor_api_cmd_exec_auditor.c +++ b/src/lib/testing_api_cmd_auditor_exec_auditor.c diff --git a/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c b/src/lib/testing_api_cmd_auditor_exec_auditor_dbinit.c index 3b7e3aa0b..3b7e3aa0b 100644 --- a/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c +++ b/src/lib/testing_api_cmd_auditor_exec_auditor_dbinit.c diff --git a/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c b/src/lib/testing_api_cmd_auditor_exec_wire_auditor.c index 744420d3f..744420d3f 100644 --- a/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c +++ b/src/lib/testing_api_cmd_auditor_exec_wire_auditor.c diff --git a/src/lib/testing_api_cmd_bank_admin_add_incoming.c b/src/lib/testing_api_cmd_bank_admin_add_incoming.c new file mode 100644 index 000000000..770b2e384 --- /dev/null +++ b/src/lib/testing_api_cmd_bank_admin_add_incoming.c @@ -0,0 +1,609 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_cmd_admin_add_incoming.c + * @brief implementation of a bank /admin/add-incoming command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "backoff.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" + + +/** + * State for a "fakebank transfer" CMD. + */ +struct AdminAddIncomingState +{ + + /** + * Label of any command that can trait-offer a reserve priv. + */ + const char *reserve_reference; + + /** + * Wire transfer amount. + */ + struct TALER_Amount amount; + + /** + * Base URL of the credited account. + */ + const char *exchange_credit_url; + + /** + * Money sender account URL. + */ + const char *payto_debit_account; + + /** + * Username to use for authentication. + */ + struct TALER_BANK_AuthenticationData auth; + + /** + * Set (by the interpreter) to the reserve's private key + * we used to make a wire transfer subject line with. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Reserve public key matching @e reserve_priv. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Handle to the pending request at the fakebank. + */ + struct TALER_BANK_AdminAddIncomingHandle *aih; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Set to the wire transfer's unique ID. + */ + uint64_t serial_id; + + /** + * Timestamp of the transaction (as returned from the bank). + */ + struct GNUNET_TIME_Absolute timestamp; + + /** + * Merchant instance. Sometimes used to get the tip reserve + * private key by reading the appropriate config section. + */ + const char *instance; + + /** + * Configuration filename. Used to get the tip reserve key + * filename (used to obtain a public key to write in the + * transfer subject). + */ + const char *config_filename; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Was this command modified via + * #TALER_TESTING_cmd_admin_add_incoming_with_retry to + * enable retries? + */ + int do_retry; +}; + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +admin_add_incoming_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #admin_add_incoming_run. + * + * @param cls a `struct AdminAddIncomingState` + */ +static void +do_retry (void *cls) +{ + struct AdminAddIncomingState *fts = cls; + + fts->retry_task = NULL; + admin_add_incoming_run (fts, + NULL, + fts->is); +} + + +/** + * This callback will process the fakebank response to the wire + * transfer. It just checks whether the HTTP response code is + * acceptable. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request; 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param serial_id unique ID of the wire transfer + * @param timestamp time stamp of the transaction made. + * @param json raw response + */ +static void +confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t serial_id, + struct GNUNET_TIME_Absolute timestamp, + const json_t *json) +{ + struct AdminAddIncomingState *fts = cls; + struct TALER_TESTING_Interpreter *is = fts->is; + + fts->aih = NULL; + if (MHD_HTTP_OK != http_status) + { + if (GNUNET_YES == fts->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log + (GNUNET_ERROR_TYPE_INFO, + "Retrying fakebank transfer failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + fts->backoff = GNUNET_TIME_UNIT_ZERO; + else + fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff); + fts->retry_task = GNUNET_SCHEDULER_add_delayed + (fts->backoff, + &do_retry, + fts); + return; + } + } + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fakebank returned HTTP status %u/%d\n", + http_status, + (int) ec); + TALER_TESTING_interpreter_fail (is); + return; + } + + fts->serial_id = serial_id; + fts->timestamp = timestamp; + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +admin_add_incoming_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AdminAddIncomingState *fts = cls; + + /* Use reserve public key as subject */ + if (NULL != fts->reserve_reference) + { + const struct TALER_TESTING_Command *ref; + const struct TALER_ReservePrivateKeyP *reserve_priv; + + ref = TALER_TESTING_interpreter_lookup_command + (is, fts->reserve_reference); + if (NULL == ref) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_priv (ref, + 0, + &reserve_priv)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv; + } + else + { + if (NULL != fts->instance) + { + char *section; + char *keys; + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + struct GNUNET_CONFIGURATION_Handle *cfg; + + GNUNET_assert (NULL != fts->config_filename); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + fts->config_filename)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_asprintf (§ion, + "instance-%s", + fts->instance); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename + (cfg, + section, + "TIP_RESERVE_PRIV_FILENAME", + &keys)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Configuration fails to specify reserve" + " private key filename in section %s\n", + section); + GNUNET_free (section); + TALER_TESTING_interpreter_fail (is); + return; + } + priv = GNUNET_CRYPTO_eddsa_key_create_from_file (keys); + GNUNET_free (keys); + if (NULL == priv) + { + GNUNET_log_config_invalid + (GNUNET_ERROR_TYPE_ERROR, + section, + "TIP_RESERVE_PRIV_FILENAME", + "Failed to read private key"); + GNUNET_free (section); + TALER_TESTING_interpreter_fail (is); + return; + } + fts->reserve_priv.eddsa_priv = *priv; + GNUNET_free (section); + GNUNET_free (priv); + GNUNET_CONFIGURATION_destroy (cfg); + } + else + { + /* No referenced reserve, no instance to take priv + * from, no explicit subject given: create new key! */ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + fts->reserve_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + } + GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv, + &fts->reserve_pub.eddsa_pub); + fts->is = is; + fts->aih + = TALER_BANK_admin_add_incoming + (TALER_TESTING_interpreter_get_context (is), + fts->exchange_credit_url, + &fts->auth, + &fts->reserve_pub, + &fts->amount, + fts->payto_debit_account, + &confirmation_cb, + fts); + if (NULL == fts->aih) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "/admin/add-incoming" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure + * @param cmd current CMD being cleaned up. + */ +static void +admin_add_incoming_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AdminAddIncomingState *fts = cls; + + if (NULL != fts->aih) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %s did not complete\n", + cmd->label); + TALER_BANK_admin_add_incoming_cancel (fts->aih); + fts->aih = NULL; + } + if (NULL != fts->retry_task) + { + GNUNET_SCHEDULER_cancel (fts->retry_task); + fts->retry_task = NULL; + } + GNUNET_free (fts); +} + + +/** + * Offer internal data from a "/admin/add-incoming" CMD to other + * commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +admin_add_incoming_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct AdminAddIncomingState *fts = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_url (1, fts->payto_debit_account), + TALER_TESTING_MAKE_TRAIT_ROW_ID (&fts->serial_id), + TALER_TESTING_MAKE_TRAIT_CREDIT_ACCOUNT (fts->exchange_credit_url), + TALER_TESTING_make_trait_amount_obj (0, &fts->amount), + TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp), + TALER_TESTING_make_trait_reserve_priv (0, + &fts->reserve_priv), + TALER_TESTING_make_trait_reserve_pub (0, + &fts->reserve_pub), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create admin/add-incoming command. + * + * @param label command label. + * @param amount amount to transfer. + * @param exchange_base_url base URL of the account that receives this + * wire transer (which account receives money). + * @param payto_debit_account which account sends money. + * @param auth authentication data + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming + (const char *label, + const char *amount, + const char *exchange_base_url, + const struct TALER_BANK_AuthenticationData *auth, + const char *payto_debit_account) +{ + struct AdminAddIncomingState *fts; + + fts = GNUNET_new (struct AdminAddIncomingState); + fts->exchange_credit_url = exchange_base_url; + fts->payto_debit_account = payto_debit_account; + fts->auth = *auth; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + { + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &admin_add_incoming_run, + .cleanup = &admin_add_incoming_cleanup, + .traits = &admin_add_incoming_traits + }; + + return cmd; + } +} + + +/** + * Create "/admin/add-incoming" CMD, letting the caller specify + * a reference to a command that can offer a reserve private key. + * This private key will then be used to construct the subject line + * of the wire transfer. + * + * @param label command label. + * @param amount the amount to transfer. + * @param account_bank_url base URL of the exchange account receiving the money + * @param payto_debit_account which account sends money + * @param auth authentication data + * @param ref reference to a command that can offer a reserve + * private key. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_with_ref + (const char *label, + const char *amount, + const char *account_base_url, + const struct TALER_BANK_AuthenticationData *auth, + const char *payto_debit_account, + const char *ref) +{ + struct AdminAddIncomingState *fts; + + fts = GNUNET_new (struct AdminAddIncomingState); + fts->exchange_credit_url = account_base_url; + fts->payto_debit_account = payto_debit_account; + fts->auth = *auth; + fts->reserve_reference = ref; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + { + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &admin_add_incoming_run, + .cleanup = &admin_add_incoming_cleanup, + .traits = &admin_add_incoming_traits + }; + + return cmd; + } +} + + +/** + * Create "/admin/add-incoming" CMD, letting the caller specifying + * the merchant instance. This version is useful when a tip + * reserve should be topped up, in fact the interpreter will need + * the "tipping instance" in order to get the instance public key + * and make a wire transfer subject out of it. + * + * @param label command label. + * @param amount amount to transfer. + * @param account_bank_url base URL of the exchange bank account + * that receives the wire transfer + * @param payto_debit_account which account (expressed as a number) + * gives money + * @param auth authentication data + * @param instance the instance that runs the tipping. Under this + * instance, the configuration file will provide the private + * key of the tipping reserve. This data will then used to + * construct the wire transfer subject line. + * @param config_filename configuration file to use. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_with_instance + (const char *label, + const char *amount, + const char *account_base_url, + const struct TALER_BANK_AuthenticationData *auth, + const char *payto_debit_account, + const char *instance, + const char *config_filename) +{ + struct AdminAddIncomingState *fts; + + fts = GNUNET_new (struct AdminAddIncomingState); + fts->exchange_credit_url = account_base_url; + fts->payto_debit_account = payto_debit_account; + fts->auth = *auth; + fts->instance = instance; + fts->config_filename = config_filename; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + { + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &admin_add_incoming_run, + .cleanup = &admin_add_incoming_cleanup, + .traits = &admin_add_incoming_traits + }; + + return cmd; + } +} + + +/** + * Modify a fakebank transfer command to enable retries when the + * reserve is not yet full or we get other transient errors from the + * fakebank. + * + * @param cmd a fakebank transfer command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd) +{ + struct AdminAddIncomingState *fts; + + GNUNET_assert (&admin_add_incoming_run == cmd.run); + fts = cmd.cls; + fts->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_api_cmd_admin_add_incoming.c */ diff --git a/src/lib/testing_api_cmd_bank_history_credit.c b/src/lib/testing_api_cmd_bank_history_credit.c new file mode 100644 index 000000000..fefb2dda7 --- /dev/null +++ b/src/lib/testing_api_cmd_bank_history_credit.c @@ -0,0 +1,765 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file bank-lib/testing_api_cmd_history.c + * @brief command to check the /history API from the bank. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ + /** + * Base URL of the account offering the "history" operation. + */ + char *account_url; + + /** + * Reference to command defining the + * first row number we want in the result. + */ + const char *start_row_reference; + + /** + * How many rows we want in the result, _at most_, + * and ascending/descending. + */ + long long num_results; + + /** + * Handle to a pending "history" operation. + */ + struct TALER_BANK_CreditHistoryHandle *hh; + + /** + * Authentication data for the operation. + */ + struct TALER_BANK_AuthenticationData auth; + + /** + * Expected number of results (= rows). + */ + uint64_t results_obtained; + + /** + * Set to GNUNET_YES if the callback detects something + * unexpected. + */ + int failed; + +}; + + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + + /** + * Wire details. + */ + struct TALER_BANK_CreditDetails details; + + /** + * Serial ID of the wire transfer. + */ + uint64_t row_id; + + /** + * URL to free. + */ + char *url; +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + (void) cls; + (void) ret; + (void) trait; + (void) index; + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Free history @a h of length @a h_len. + * + * @param h history array to free. + * @param h_len number of entries in @a h. + */ +static void +free_history (struct History *h, + uint64_t h_len) +{ + for (uint64_t off = 0; off<h_len; off++) + GNUNET_free (h[off].url); + GNUNET_free_non_null (h); +} + + +/** + * Log which history we expected. Called when an error occurs. + * + * @param h what we expected. + * @param h_len number of entries in @a h. + * @param off position of the missmatch. + */ +static void +print_expected (struct History *h, + uint64_t h_len, + unsigned int off) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Transaction history missmatch at position %u/%llu\n", + off, + (unsigned long long) h_len); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history:\n"); + for (uint64_t i = 0; i<h_len; i++) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "H(%llu): %s (serial: %llu, subject: %s," + " counterpart: %s)\n", + (unsigned long long) i, + TALER_amount2s (&h[i].details.amount), + (unsigned long long) h[i].row_id, + TALER_B2S (&h[i].details.reserve_pub), + h[i].details.account_url); + } +} + + +/** + * Tell if the current item is beyond the allowed limit. + * + * @param total current number of items in the built history list. + * Note, this is the list we build locally and compare with + * what the server returned. + * @param hs the history CMD state. + * @param pos current item to be evaluated or not (if the list + * has already enough elements). + * @return GNUNET_OK / GNUNET_NO. + */ +static int +build_history_hit_limit (uint64_t total, + const struct HistoryState *hs, + const struct TALER_TESTING_Command *pos) +{ + return total >= hs->num_results; +} + + +/** + * This function constructs the list of history elements that + * interest the account number of the caller. It has two main + * loops: the first to figure out how many history elements have + * to be allocated, and the second to actually populate every + * element. + * + * @param is interpreter state (supposedly having the + * current CMD pointing at a "history" CMD). + * @param[out] rh history array to initialize. + * + * @return number of entries in @a rh. + */ +static uint64_t +build_history (struct TALER_TESTING_Interpreter *is, + struct History **rh) +{ + struct HistoryState *hs = is->commands[is->ip].cls; + uint64_t total; + struct History *h; + const struct TALER_TESTING_Command *add_incoming_cmd; + int inc; + unsigned int start; + unsigned int end; + + /** + * @var turns GNUNET_YES whenever either no 'start' value was + * given for the history query, or the given value is found + * in the list of all the CMDs. + */int ok; + const uint64_t *row_id_start = NULL; + + if (NULL != hs->start_row_reference) + { + TALER_LOG_INFO + ("`%s': start row given via reference `%s'\n", + TALER_TESTING_interpreter_get_current_label (is), + hs->start_row_reference); + add_incoming_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + GNUNET_assert (NULL != add_incoming_cmd); + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64 + (add_incoming_cmd, 0, &row_id_start)); + } + + GNUNET_assert (0 != hs->num_results); + if (0 == is->ip) + { + TALER_LOG_DEBUG ("Checking history at first CMD..\n"); + *rh = NULL; + return 0; + } + + /* AKA 'delta'. */ + if (hs->num_results > 0) + { + inc = 1; /* _inc_rement */ + start = 0; + end = is->ip - 1; + } + else + { + inc = -1; + start = is->ip - 1; + end = 0; + } + + total = 0; + ok = GNUNET_NO; + + if (NULL == row_id_start) + ok = GNUNET_YES; + + /* This loop counts how many commands _later than "start"_ belong + * to the history of the caller. This is stored in the @var total + * variable. */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + const char *credit_account; + const char *debit_account; + + /** + * The following command allows us to skip over those CMDs + * that do not offer a "row_id" trait. Such skipped CMDs are + * not interesting for building a history. + */if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos, + 0, + &row_id)) + continue; + + /* Seek "/history" starting row. */ + if (NULL != row_id_start) + { + if (*row_id_start == *row_id) + { + /* Doesn't count, start is excluded from output. */ + total = 0; + ok = GNUNET_YES; + continue; + } + } + + /* when 'start' was _not_ given, then ok == GNUNET_YES */ + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + + TALER_LOG_DEBUG ("Found first row\n"); + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_DEBUG ("Hit history limit\n"); + break; + } + + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + TALER_LOG_INFO ("Potential history element:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + TALER_LOG_INFO ("+1 my history\n"); + total++; /* found matching record */ + } + } + + GNUNET_assert (GNUNET_YES == ok); + + if (0 == total) + { + TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n"); + *rh = NULL; + return 0; + } + + + GNUNET_assert (total < UINT_MAX); + h = GNUNET_new_array ((unsigned int) total, + struct History); + total = 0; + ok = GNUNET_NO; + if (NULL == row_id_start) + ok = GNUNET_YES; + + /** + * This loop _only_ populates the array of history elements. + */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + char *bank_hostname; + const char *credit_account; + const char *debit_account; + + if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID + (pos, &row_id)) + continue; + + if (NULL != row_id_start) + { + + if (*row_id_start == *row_id) + { + /** + * Warning: this zeroing is superfluous, as + * total doesn't get incremented if 'start' + * was given and couldn't be found. + */total = 0; + ok = GNUNET_YES; + continue; + } + } + + TALER_LOG_INFO ("Found first row (2)\n"); + + if (GNUNET_NO == ok) + { + TALER_LOG_INFO ("Skip on `%s'\n", + pos->label); + continue; /* skip until we find the marker */ + } + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_INFO ("Hit history limit (2)\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + TALER_LOG_INFO ("Potential history bit:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + /** + * Discard transactions where the audited account played + * _both_ the credit and the debit roles, but _only if_ + * the audit goes on both directions.. This needs more + * explaination! + */if (0 == strcasecmp (hs->account_url, + credit_account)) + { + GNUNET_break (0); + continue; + } + + bank_hostname = strchr (hs->account_url, ':'); + GNUNET_assert (NULL != bank_hostname); + bank_hostname += 3; + + /* Next two blocks only put the 'direction' and 'banking' + * information. */ + + /* Asked for credit, and account got the credit. */ + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + h[total].url = GNUNET_strdup (debit_account); + h[total].details.account_url = h[total].url; + } + + /* This block _completes_ the information of the current item, + * with amount / subject / exchange URL. */ + if (0 == strcasecmp (hs->account_url, + credit_account)) + { + const struct TALER_Amount *amount; + const struct TALER_ReservePublicKeyP *reserve_pub; + const char *account_url; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_amount_obj + (pos, 0, &amount)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_reserve_pub + (pos, 0, &reserve_pub)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_url + (pos, 1, + &account_url)); + h[total].details.amount = *amount; + h[total].row_id = *row_id; + h[total].details.reserve_pub = *reserve_pub; + h[total].details.account_url = account_url; + TALER_LOG_INFO ("+1-bit of my history\n"); + total++; + } + } + *rh = h; + return total; +} + + +/** + * Compute how many results we expect to be returned for + * the current command at @a is. + * + * @param is the interpreter state to inspect. + * @return number of results expected. + */ +static uint64_t +compute_result_count (struct TALER_TESTING_Interpreter *is) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + free_history (h, total); + return total; +} + + +/** + * Check that the "/history" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param off the offset (of the CMD list) where the command + * to check is. + * @param dir the expected direction of the transaction. + * @param details the expected transaction details. + * + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, + unsigned int off, + const struct TALER_BANK_CreditDetails *details) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + if (off >= total) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test says history has at most %u" + " results, but got result #%u to check\n", + (unsigned int) total, + off); + print_expected (h, + total, + off); + return GNUNET_SYSERR; + } + if ( (0 != GNUNET_memcmp (&h[off].details.reserve_pub, + &details->reserve_pub)) || + (0 != TALER_amount_cmp (&h[off].details.amount, + &details->amount)) || + (0 != strcasecmp (h[off].details.account_url, + details->account_url)) ) + { + GNUNET_break (0); + print_expected (h, + total, + off); + free_history (h, + total); + return GNUNET_SYSERR; + } + free_history (h, + total); + return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent. The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * finally check it against what the bank returned. + * + * @param cls closure. + * @param http_status HTTP response code, #MHD_HTTP_OK (200) + * for successful status request 0 if the bank's reply is + * bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on + * success the last callback is always of this status + * (even if `abs(num_results)` were already returned). + * @param ec taler status code. + * @param dir direction of the transfer. + * @param row_id monotonically increasing counter corresponding to + * the transaction. + * @param details details about the wire transfer. + * @param json detailed response from the HTTPD, or NULL if + * reply was not in JSON. + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static int +history_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t row_id, + const struct TALER_BANK_CreditDetails *details, + const json_t *json) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct HistoryState *hs = is->commands[is->ip].cls; + + (void) row_id; + if (MHD_HTTP_OK != http_status) + { + hs->hh = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unwanted response code from /history: %u\n", + http_status); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + if (NULL == details) + { + hs->hh = NULL; + if ( (hs->results_obtained != compute_result_count (is)) || + (GNUNET_YES == hs->failed) ) + { + uint64_t total; + struct History *h; + + GNUNET_break (0); + total = build_history (is, &h); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history of length %llu, got %llu;" + " HTTP status code: %u/%d, failed: %d\n", + (unsigned long long) total, + (unsigned long long) hs->results_obtained, + http_status, + (int) ec, + hs->failed); + print_expected (h, + total, + UINT_MAX); + free_history (h, + total); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + TALER_TESTING_interpreter_next (is); + return GNUNET_OK; + } + + /* check current element */ + if (GNUNET_OK != check_result (is, + hs->results_obtained, + details)) + { + char *acc; + + GNUNET_break (0); + acc = json_dumps (json, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result %u was `%s'\n", + (unsigned int) hs->results_obtained++, + acc); + if (NULL != acc) + free (acc); + hs->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + hs->results_obtained++; + return GNUNET_OK; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct HistoryState *hs = cls; + uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; + const uint64_t *row_ptr; + + (void) cmd; + /* Get row_id from trait. */ + if (NULL != hs->start_row_reference) + { + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != + TALER_TESTING_get_trait_uint64 (history_cmd, + 0, + &row_ptr)) + TALER_TESTING_FAIL (is); + else + row_id = *row_ptr; + TALER_LOG_DEBUG ("row id (from trait) is %llu\n", + (unsigned long long) row_id); + } + + hs->hh = TALER_BANK_credit_history (is->ctx, + hs->account_url, + &hs->auth, + row_id, + hs->num_results, + &history_cb, + is); + GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "history" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +history_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct HistoryState *hs = cls; + + (void) cmd; + if (NULL != hs->hh) + { + TALER_LOG_WARNING ("/history did not complete\n"); + TALER_BANK_credit_history_cancel (hs->hh); + } + GNUNET_free (hs->account_url); + GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param account_url base URL of the account offering the "history" + * operation. + * @param start_row_reference reference to a command that can + * offer a row identifier, to be used as the starting row + * to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_credits (const char *label, + const char *account_url, + const struct + TALER_BANK_AuthenticationData *auth, + const char *start_row_reference, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->account_url = GNUNET_strdup (account_url); + hs->start_row_reference = start_row_reference; + hs->num_results = num_results; + hs->auth = *auth; + { + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_credit_history.c */ diff --git a/src/lib/testing_api_cmd_bank_history_debit.c b/src/lib/testing_api_cmd_bank_history_debit.c new file mode 100644 index 000000000..96f989c04 --- /dev/null +++ b/src/lib/testing_api_cmd_bank_history_debit.c @@ -0,0 +1,766 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file bank-lib/testing_api_cmd_history.c + * @brief command to check the /history API from the bank. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ + /** + * Base URL of the account offering the "history" operation. + */ + const char *account_url; + + /** + * Reference to command defining the + * first row number we want in the result. + */ + const char *start_row_reference; + + /** + * How many rows we want in the result, _at most_, + * and ascending/descending. + */ + long long num_results; + + /** + * Login data to use to authenticate. + */ + struct TALER_BANK_AuthenticationData auth; + + /** + * Handle to a pending "history" operation. + */ + struct TALER_BANK_DebitHistoryHandle *hh; + + /** + * Expected number of results (= rows). + */ + uint64_t results_obtained; + + /** + * Set to #GNUNET_YES if the callback detects something + * unexpected. + */ + int failed; + +}; + + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + + /** + * Wire details. + */ + struct TALER_BANK_DebitDetails details; + + /** + * Serial ID of the wire transfer. + */ + uint64_t row_id; + + /** + * URL to free. + */ + char *url; +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + (void) cls; + (void) ret; + (void) trait; + (void) index; + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Free history @a h of length @a h_len. + * + * @param h history array to free. + * @param h_len number of entries in @a h. + */ +static void +free_history (struct History *h, + uint64_t h_len) +{ + for (uint64_t off = 0; off<h_len; off++) + GNUNET_free (h[off].url); + GNUNET_free_non_null (h); +} + + +/** + * Log which history we expected. Called when an error occurs. + * + * @param h what we expected. + * @param h_len number of entries in @a h. + * @param off position of the missmatch. + */ +static void +print_expected (struct History *h, + uint64_t h_len, + unsigned int off) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Transaction history missmatch at position %u/%llu\n", + off, + (unsigned long long) h_len); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history:\n"); + for (uint64_t i = 0; i<h_len; i++) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "H(%llu): %s (serial: %llu, subject: %s," + " counterpart: %s)\n", + (unsigned long long) i, + TALER_amount2s (&h[i].details.amount), + (unsigned long long) h[i].row_id, + TALER_B2S (&h[i].details.wtid), + h[i].details.account_url); + } +} + + +/** + * Tell if the current item is beyond the allowed limit. + * + * @param total current number of items in the built history list. + * Note, this is the list we build locally and compare with + * what the server returned. + * @param hs the history CMD state. + * @param pos current item to be evaluated or not (if the list + * has already enough elements). + * @return GNUNET_OK / GNUNET_NO. + */ +static int +build_history_hit_limit (uint64_t total, + const struct HistoryState *hs, + const struct TALER_TESTING_Command *pos) +{ + return total >= hs->num_results; +} + + +/** + * This function constructs the list of history elements that + * interest the account number of the caller. It has two main + * loops: the first to figure out how many history elements have + * to be allocated, and the second to actually populate every + * element. + * + * @param is interpreter state (supposedly having the + * current CMD pointing at a "history" CMD). + * @param[out] rh history array to initialize. + * + * @return number of entries in @a rh. + */ +static uint64_t +build_history (struct TALER_TESTING_Interpreter *is, + struct History **rh) +{ + struct HistoryState *hs = is->commands[is->ip].cls; + uint64_t total; + struct History *h; + const struct TALER_TESTING_Command *add_incoming_cmd; + int inc; + unsigned int start; + unsigned int end; + + /** + * @var turns GNUNET_YES whenever either no 'start' value was + * given for the history query, or the given value is found + * in the list of all the CMDs. + */int ok; + const uint64_t *row_id_start = NULL; + + if (NULL != hs->start_row_reference) + { + TALER_LOG_INFO + ("`%s': start row given via reference `%s'\n", + TALER_TESTING_interpreter_get_current_label (is), + hs->start_row_reference); + add_incoming_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + GNUNET_assert (NULL != add_incoming_cmd); + GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_uint64 + (add_incoming_cmd, 0, &row_id_start)); + } + + GNUNET_assert (0 != hs->num_results); + if (0 == is->ip) + { + TALER_LOG_DEBUG ("Checking history at first CMD..\n"); + *rh = NULL; + return 0; + } + + /* AKA 'delta'. */ + if (hs->num_results > 0) + { + inc = 1; /* _inc_rement */ + start = 0; + end = is->ip - 1; + } + else + { + inc = -1; + start = is->ip - 1; + end = 0; + } + + total = 0; + ok = GNUNET_NO; + + if (NULL == row_id_start) + ok = GNUNET_YES; + + /* This loop counts how many commands _later than "start"_ belong + * to the history of the caller. This is stored in the @var total + * variable. */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + const char *debit_account; + const char *credit_account; + + /** + * The following command allows us to skip over those CMDs + * that do not offer a "row_id" trait. Such skipped CMDs are + * not interesting for building a history. + */if (GNUNET_OK != TALER_TESTING_get_trait_uint64 (pos, + 0, + &row_id)) + continue; + + /* Seek "/history" starting row. */ + if (NULL != row_id_start) + { + if (*row_id_start == *row_id) + { + /* Doesn't count, start is excluded from output. */ + total = 0; + ok = GNUNET_YES; + continue; + } + } + + /* when 'start' was _not_ given, then ok == GNUNET_YES */ + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + + TALER_LOG_DEBUG ("Found first row\n"); + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_DEBUG ("Hit history limit\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + TALER_LOG_INFO ("Potential history element:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + TALER_LOG_INFO ("+1 my history\n"); + total++; /* found matching record */ + } + } + + GNUNET_assert (GNUNET_YES == ok); + + if (0 == total) + { + TALER_LOG_DEBUG ("Checking history at first CMD.. (2)\n"); + *rh = NULL; + return 0; + } + + + GNUNET_assert (total < UINT_MAX); + h = GNUNET_new_array ((unsigned int) total, + struct History); + total = 0; + ok = GNUNET_NO; + if (NULL == row_id_start) + ok = GNUNET_YES; + + /** + * This loop _only_ populates the array of history elements. + */ + for (unsigned int off = start; off != end + inc; off += inc) + { + const struct TALER_TESTING_Command *pos = &is->commands[off]; + const uint64_t *row_id; + char *bank_hostname; + const char *credit_account; + const char *debit_account; + + if (GNUNET_OK != TALER_TESTING_GET_TRAIT_ROW_ID + (pos, &row_id)) + continue; + + if (NULL != row_id_start) + { + + if (*row_id_start == *row_id) + { + /** + * Warning: this zeroing is superfluous, as + * total doesn't get incremented if 'start' + * was given and couldn't be found. + */total = 0; + ok = GNUNET_YES; + continue; + } + } + + TALER_LOG_INFO ("Found first row (2)\n"); + + if (GNUNET_NO == ok) + { + TALER_LOG_INFO ("Skip on `%s'\n", + pos->label); + continue; /* skip until we find the marker */ + } + + if (build_history_hit_limit (total, + hs, + pos)) + { + TALER_LOG_INFO ("Hit history limit (2)\n"); + break; + } + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_DEBIT_ACCOUNT + (pos, &debit_account)); + + GNUNET_assert + (GNUNET_OK == TALER_TESTING_GET_TRAIT_CREDIT_ACCOUNT + (pos, &credit_account)); + + TALER_LOG_INFO ("Potential history bit:" + " %s->%s; my account: %s\n", + debit_account, + credit_account, + hs->account_url); + + /** + * Discard transactions where the audited account played + * _both_ the debit and the debit roles, but _only if_ + * the audit goes on both directions.. This needs more + * explaination! + */if (0 == strcasecmp (hs->account_url, + debit_account)) + { + GNUNET_break (0); + continue; + } + + bank_hostname = strchr (hs->account_url, ':'); + GNUNET_assert (NULL != bank_hostname); + bank_hostname += 3; + + /* Next two blocks only put the 'direction' and 'banking' + * information. */ + + /* Asked for debit, and account got the debit. */ + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + h[total].url = GNUNET_strdup (credit_account); + h[total].details.account_url = h[total].url; + } + + /* This block _completes_ the information of the current item, + * with amount / subject / exchange URL. */ + if (0 == strcasecmp (hs->account_url, + debit_account)) + { + const struct TALER_Amount *amount; + const struct TALER_WireTransferIdentifierRawP *wtid; + const char *account_url; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_amount_obj + (pos, 0, &amount)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_wtid + (pos, 0, &wtid)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_url + (pos, 1, + &account_url)); + h[total].details.amount = *amount; + h[total].row_id = *row_id; + h[total].details.wtid = *wtid; + h[total].details.account_url = account_url; + TALER_LOG_INFO ("+1-bit of my history\n"); + total++; + } + } + *rh = h; + return total; +} + + +/** + * Compute how many results we expect to be returned for + * the current command at @a is. + * + * @param is the interpreter state to inspect. + * @return number of results expected. + */ +static uint64_t +compute_result_count (struct TALER_TESTING_Interpreter *is) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + free_history (h, total); + return total; +} + + +/** + * Check that the "/history" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param off the offset (of the CMD list) where the command + * to check is. + * @param dir the expected direction of the transaction. + * @param details the expected transaction details. + * + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, + unsigned int off, + const struct TALER_BANK_DebitDetails *details) +{ + uint64_t total; + struct History *h; + + total = build_history (is, &h); + if (off >= total) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test says history has at most %u" + " results, but got result #%u to check\n", + (unsigned int) total, + off); + print_expected (h, + total, + off); + return GNUNET_SYSERR; + } + if ( (0 != GNUNET_memcmp (&h[off].details.wtid, + &details->wtid)) || + (0 != TALER_amount_cmp (&h[off].details.amount, + &details->amount)) || + (0 != strcasecmp (h[off].details.account_url, + details->account_url)) ) + { + GNUNET_break (0); + print_expected (h, + total, + off); + free_history (h, + total); + return GNUNET_SYSERR; + } + free_history (h, + total); + return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent. The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * finally check it against what the bank returned. + * + * @param cls closure. + * @param http_status HTTP response code, #MHD_HTTP_OK (200) + * for successful status request 0 if the bank's reply is + * bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on + * success the last callback is always of this status + * (even if `abs(num_results)` were already returned). + * @param ec taler status code. + * @param dir direction of the transfer. + * @param row_id monotonically increasing counter corresponding to + * the transaction. + * @param details details about the wire transfer. + * @param json detailed response from the HTTPD, or NULL if + * reply was not in JSON. + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static int +history_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t row_id, + const struct TALER_BANK_DebitDetails *details, + const json_t *json) +{ + struct TALER_TESTING_Interpreter *is = cls; + struct HistoryState *hs = is->commands[is->ip].cls; + + (void) row_id; + if (MHD_HTTP_OK != http_status) + { + hs->hh = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unwanted response code from /history: %u\n", + http_status); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + if (NULL == details) + { + hs->hh = NULL; + if ( (hs->results_obtained != compute_result_count (is)) || + (GNUNET_YES == hs->failed) ) + { + uint64_t total; + struct History *h; + + GNUNET_break (0); + total = build_history (is, &h); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected history of length %llu, got %llu;" + " HTTP status code: %u/%d, failed: %d\n", + (unsigned long long) total, + (unsigned long long) hs->results_obtained, + http_status, + (int) ec, + hs->failed); + print_expected (h, + total, + UINT_MAX); + free_history (h, + total); + TALER_TESTING_interpreter_fail (is); + return GNUNET_SYSERR; + } + TALER_TESTING_interpreter_next (is); + return GNUNET_OK; + } + + /* check current element */ + if (GNUNET_OK != check_result (is, + hs->results_obtained, + details)) + { + char *acc; + + GNUNET_break (0); + acc = json_dumps (json, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result %u was `%s'\n", + (unsigned int) hs->results_obtained++, + acc); + if (NULL != acc) + free (acc); + hs->failed = GNUNET_YES; + return GNUNET_SYSERR; + } + hs->results_obtained++; + return GNUNET_OK; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct HistoryState *hs = cls; + uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; + const uint64_t *row_ptr; + + (void) cmd; + /* Get row_id from trait. */ + if (NULL != hs->start_row_reference) + { + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != + TALER_TESTING_get_trait_uint64 (history_cmd, + 0, + &row_ptr)) + TALER_TESTING_FAIL (is); + else + row_id = *row_ptr; + TALER_LOG_DEBUG ("row id (from trait) is %llu\n", + (unsigned long long) row_id); + } + + hs->hh = TALER_BANK_debit_history (is->ctx, + hs->account_url, + &hs->auth, + row_id, + hs->num_results, + &history_cb, + is); + GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "history" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +history_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct HistoryState *hs = cls; + + (void) cmd; + if (NULL != hs->hh) + { + TALER_LOG_WARNING ("/history did not complete\n"); + TALER_BANK_debit_history_cancel (hs->hh); + } + GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param account_url base URL of the account offering the "history" + * operation. + * @param auth login data to use + * @param start_row_reference reference to a command that can + * offer a row identifier, to be used as the starting row + * to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_debits (const char *label, + const char *account_url, + const struct TALER_BANK_AuthenticationData *auth, + const char *start_row_reference, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->account_url = account_url; + hs->start_row_reference = start_row_reference; + hs->num_results = num_results; + hs->auth = *auth; + + { + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_history_debit.c */ diff --git a/src/lib/testing_api_cmd_bank_transfer.c b/src/lib/testing_api_cmd_bank_transfer.c new file mode 100644 index 000000000..d5a3872ed --- /dev/null +++ b/src/lib/testing_api_cmd_bank_transfer.c @@ -0,0 +1,394 @@ +/* + This file is part of TALER + Copyright (C) 2018-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file exchange-lib/testing_api_cmd_transfer.c + * @brief implementation of a bank /transfer command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "backoff.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "taler_testing_bank_lib.h" + + +/** + * State for a "transfer" CMD. + */ +struct TransferState +{ + + /** + * Wire transfer amount. + */ + struct TALER_Amount amount; + + /** + * Base URL of the debit account. + */ + const char *account_debit_url; + + /** + * Money receiver account URL. + */ + const char *payto_credit_account; + + /** + * Username to use for authentication. + */ + struct TALER_BANK_AuthenticationData auth; + + /** + * Base URL of the exchange. + */ + const char *exchange_base_url; + + /** + * Wire transfer identifier to use. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * Handle to the pending request at the fakebank. + */ + struct TALER_BANK_WireExecuteHandle *weh; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Set to the wire transfer's unique ID. + */ + uint64_t serial_id; + + /** + * Timestamp of the transaction (as returned from the bank). + */ + struct GNUNET_TIME_Absolute timestamp; + + /** + * Configuration filename. Used to get the tip reserve key + * filename (used to obtain a public key to write in the + * transfer subject). + */ + const char *config_filename; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Was this command modified via + * #TALER_TESTING_cmd_admin_add_incoming_with_retry to + * enable retries? + */ + int do_retry; +}; + + +/** + * Run the "transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #transfer_run. + * + * @param cls a `struct TransferState` + */ +static void +do_retry (void *cls) +{ + struct TransferState *fts = cls; + + fts->retry_task = NULL; + transfer_run (fts, + NULL, + fts->is); +} + + +/** + * This callback will process the fakebank response to the wire + * transfer. It just checks whether the HTTP response code is + * acceptable. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for + * successful status request; 0 if the exchange's reply is + * bogus (fails to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param serial_id unique ID of the wire transfer + * @param timestamp time stamp of the transaction made. + */ +static void +confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t serial_id, + struct GNUNET_TIME_Absolute timestamp) +{ + struct TransferState *fts = cls; + struct TALER_TESTING_Interpreter *is = fts->is; + + fts->weh = NULL; + if (MHD_HTTP_OK != http_status) + { + if (GNUNET_YES == fts->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying transfer failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + fts->backoff = GNUNET_TIME_UNIT_ZERO; + else + fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff); + fts->retry_task = GNUNET_SCHEDULER_add_delayed + (fts->backoff, + &do_retry, + fts); + return; + } + } + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fakebank returned HTTP status %u/%d\n", + http_status, + (int) ec); + TALER_TESTING_interpreter_fail (is); + return; + } + + fts->serial_id = serial_id; + fts->timestamp = timestamp; + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the "transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +transfer_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct TransferState *fts = cls; + void *buf; + size_t buf_size; + + TALER_BANK_prepare_wire_transfer (fts->payto_credit_account, + &fts->amount, + fts->exchange_base_url, + &fts->wtid, + &buf, + &buf_size); + fts->is = is; + fts->weh + = TALER_BANK_execute_wire_transfer + (TALER_TESTING_interpreter_get_context (is), + fts->account_debit_url, + &fts->auth, + buf, + buf_size, + &confirmation_cb, + fts); + GNUNET_free (buf); + if (NULL == fts->weh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "fakebank transfer" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure + * @param cmd current CMD being cleaned up. + */ +static void +transfer_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct TransferState *fts = cls; + + if (NULL != fts->weh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %s did not complete\n", + cmd->label); + TALER_BANK_execute_wire_transfer_cancel (fts->weh); + fts->weh = NULL; + } + if (NULL != fts->retry_task) + { + GNUNET_SCHEDULER_cancel (fts->retry_task); + fts->retry_task = NULL; + } + GNUNET_free (fts); +} + + +/** + * Offer internal data from a "fakebank transfer" CMD to other + * commands. + * + * @param cls closure. + * @param ret[out] result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +transfer_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct TransferState *fts = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_url (1, fts->account_debit_url), + TALER_TESTING_MAKE_TRAIT_ROW_ID (&fts->serial_id), + TALER_TESTING_MAKE_TRAIT_CREDIT_ACCOUNT (fts->payto_credit_account), + TALER_TESTING_MAKE_TRAIT_DEBIT_ACCOUNT (fts->account_debit_url), + TALER_TESTING_make_trait_amount_obj (0, &fts->amount), + TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp), + TALER_TESTING_make_trait_wtid (0, + &fts->wtid), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +/** + * Create transfer command. + * + * @param label command label. + * @param amount amount to transfer. + * @param account_base_url base URL of the account that implements this + * wire transer (which account gives money). + * @param auth authentication data to use + * @param payto_credit_account which account receives money. + * @param wtid wire transfer identifier to use + * @param exchange_base_url exchange URL to use + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_transfer + (const char *label, + const char *amount, + const char *account_base_url, + const struct TALER_BANK_AuthenticationData *auth, + const char *payto_credit_account, + const struct TALER_WireTransferIdentifierRawP *wtid, + const char *exchange_base_url) +{ + struct TransferState *fts; + + fts = GNUNET_new (struct TransferState); + fts->account_debit_url = account_base_url; + fts->exchange_base_url = exchange_base_url; + fts->payto_credit_account = payto_credit_account; + fts->auth = *auth; + fts->wtid = *wtid; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &fts->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + + { + struct TALER_TESTING_Command cmd = { + .cls = fts, + .label = label, + .run = &transfer_run, + .cleanup = &transfer_cleanup, + .traits = &transfer_traits + }; + + return cmd; + } +} + + +/** + * Modify a transfer command to enable retries when the reserve is not yet + * full or we get other transient errors from the bank. + * + * @param cmd a fakebank transfer command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd) +{ + struct TransferState *fts; + + GNUNET_assert (&transfer_run == cmd.run); + fts = cmd.cls; + fts->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_api_cmd_transfer.c */ diff --git a/src/lib/testing_auditor_api_helpers.c b/src/lib/testing_api_helpers_auditor.c index cf15131da..cf15131da 100644 --- a/src/lib/testing_auditor_api_helpers.c +++ b/src/lib/testing_api_helpers_auditor.c diff --git a/src/lib/testing_api_helpers_bank.c b/src/lib/testing_api_helpers_bank.c new file mode 100644 index 000000000..64976edbb --- /dev/null +++ b/src/lib/testing_api_helpers_bank.c @@ -0,0 +1,361 @@ +/* + This file is part of TALER + Copyright (C) 2018 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file bank-lib/testing_api_helpers.c + * @brief convenience functions for bank-lib tests. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_testing_bank_lib.h" +#include "taler_fakebank_lib.h" + + +#define BANK_FAIL() \ + do {GNUNET_break (0); return NULL; } while (0) + + +/** + * Keep each bank account credentials at index: + * bank account number - 1 + */ +struct TALER_BANK_AuthenticationData AUTHS[] = { + + /* Bank credentials */ + {.method = TALER_BANK_AUTH_BASIC, + .details.basic.username = TALER_TESTING_BANK_USERNAME, + .details.basic.password = TALER_TESTING_BANK_PASSWORD}, + + /* Exchange credentials */ + {.method = TALER_BANK_AUTH_BASIC, + .details.basic.username = TALER_TESTING_EXCHANGE_USERNAME, + .details.basic.password = TALER_TESTING_EXCHANGE_PASSWORD }, + + /* User credentials */ + {.method = TALER_BANK_AUTH_BASIC, + .details.basic.username = TALER_TESTING_USER_USERNAME, + .details.basic.password = TALER_TESTING_USER_PASSWORD } +}; + + +/** + * Runs the Fakebank by guessing / extracting the portnumber + * from the base URL. + * + * @param bank_url bank's base URL. + * @return the fakebank process handle, or NULL if any + * error occurs. + */ +struct TALER_FAKEBANK_Handle * +TALER_TESTING_run_fakebank (const char *bank_url) +{ + const char *port; + long pnum; + struct TALER_FAKEBANK_Handle *fakebankd; + + port = strrchr (bank_url, + (unsigned char) ':'); + if (NULL == port) + pnum = 80; + else + pnum = strtol (port + 1, NULL, 10); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting Fakebank on port %u (%s)\n", + (unsigned int) pnum, + bank_url); + fakebankd = TALER_FAKEBANK_start ((uint16_t) pnum); + if (NULL == fakebankd) + { + GNUNET_break (0); + return NULL; + } + return fakebankd; +} + + +/** + * Look for substring in a programs' name. + * + * @param prog program's name to look into + * @param marker chunk to find in @a prog + */ +int +TALER_TESTING_has_in_name (const char *prog_name, + const char *marker) +{ + size_t name_pos; + size_t pos; + + if (! prog_name || ! marker) + return GNUNET_NO; + + pos = 0; + name_pos = 0; + while (prog_name[pos]) + { + if ('/' == prog_name[pos]) + name_pos = pos + 1; + pos++; + } + if (name_pos == pos) + return GNUNET_YES; + return strstr (prog_name + name_pos, marker) != NULL; +} + + +/** + * Start the (Python) bank process. Assume the port + * is available and the database is clean. Use the "prepare + * bank" function to do such tasks. + * + * @param config_filename configuration filename. + * @param bank_url base URL of the bank, used by `wget' to check + * that the bank was started right. + * + * @return the process, or NULL if the process could not + * be started. + */ +struct GNUNET_OS_Process * +TALER_TESTING_run_bank (const char *config_filename, + const char *bank_url) +{ + struct GNUNET_OS_Process *bank_proc; + unsigned int iter; + char *wget_cmd; + char *database; + char *serve_cfg; + char *serve_arg; + struct GNUNET_CONFIGURATION_Handle *cfg; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + config_filename)) + { + GNUNET_break (0); + GNUNET_CONFIGURATION_destroy (cfg); + exit (77); + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "bank", + "database", + &database)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "bank", + "database"); + GNUNET_break (0); + GNUNET_CONFIGURATION_destroy (cfg); + exit (77); + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "bank", + "serve", + &serve_cfg)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "bank", + "serve"); + GNUNET_break (0); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (database); + exit (77); + } + GNUNET_CONFIGURATION_destroy (cfg); + + serve_arg = "serve-http"; + if (0 != strcmp ("http", serve_cfg)) + serve_arg = "serve-uwsgi"; + GNUNET_free (serve_cfg); + bank_proc = GNUNET_OS_start_process + (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-bank-manage-testing", + "taler-bank-manage-testing", + config_filename, + database, + serve_arg, NULL); + GNUNET_free (database); + if (NULL == bank_proc) + { + BANK_FAIL (); + } + + GNUNET_asprintf (&wget_cmd, + "wget -q -t 2 -T 1 %s -o /dev/null -O /dev/null", + bank_url); + + /* give child time to start and bind against the socket */ + fprintf (stderr, + "Waiting for `taler-bank-manage' to be ready"); + iter = 0; + do + { + if (10 == iter) + { + fprintf ( + stderr, + "Failed to launch `taler-bank-manage' (or `wget')\n"); + GNUNET_OS_process_kill (bank_proc, + SIGTERM); + GNUNET_OS_process_wait (bank_proc); + GNUNET_OS_process_destroy (bank_proc); + GNUNET_free (wget_cmd); + BANK_FAIL (); + } + fprintf (stderr, "."); + sleep (1); + iter++; + } + while (0 != system (wget_cmd)); + GNUNET_free (wget_cmd); + fprintf (stderr, "\n"); + + return bank_proc; + +} + + +/** + * Prepare the bank execution. Check if the port is available + * and reset database. + * + * @param config_filename configuration file name. + * + * @return the base url, or NULL upon errors. Must be freed + * by the caller. + */ +char * +TALER_TESTING_prepare_bank (const char *config_filename) +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + unsigned long long port; + struct GNUNET_OS_Process *dbreset_proc; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + char *base_url; + char *database; + + cfg = GNUNET_CONFIGURATION_create (); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, config_filename)) + { + GNUNET_CONFIGURATION_destroy (cfg); + BANK_FAIL (); + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "bank", + "DATABASE", + &database)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "bank", + "DATABASE"); + GNUNET_CONFIGURATION_destroy (cfg); + BANK_FAIL (); + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "bank", + "HTTP_PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "bank", + "HTTP_PORT"); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (database); + BANK_FAIL (); + } + GNUNET_CONFIGURATION_destroy (cfg); + + if (GNUNET_OK != GNUNET_NETWORK_test_port_free + (IPPROTO_TCP, (uint16_t) port)) + { + fprintf (stderr, + "Required port %llu not available, skipping.\n", + port); + BANK_FAIL (); + } + + /* DB preparation */ + if (NULL == + (dbreset_proc = GNUNET_OS_start_process ( + GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-bank-manage", + "taler-bank-manage", + "-c", "bank.conf", + "--with-db", database, + "django", + "flush", + "--no-input", NULL))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to flush the bank db.\n"); + GNUNET_free (database); + BANK_FAIL (); + } + GNUNET_free (database); + + if (GNUNET_SYSERR == + GNUNET_OS_process_wait_status (dbreset_proc, + &type, + &code)) + { + GNUNET_OS_process_destroy (dbreset_proc); + BANK_FAIL (); + } + if ( (type == GNUNET_OS_PROCESS_EXITED) && + (0 != code) ) + { + fprintf (stderr, + "Failed to setup database\n"); + BANK_FAIL (); + } + if ( (type != GNUNET_OS_PROCESS_EXITED) || + (0 != code) ) + { + fprintf (stderr, + "Unexpected error running" + " `taler-bank-manage django flush..'!\n"); + BANK_FAIL (); + } + + GNUNET_OS_process_destroy (dbreset_proc); + + GNUNET_asprintf (&base_url, + "http://localhost:%llu/", + port); + return base_url; +} + + +/* end of testing_api_helpers.c */ diff --git a/src/lib/testing_api_helpers.c b/src/lib/testing_api_helpers_exchange.c index cc8430784..cc8430784 100644 --- a/src/lib/testing_api_helpers.c +++ b/src/lib/testing_api_helpers_exchange.c |