From ca55b5078f1b8331bbe571209beff24c914b6b33 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 12 Jan 2020 20:51:42 +0100 Subject: restructure tests and build system --- src/lib/Makefile.am | 194 +-- .../test-taler-exchange-aggregator-postgres.conf | 87 ++ .../test-taler-exchange-wirewatch-postgres.conf | 73 ++ src/lib/test_taler_exchange_aggregator.c | 1339 ++++++++++++++++++++ src/lib/test_taler_exchange_wirewatch.c | 876 +++++++++++++ 5 files changed, 2487 insertions(+), 82 deletions(-) create mode 100644 src/lib/test-taler-exchange-aggregator-postgres.conf create mode 100644 src/lib/test-taler-exchange-wirewatch-postgres.conf create mode 100644 src/lib/test_taler_exchange_aggregator.c create mode 100644 src/lib/test_taler_exchange_wirewatch.c (limited to 'src/lib') diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 231e049a0..8ffee92bf 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -6,6 +6,8 @@ if USE_COVERAGE XLIB = -lgcov endif +# Libraries + lib_LTLIBRARIES = \ libtalerauditor.la \ libtalerexchange.la \ @@ -37,7 +39,13 @@ libtalerexchange_la_LIBADD = \ -lgnunetutil \ -ljansson \ $(XLIB) - +if HAVE_LIBCURL +libtalerexchange_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerexchange_la_LIBADD += -lgnurl +endif +endif libtalerauditor_la_LDFLAGS = \ -version-info 0:0:0 \ @@ -56,7 +64,6 @@ libtalerauditor_la_LIBADD = \ -lgnunetutil \ -ljansson \ $(XLIB) - if HAVE_LIBCURL libtalerauditor_la_LIBADD += -lcurl else @@ -65,7 +72,6 @@ libtalerauditor_la_LIBADD += -lgnurl endif endif - libtalertesting_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined @@ -135,96 +141,70 @@ libtalertesting_la_LIBADD = \ -ljansson \ $(XLIB) -if HAVE_LIBCURL -libtalerexchange_la_LIBADD += -lcurl -else -if HAVE_LIBGNURL -libtalerexchange_la_LIBADD += -lgnurl -endif -endif +# Testcases + +AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; check_PROGRAMS = \ - test_auditor_api_version \ test_auditor_api \ + test_auditor_api_version \ 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 - + test_exchange_api_overlapping_keys_bug \ + test_taler_exchange_aggregator-postgres \ + test_taler_exchange_wirewatch-postgres if HAVE_TWISTER check_PROGRAMS += \ test_exchange_api_twisted \ - test_bank_api_with_pybank_twisted \ - test_bank_api_with_fakebank_twisted + test_bank_api_with_fakebank_twisted \ + test_bank_api_with_pybank_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 +TESTS = \ + $(check_PROGRAMS) -test_exchange_api_twisted_SOURCES = \ - test_exchange_api_twisted.c -test_exchange_api_twisted_LDADD = \ - $(LIBGCRYPT_LIBS) \ +test_auditor_api_SOURCES = \ + test_auditor_api.c +test_auditor_api_LDADD = \ + 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/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ - -ltalertwistertesting \ - -lgnunetjson \ -lgnunetcurl \ -lgnunetutil \ -ljansson -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 \ +test_auditor_api_version_SOURCES = \ + test_auditor_api_version.c +test_auditor_api_version_LDADD = \ + libtalerauditor.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ -lgnunetcurl \ -lgnunetutil \ -ljansson -test_bank_api_with_pybank_twisted_SOURCES = \ - test_bank_api_twisted.c -test_bank_api_with_pybank_twisted_LDADD = \ +test_bank_api_with_fakebank_SOURCES = \ + test_bank_api.c +test_bank_api_with_fakebank_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 \ + -ltalerexchange \ -lgnunetutil \ - -ljansson - - - -AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; + libtalerbank.la -TESTS = \ - $(check_PROGRAMS) +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_exchange_api_SOURCES = \ test_exchange_api.c @@ -240,9 +220,9 @@ test_exchange_api_LDADD = \ -lgnunetutil \ -ljansson -test_exchange_api_overlapping_keys_bug_SOURCES = \ - test_exchange_api_overlapping_keys_bug.c -test_exchange_api_overlapping_keys_bug_LDADD = \ +test_exchange_api_keys_cherry_picking_SOURCES = \ + test_exchange_api_keys_cherry_picking.c +test_exchange_api_keys_cherry_picking_LDADD = \ libtalertesting.la \ libtalerexchange.la \ $(LIBGCRYPT_LIBS) \ @@ -253,9 +233,9 @@ test_exchange_api_overlapping_keys_bug_LDADD = \ -lgnunetutil \ -ljansson -test_exchange_api_keys_cherry_picking_SOURCES = \ - test_exchange_api_keys_cherry_picking.c -test_exchange_api_keys_cherry_picking_LDADD = \ +test_exchange_api_overlapping_keys_bug_SOURCES = \ + test_exchange_api_overlapping_keys_bug.c +test_exchange_api_overlapping_keys_bug_LDADD = \ libtalertesting.la \ libtalerexchange.la \ $(LIBGCRYPT_LIBS) \ @@ -266,37 +246,87 @@ test_exchange_api_keys_cherry_picking_LDADD = \ -lgnunetutil \ -ljansson -test_auditor_api_SOURCES = \ - test_auditor_api.c -test_auditor_api_LDADD = \ - libtalerauditor.la \ +test_taler_exchange_aggregator_postgres_SOURCES = \ + test_taler_exchange_aggregator.c +test_taler_exchange_aggregator_postgres_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lmicrohttpd \ + -lgnunetutil \ + -lgnunetjson \ + -ljansson \ + -lpthread + +test_taler_exchange_wirewatch_postgres_SOURCES = \ + test_taler_exchange_wirewatch.c +test_taler_exchange_wirewatch_postgres_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lmicrohttpd \ + -lgnunetutil \ + -lgnunetjson \ + -lgnunetpq \ + -ljansson \ + -lpthread + +test_exchange_api_twisted_SOURCES = \ + test_exchange_api_twisted.c +test_exchange_api_twisted_LDADD = \ + $(LIBGCRYPT_LIBS) \ libtalertesting.la \ libtalerexchange.la \ - $(LIBGCRYPT_LIBS) \ $(top_builddir)/src/bank-lib/libtalerfakebank.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ + -ltalertwistertesting \ + -lgnunetjson \ -lgnunetcurl \ -lgnunetutil \ -ljansson +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_auditor_api_version_SOURCES = \ - test_auditor_api_version.c -test_auditor_api_version_LDADD = \ - libtalerauditor.la \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ +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 +# Distribution EXTRA_DIST = \ bank.conf \ bank_twisted.conf \ + test_auditor_api.conf \ + test_auditor_api_expire_reserve_now.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 \ @@ -310,5 +340,5 @@ EXTRA_DIST = \ test_exchange_api_keys_cherry_picking_extended.conf \ test_exchange_api_keys_cherry_picking_extended_2.conf \ test_exchange_api_expire_reserve_now.conf \ - test_auditor_api.conf \ - test_auditor_api_expire_reserve_now.conf + test-taler-exchange-aggregator-postgres.conf \ + test-taler-exchange-wirewatch-postgres.conf diff --git a/src/lib/test-taler-exchange-aggregator-postgres.conf b/src/lib/test-taler-exchange-aggregator-postgres.conf new file mode 100644 index 000000000..b6c18fd5b --- /dev/null +++ b/src/lib/test-taler-exchange-aggregator-postgres.conf @@ -0,0 +1,87 @@ +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_taler_exchange_httpd_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[exchange] +# The DB plugin to use +DB = postgres + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# Expected base URL of the exchange. Used in wire transfers for +# the tracking API. +BASE_URL = "https://exchange.taler.net/" + +[exchangedb] +# After how long do we close idle reserves? The exchange +# and the auditor must agree on this value. We currently +# expect it to be globally defined for the whole system, +# as there is no way for wallets to query this value. Thus, +# it is only configurable for testing, and should be treated +# as constant in production. +IDLE_RESERVE_EXPIRATION_TIME = 4 weeks + +[exchangedb-postgres] + +#The connection string the plugin has to use for connecting to the database +CONFIG = postgres:///talercheck + + +[exchangedb] + +# After how long do we close idle reserves? The exchange +# and the auditor must agree on this value. We currently +# expect it to be globally defined for the whole system, +# as there is no way for wallets to query this value. Thus, +# it is only configurable for testing, and should be treated +# as constant in production. +IDLE_RESERVE_EXPIRATION_TIME = 4 weeks + +# After how long do we forget about reserves? Should be above +# the legal expiration timeframe of withdrawn coins. +LEGAL_RESERVE_EXPIRATION_TIME = 7 years + +[account-1] + +# What is the account URL? +URL = "payto://x-taler-bank/localhost:8082/3" + +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +PLUGIN = "taler_bank" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +TALER_BANK_AUTH_METHOD = NONE + +[fees-x-taler-bank] + +# Fees for the forseeable future... +# If you see this after 2018, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 diff --git a/src/lib/test-taler-exchange-wirewatch-postgres.conf b/src/lib/test-taler-exchange-wirewatch-postgres.conf new file mode 100644 index 000000000..7f8cc4793 --- /dev/null +++ b/src/lib/test-taler-exchange-wirewatch-postgres.conf @@ -0,0 +1,73 @@ +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_taler_exchange_httpd_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[exchange] +# The DB plugin to use +DB = postgres + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# Expected base URL of the exchange. +BASE_URL = "https://exchange.taler.net/" + +[exchangedb] +# After how long do we close idle reserves? The exchange +# and the auditor must agree on this value. We currently +# expect it to be globally defined for the whole system, +# as there is no way for wallets to query this value. Thus, +# it is only configurable for testing, and should be treated +# as constant in production. +# +# This is THE test that requires a short reserve expiration time! +IDLE_RESERVE_EXPIRATION_TIME = 5 s + +[exchangedb-postgres] + +#The connection string the plugin has to use for connecting to the database +CONFIG = postgres:///talercheck + +[account-1] + +# What is the account URL? +URL = "payto://x-taler-bank/localhost:8082/3" + +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +PLUGIN = "taler_bank" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +TALER_BANK_AUTH_METHOD = NONE + +[fees-x-taler-bank] + +# Fees for the forseeable future... +# If you see this after 2018, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 diff --git a/src/lib/test_taler_exchange_aggregator.c b/src/lib/test_taler_exchange_aggregator.c new file mode 100644 index 000000000..a5f03e1d9 --- /dev/null +++ b/src/lib/test_taler_exchange_aggregator.c @@ -0,0 +1,1339 @@ +/* + This file is part of TALER + (C) 2016-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 +*/ + +/** + * @file exchange/test_taler_exchange_aggregator.c + * @brief Tests for taler-exchange-aggregator logic + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" +#include +#include "taler_fakebank_lib.h" + + +/** + * Commands for the interpreter. + */ +enum OpCode +{ + + /** + * Terminate testcase with 'skipped' result. + */ + OPCODE_TERMINATE_SKIP, + + /** + * Run taler-exchange-aggregator. + */ + OPCODE_RUN_AGGREGATOR, + + /** + * Expect that we have exhaustively gone over all transactions. + */ + OPCODE_EXPECT_TRANSACTIONS_EMPTY, + + /** + * Execute deposit operation against database. + */ + OPCODE_DATABASE_DEPOSIT, + + /** + * Wait a certain amount of time. + */ + OPCODE_WAIT, + + /** + * Expect that we have received the specified transaction. + */ + OPCODE_EXPECT_TRANSACTION, + + /** + * Finish testcase with success. + */ + OPCODE_TERMINATE_SUCCESS +}; + +/** + * Command state for the interpreter. + */ +struct Command +{ + + /** + * What instruction should we run? + */ + enum OpCode opcode; + + /** + * Human-readable label for the command. + */ + const char *label; + + union + { + + /** + * If @e opcode is #OPCODE_EXPECT_TRANSACTION, this + * specifies which transaction we expected. Note that + * the WTID will be set, not checked! + */ + struct + { + + /** + * Amount to be transferred. + */ + const char *amount; + + /** + * Account to debit. + */ + uint64_t debit_account; + + /** + * Account to credit. + */ + uint64_t credit_account; + + /** + * Base URL of the exchange. + */ + const char *exchange_base_url; + + /** + * Subject of the transfer, set by the command. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + } expect_transaction; + + /** + * If @e opcode is #OPCODE_DATABASE_DEPOST, this + * specifies which deposit operation we should fake. + */ + struct + { + + /** + * Each merchant name is automatically mapped to a unique + * merchant public key. + */ + const char *merchant_name; + + /** + * Merchant account number, is mapped to wire details. + */ + uint64_t merchant_account; + + /** + * By when does the merchant request the funds to be wired. + */ + struct GNUNET_TIME_Relative wire_deadline; + + /** + * What is the total amount (including exchange fees). + */ + const char *amount_with_fee; + + /** + * How high are the exchange fees? Must be smaller than @e amount_with_fee. + */ + const char *deposit_fee; + + } deposit; + + /** + * How long should we wait if the opcode is #OPCODE_WAIT. + */ + struct GNUNET_TIME_Relative wait_delay; + + } details; + +}; + + +/** + * State of the interpreter. + */ +struct State +{ + /** + * Array of commands to run. + */ + struct Command*commands; + + /** + * Offset of the next command to be run. + */ + unsigned int ioff; +}; + + +/** + * Pipe used to communicate child death via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + +/** + * ID of task called whenever we get a SIGCHILD. + */ +static struct GNUNET_SCHEDULER_Task *child_death_task; + +/** + * ID of task called whenever we time out. + */ +static struct GNUNET_SCHEDULER_Task *timeout_task; + +/** + * Return value from main(). + */ +static int result; + +/** + * Name of the configuration file to use. + */ +static char *config_filename; + +/** + * Database plugin. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; + +/** + * Our session with the database. + */ +static struct TALER_EXCHANGEDB_Session *session; + +/** + * The handle for the aggregator process that we are testing. + */ +static struct GNUNET_OS_Process *aggregator_proc; + +/** + * State of our interpreter while we are running the aggregator + * process. + */ +static struct State *aggregator_state; + +/** + * Task running the interpreter(). + */ +static struct GNUNET_SCHEDULER_Task *int_task; + +/** + * Private key we use for fake coins. + */ +static struct GNUNET_CRYPTO_RsaPrivateKey *coin_pk; + +/** + * Public key we use for fake coins. + */ +static struct GNUNET_CRYPTO_RsaPublicKey *coin_pub; + +/** + * Handle for our fake bank. + */ +static struct TALER_FAKEBANK_Handle *fb; + + +/** + * Interprets the commands from the test program. + * + * @param cls the `struct State` of the interpreter + */ +static void +interpreter (void *cls); + + +/** + * Task triggered whenever we are to shutdown. + * + * @param cls closure, NULL if we need to self-restart + */ +static void +timeout_action (void *cls) +{ + timeout_task = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test failed: timeout\n"); + result = 2; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task triggered whenever we are to shutdown. + * + * @param cls closure, NULL if we need to self-restart + */ +static void +shutdown_action (void *cls) +{ + if (NULL != timeout_task) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } + if (NULL != int_task) + { + GNUNET_SCHEDULER_cancel (int_task); + int_task = NULL; + } + if (NULL != fb) + { + TALER_FAKEBANK_stop (fb); + fb = NULL; + } + if (NULL != child_death_task) + { + GNUNET_SCHEDULER_cancel (child_death_task); + child_death_task = NULL; + } + if (NULL != aggregator_proc) + { + GNUNET_break (0 == GNUNET_OS_process_kill (aggregator_proc, + SIGKILL)); + GNUNET_OS_process_wait (aggregator_proc); + GNUNET_OS_process_destroy (aggregator_proc); + aggregator_proc = NULL; + } + plugin->drop_tables (plugin->cls); + TALER_EXCHANGEDB_plugin_unload (plugin); + plugin = NULL; +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure, NULL if we need to self-restart + */ +static void +maint_child_death (void *cls) +{ + const struct GNUNET_DISK_FileHandle *pr; + char c[16]; + struct State *state; + + child_death_task = NULL; + pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); + GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); + GNUNET_OS_process_wait (aggregator_proc); + GNUNET_OS_process_destroy (aggregator_proc); + aggregator_proc = NULL; + aggregator_state->ioff++; + state = aggregator_state; + aggregator_state = NULL; + child_death_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + pr, + &maint_child_death, NULL); + + interpreter (state); +} + + +/** + * Setup (fake) information about a coin used in deposit. + * + * @param[out] issue information to initialize with "valid" data + */ +static void +fake_issue (struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue) +{ + memset (issue, 0, sizeof (struct + TALER_EXCHANGEDB_DenominationKeyInformationP)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount_nbo ("EUR:1", + &issue->properties.value)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount_nbo ("EUR:0.1", + &issue->properties.fee_withdraw)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount_nbo ("EUR:0.1", + &issue->properties.fee_deposit)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount_nbo ("EUR:0.1", + &issue->properties.fee_refresh)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount_nbo ("EUR:0.1", + &issue->properties.fee_refund)); +} + + +/** + * Setup (fake) information about a coin used in deposit. + * + * @param[out] coin information to initialize with "valid" data + */ +static void +fake_coin (struct TALER_CoinPublicInfo *coin) +{ + struct GNUNET_HashCode hc; + + GNUNET_CRYPTO_rsa_public_key_hash (coin_pub, + &coin->denom_pub_hash); + GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, + &hc); + coin->denom_sig.rsa_signature = GNUNET_CRYPTO_rsa_sign_fdh (coin_pk, + &hc); +} + + +/** + * Helper function to fake a deposit operation. + * + * @return #GNUNET_OK on success + */ +static int +do_deposit (struct Command *cmd) +{ + struct TALER_EXCHANGEDB_Deposit deposit; + struct TALER_MerchantPrivateKeyP merchant_priv; + int ret; + + memset (&deposit, + 0, + sizeof (deposit)); + /* we derive the merchant's private key from the + name, to ensure that the same name always + results in the same key pair. */ + GNUNET_CRYPTO_kdf (&merchant_priv, + sizeof (struct TALER_MerchantPrivateKeyP), + "merchant-priv", + strlen ("merchant-priv"), + cmd->details.deposit.merchant_name, + strlen (cmd->details.deposit.merchant_name), + NULL, 0); + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv.eddsa_priv, + &deposit.merchant_pub.eddsa_pub); + /* contract is just picked at random; + note: we may want to write this back to 'cmd' in the future. */ + GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, + &deposit.h_contract_terms); + if ( (GNUNET_OK != + TALER_string_to_amount (cmd->details.deposit.amount_with_fee, + &deposit.amount_with_fee)) || + (GNUNET_OK != + TALER_string_to_amount (cmd->details.deposit.deposit_fee, + &deposit.deposit_fee)) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + fake_coin (&deposit.coin); + /* Build JSON for wire details */ + { + char *str; + + GNUNET_asprintf (&str, + "payto://x-taler-bank/localhost:8082/%llu", + (unsigned long + long) cmd->details.deposit.merchant_account); + deposit.receiver_wire_account + = json_pack ("{s:s, s:s}", + "salt", "this-is-a-salt-value", + "url", str); + GNUNET_free (str); + } + GNUNET_assert (GNUNET_OK == + TALER_JSON_merchant_wire_signature_hash ( + deposit.receiver_wire_account, + &deposit.h_wire)); + deposit.timestamp = GNUNET_TIME_absolute_get (); + GNUNET_TIME_round_abs (&deposit.timestamp); + deposit.wire_deadline = GNUNET_TIME_relative_to_absolute ( + cmd->details.deposit.wire_deadline); + GNUNET_TIME_round_abs (&deposit.wire_deadline); + + /* finally, actually perform the DB operation */ + if ( (GNUNET_OK != + plugin->start (plugin->cls, + session, + "aggregator-test-1")) || + (0 > + plugin->ensure_coin_known (plugin->cls, + session, + &deposit.coin)) || + (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->insert_deposit (plugin->cls, + session, + &deposit)) || + (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + plugin->commit (plugin->cls, + session)) ) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + else + ret = GNUNET_OK; + GNUNET_CRYPTO_rsa_signature_free (deposit.coin.denom_sig.rsa_signature); + json_decref (deposit.receiver_wire_account); + return ret; +} + + +/** + * Fail the testcase at the current command. + */ +static void +fail (struct Command *cmd) +{ + fprintf (stderr, + "Testcase failed at command `%s'\n", + cmd->label); + result = 2; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Interprets the commands from the test program. + * + * @param cls the `struct State` of the interpreter + */ +static void +interpreter (void *cls) +{ + struct State *state = cls; + + int_task = NULL; + while (1) + { + struct Command *cmd = &state->commands[state->ioff]; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Running command %u (%s)\n", + state->ioff, + cmd->label); + switch (cmd->opcode) + { + case OPCODE_TERMINATE_SKIP: + /* return skip: test not finished, but did not fail either */ + result = 77; + GNUNET_SCHEDULER_shutdown (); + return; + case OPCODE_WAIT: + state->ioff++; + int_task = GNUNET_SCHEDULER_add_delayed (cmd->details.wait_delay, + &interpreter, + state); + return; + case OPCODE_RUN_AGGREGATOR: + GNUNET_assert (NULL == aggregator_state); + aggregator_state = state; + aggregator_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-aggregator", + "taler-exchange-aggregator", + "-c", config_filename, + "-t", /* enable temporary tables */ + NULL); + if (NULL == aggregator_proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start taler-exchange-aggregator. Check $PATH.\n"); + GNUNET_break (0); + aggregator_state = NULL; + fail (cmd); + return; + } + return; + case OPCODE_EXPECT_TRANSACTIONS_EMPTY: + if (GNUNET_OK != TALER_FAKEBANK_check_empty (fb)) + { + fail (cmd); + return; + } + state->ioff++; + break; + case OPCODE_DATABASE_DEPOSIT: + if (GNUNET_OK != + do_deposit (cmd)) + { + fail (cmd); + return; + } + state->ioff++; + break; + case OPCODE_EXPECT_TRANSACTION: + { + struct TALER_Amount want_amount; + + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.expect_transaction.amount, + &want_amount)) + { + GNUNET_break (0); + fail (cmd); + return; + } + if (GNUNET_OK != + TALER_FAKEBANK_check_debit (fb, + &want_amount, + cmd->details.expect_transaction. + debit_account, + cmd->details.expect_transaction. + credit_account, + cmd->details.expect_transaction. + exchange_base_url, + &cmd->details.expect_transaction.wtid)) + { + fail (cmd); + return; + } + state->ioff++; + break; + } + case OPCODE_TERMINATE_SUCCESS: + result = 0; + GNUNET_SCHEDULER_shutdown (); + return; + } + } +} + + +/** + * Contains the test program. Here each step of the testcase + * is defined. + */ +static void +run_test () +{ + static struct Command commands[] = { + /* test running with empty DB */ + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-on-empty-db" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-on-start" + }, + + /* test simple deposit */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-1", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-1" + }, + + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-1", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.89" + }, + + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-on-start" + }, + + /* test idempotency: run again on transactions already done */ + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-1" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-1" + }, + + /* test combining deposits */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-2a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-2b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-2" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-2", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:1.79" + }, + + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-2" + }, + + /* test NOT combining deposits of different accounts or keys */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-3a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-3b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 5, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-3c", + .details.deposit.merchant_name = "alice", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:1", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-3" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-3a", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.89" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-3b", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.89" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-3c", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 5, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.89" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-3" + }, + + /* test NOT running deposits instantly, but after delay */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-4a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.2", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-4b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.2", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-4-early" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-4-fast" + }, + { + .opcode = OPCODE_WAIT, + .label = "wait (5s)", + .details.wait_delay = { 1000LL * 1000 * 6 } /* 6s */ + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-4-delayed" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-4", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.19" + }, + + /* test picking all deposits at earliest deadline */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-5a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 10 }, /* 10s */ + .details.deposit.amount_with_fee = "EUR:0.2", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-5b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.2", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-5-early" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-5-early" + }, + { + .opcode = OPCODE_WAIT, + .label = "wait (5s)", + .details.wait_delay = { 1000LL * 1000 * 6 } /* 6s */ + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-5-delayed" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-5", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.19" + }, + + /* Test NEVER running 'tiny' unless they make up minimum unit */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-6a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.102", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-6a-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-6a-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-6b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.102", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-6c", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.102", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-6c-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-6c-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-6d", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.102", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-6d-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-6d-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-6e", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.112", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-6e" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-6", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.01" + }, + + /* Test profiteering if wire deadline is short */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-7a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.109", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-7a-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-7a-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-7b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.119", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-7-profit" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-7", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.01" + }, + /* Now check profit was actually taken */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-7c", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.122", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-7c-loss" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-7", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.01" + }, + + /* Test that aggregation would happen fully if wire deadline is long */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-8a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.109", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-8a-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-8a-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-8b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.109", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-8b-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-8b-tiny" + }, + /* now trigger aggregate with large transaction and short deadline */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-8c", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.122", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-8" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-8", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.03" + }, + + + /* Test aggregation with fees and rounding profits */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-9a", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.104", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-9a-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-9a-tiny" + }, + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-9b", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 5 }, /* 5s */ + .details.deposit.amount_with_fee = "EUR:0.105", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-9b-tiny" + }, + { + .opcode = OPCODE_EXPECT_TRANSACTIONS_EMPTY, + .label = "expect-empty-transactions-after-9b-tiny" + }, + /* now trigger aggregate with large transaction and short deadline */ + { + .opcode = OPCODE_DATABASE_DEPOSIT, + .label = "do-deposit-9c", + .details.deposit.merchant_name = "bob", + .details.deposit.merchant_account = 4, + .details.deposit.wire_deadline = { 1000LL * 1000 * 0 }, /* 0s */ + .details.deposit.amount_with_fee = "EUR:0.112", + .details.deposit.deposit_fee = "EUR:0.1" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-deposit-9" + }, + /* 0.009 + 0.009 + 0.022 - 0.001 - 0.002 - 0.008 = 0.029 => 0.02 */ + { + .opcode = OPCODE_EXPECT_TRANSACTION, + .label = "expect-deposit-9", + .details.expect_transaction.debit_account = 3, + .details.expect_transaction.credit_account = 4, + .details.expect_transaction.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transaction.amount = "EUR:0.01" + }, + + /* Everything tested, terminate with success */ + { + .opcode = OPCODE_TERMINATE_SUCCESS, + .label = "testcase-complete-terminating-with-success" + }, + /* note: rest not reached, this is just sample code */ + { + .opcode = OPCODE_TERMINATE_SKIP, + .label = "testcase-incomplete-terminating-with-skip" + } + }; + static struct State state = { + .commands = commands + }; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching interpreter\n"); + int_task = GNUNET_SCHEDULER_add_now (&interpreter, + &state); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure with configuration + */ +static void +run (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct TALER_EXCHANGEDB_DenominationKeyInformationP issue; + struct TALER_DenominationPublicKey dpk; + + plugin = TALER_EXCHANGEDB_plugin_load (cfg); + if (NULL == plugin) + { + GNUNET_break (0); + result = 77; + return; + } + if (GNUNET_OK != + plugin->create_tables (plugin->cls)) + { + GNUNET_break (0); + TALER_EXCHANGEDB_plugin_unload (plugin); + plugin = NULL; + result = 77; + return; + } + session = plugin->get_session (plugin->cls); + GNUNET_assert (NULL != session); + fake_issue (&issue); + dpk.rsa_public_key = coin_pub; + GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key, + &issue.properties.denom_hash); + if ( (GNUNET_OK != + plugin->start (plugin->cls, + session, + "aggregator-test-2")) || + (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->insert_denomination_info (plugin->cls, + session, + &dpk, + &issue)) || + (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + plugin->commit (plugin->cls, + session)) ) + { + GNUNET_break (0); + TALER_EXCHANGEDB_plugin_unload (plugin); + plugin = NULL; + result = 77; + return; + } + child_death_task = + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ), + &maint_child_death, + NULL); + GNUNET_SCHEDULER_add_shutdown (&shutdown_action, + NULL); + timeout_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, + &timeout_action, + NULL); + result = 1; /* test failed for undefined reason */ + fb = TALER_FAKEBANK_start (8082); + if (NULL == fb) + { + GNUNET_SCHEDULER_shutdown (); + result = 77; + return; + } + run_test (); +} + + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle + (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; /* restore errno */ +} + + +int +main (int argc, + char *const argv[]) +{ + const char *plugin_name; + char *testname; + struct GNUNET_OS_Process *proc; + struct GNUNET_CONFIGURATION_Handle *cfg; + struct GNUNET_SIGNAL_Context *shc_chld; + + result = -1; + if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) + { + GNUNET_break (0); + return -1; + } + plugin_name++; + (void) GNUNET_asprintf (&testname, + "test-taler-exchange-aggregator-%s", + plugin_name); + (void) GNUNET_asprintf (&config_filename, + "%s.conf", + testname); + /* these might get in the way */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test_taler_exchange_aggregator", + "WARNING", + NULL); + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", config_filename, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + 8082)) + { + fprintf (stderr, + "Required port %u not available, skipping.\n", + (unsigned int) 8082); + return 77; + } + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse (cfg, + config_filename)) + { + GNUNET_break (0); + GNUNET_free (config_filename); + GNUNET_free (testname); + return 2; + } + sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, + GNUNET_NO, GNUNET_NO); + GNUNET_assert (NULL != sigpipe); + shc_chld = + GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, + &sighandler_child_death); + coin_pk = GNUNET_CRYPTO_rsa_private_key_create (1024); + coin_pub = GNUNET_CRYPTO_rsa_private_key_get_public (coin_pk); + GNUNET_SCHEDULER_run (&run, + cfg); + GNUNET_CRYPTO_rsa_private_key_free (coin_pk); + GNUNET_CRYPTO_rsa_public_key_free (coin_pub); + GNUNET_SIGNAL_handler_uninstall (shc_chld); + shc_chld = NULL; + GNUNET_DISK_pipe_close (sigpipe); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (config_filename); + GNUNET_free (testname); + return result; +} + + +/* end of test_taler_exchange_aggregator.c */ diff --git a/src/lib/test_taler_exchange_wirewatch.c b/src/lib/test_taler_exchange_wirewatch.c new file mode 100644 index 000000000..9c089a54d --- /dev/null +++ b/src/lib/test_taler_exchange_wirewatch.c @@ -0,0 +1,876 @@ +/* + This file is part of TALER + (C) 2016, 2017, 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 +*/ + +/** + * @file exchange/test_taler_exchange_wirewatch.c + * @brief Tests for taler-exchange-wirewatch and taler-exchange-aggregator logic; + * Performs an invalid wire transfer to the exchange, and then checks that + * wirewatch immediately sends the money back. + * Then performs a valid wire transfer, waits for the reserve to expire, + * and then checks that the aggregator sends the money back. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include +#include +#include "taler_json_lib.h" +#include +#include "taler_fakebank_lib.h" + + +/** + * Commands for the interpreter. + */ +enum OpCode +{ + + /** + * Terminate testcase with 'skipped' result. + */ + OPCODE_TERMINATE_SKIP, + + /** + * Run taler-exchange-aggregator. + */ + OPCODE_RUN_AGGREGATOR, + + /** + * Expect that we have exhaustively gone over all transactions. + */ + OPCODE_RUN_WIREWATCH, + + /** + * Send money from bank to exchange. + */ + OPCODE_RUN_TRANSFER, + + /** + * Wait a certain amount of time. + */ + OPCODE_WAIT, + + /** + * Expect that we have received the specified transfer. + */ + OPCODE_EXPECT_TRANSFER, + + /** + * Expect that we have 'expected' all wire transfers. + */ + OPCODE_EXPECT_TRANSFERS_EMPTY, + + /** + * Finish testcase with success. + */ + OPCODE_TERMINATE_SUCCESS +}; + + +/** + * Command state for the interpreter. + */ +struct Command +{ + + /** + * What instruction should we run? + */ + enum OpCode opcode; + + /** + * Human-readable label for the command. + */ + const char *label; + + union + { + + /** + * If @e opcode is #OPCODE_EXPECT_TRANSFER, this + * specifies which transaction we expected. Note that + * the WTID will be set, not checked! + */ + struct + { + + /** + * Amount to be transferred. + */ + const char *amount; + + /** + * Account to debit. + */ + uint64_t debit_account; + + /** + * Account to credit. + */ + uint64_t credit_account; + + /** + * Expected base URL for the exchange. + */ + const char *exchange_base_url; + + /** + * Subject of the transfer, set by the command. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + } expect_transfer; + + + /** + * If @e opcode is #OPCODE_RUN_TRANSFER, this + * specifies which transaction the bank should do. + */ + struct + { + + /** + * Amount to be transferred. + */ + const char *amount; + + /** + * Account to debit. + */ + uint64_t debit_account; + + /** + * Account to credit. + */ + uint64_t credit_account; + + /** + * Subject of the transfer, set by the command. + */ + const char *subject; + + /** + * Serial ID of the wire transfer as assigned by the bank. + */ + uint64_t serial_id; + + } run_transfer; + + struct + { + + /** + * The handle for the aggregator process that we are testing. + */ + struct GNUNET_OS_Process *aggregator_proc; + + /** + * ID of task called whenever we get a SIGCHILD. + */ + struct GNUNET_SCHEDULER_Task *child_death_task; + + } aggregator; + + struct + { + + /** + * The handle for the wirewatch process that we are testing. + */ + struct GNUNET_OS_Process *wirewatch_proc; + + /** + * ID of task called whenever we get a SIGCHILD. + */ + struct GNUNET_SCHEDULER_Task *child_death_task; + + } wirewatch; + + /** + * How long should we wait if the opcode is #OPCODE_WAIT. + */ + struct GNUNET_TIME_Relative wait_delay; + + } details; + +}; + + +/** + * State of the interpreter. + */ +struct State +{ + /** + * Array of commands to run. + */ + struct Command*commands; + + /** + * Offset of the next command to be run. + */ + unsigned int ioff; +}; + + +/** + * Pipe used to communicate child death via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + +/** + * ID of task called whenever we time out. + */ +static struct GNUNET_SCHEDULER_Task *timeout_task; + +/** + * Return value from main(). + */ +static int result; + +/** + * Name of the configuration file to use. + */ +static char *config_filename; + +/** + * Task running the interpreter(). + */ +static struct GNUNET_SCHEDULER_Task *int_task; + +/** + * Handle for our fake bank. + */ +static struct TALER_FAKEBANK_Handle *fb; + + +/** + * Interprets the commands from the test program. + * + * @param cls the `struct State` of the interpreter + */ +static void +interpreter (void *cls); + + +/** + * Advance the IP and run the next command. + * + * @param state interpreter to advance. + */ +static void +next_command (struct State *state) +{ + GNUNET_assert (NULL == int_task); + state->ioff++; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Advancing to command %s\n", + state->commands[state->ioff].label); + int_task = GNUNET_SCHEDULER_add_now (&interpreter, + state); +} + + +/** + * Fail the testcase at the current command. + */ +static void +fail (struct Command *cmd) +{ + GNUNET_assert (NULL == int_task); + fprintf (stderr, + "Testcase failed at command `%s'\n", + cmd->label); + result = 2; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task triggered whenever we are to shutdown. + * + * @param cls closure, NULL if we need to self-restart + */ +static void +timeout_action (void *cls) +{ + timeout_task = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test failed: timeout\n"); + result = 2; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task triggered whenever we are to shutdown. + * + * @param cls our `struct State` + */ +static void +shutdown_action (void *cls) +{ + struct State *state = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Running shutdown\n"); + if (NULL != timeout_task) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } + if (NULL != int_task) + { + GNUNET_SCHEDULER_cancel (int_task); + int_task = NULL; + } + if (NULL != fb) + { + TALER_FAKEBANK_stop (fb); + fb = NULL; + } + for (unsigned int i = 0; i<=state->ioff; i++) + { + struct Command *cmd = &state->commands[i]; + + switch (cmd->opcode) + { + case OPCODE_TERMINATE_SKIP: + break; + case OPCODE_RUN_AGGREGATOR: + if (NULL != cmd->details.aggregator.child_death_task) + { + GNUNET_SCHEDULER_cancel (cmd->details.aggregator.child_death_task); + cmd->details.aggregator.child_death_task = NULL; + } + if (NULL != cmd->details.aggregator.aggregator_proc) + { + GNUNET_break (0 == GNUNET_OS_process_kill ( + cmd->details.aggregator.aggregator_proc, + SIGKILL)); + GNUNET_OS_process_wait (cmd->details.aggregator.aggregator_proc); + GNUNET_OS_process_destroy (cmd->details.aggregator.aggregator_proc); + cmd->details.aggregator.aggregator_proc = NULL; + } + break; + case OPCODE_RUN_WIREWATCH: + if (NULL != cmd->details.wirewatch.child_death_task) + { + GNUNET_SCHEDULER_cancel (cmd->details.wirewatch.child_death_task); + cmd->details.wirewatch.child_death_task = NULL; + } + if (NULL != cmd->details.wirewatch.wirewatch_proc) + { + GNUNET_break (0 == GNUNET_OS_process_kill ( + cmd->details.wirewatch.wirewatch_proc, + SIGKILL)); + GNUNET_OS_process_wait (cmd->details.wirewatch.wirewatch_proc); + GNUNET_OS_process_destroy (cmd->details.wirewatch.wirewatch_proc); + cmd->details.wirewatch.wirewatch_proc = NULL; + } + break; + case OPCODE_RUN_TRANSFER: + break; + case OPCODE_WAIT: + break; + case OPCODE_EXPECT_TRANSFER: + break; + case OPCODE_EXPECT_TRANSFERS_EMPTY: + break; + case OPCODE_TERMINATE_SUCCESS: + break; + } + } +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls our `struct State` + */ +static void +maint_child_death (void *cls) +{ + struct State *state = cls; + const struct GNUNET_DISK_FileHandle *pr; + struct Command *cmd = &state->commands[state->ioff]; + char c[16]; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Child process died for command %s\n", + cmd->label); + pr = GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ); + GNUNET_break (0 < GNUNET_DISK_file_read (pr, + &c, + sizeof (c))); + switch (cmd->opcode) + { + case OPCODE_RUN_AGGREGATOR: + GNUNET_assert (NULL != cmd->details.aggregator.child_death_task); + cmd->details.aggregator.child_death_task = NULL; + GNUNET_OS_process_wait (cmd->details.aggregator.aggregator_proc); + GNUNET_OS_process_destroy (cmd->details.aggregator.aggregator_proc); + cmd->details.aggregator.aggregator_proc = NULL; + break; + case OPCODE_RUN_WIREWATCH: + GNUNET_assert (NULL != cmd->details.wirewatch.child_death_task); + cmd->details.wirewatch.child_death_task = NULL; + GNUNET_OS_process_wait (cmd->details.wirewatch.wirewatch_proc); + GNUNET_OS_process_destroy (cmd->details.wirewatch.wirewatch_proc); + cmd->details.wirewatch.wirewatch_proc = NULL; + break; + default: + fail (cmd); + return; + } + next_command (state); +} + + +/** + * Interprets the commands from the test program. + * + * @param cls the `struct State` of the interpreter + */ +static void +interpreter (void *cls) +{ + struct State *state = cls; + struct Command *cmd = &state->commands[state->ioff]; + + GNUNET_assert (NULL != int_task); + int_task = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Running command %u (%s)\n", + state->ioff, + cmd->label); + switch (cmd->opcode) + { + case OPCODE_TERMINATE_SKIP: + /* return skip: test not finished, but did not fail either */ + result = 77; + GNUNET_SCHEDULER_shutdown (); + return; + case OPCODE_RUN_AGGREGATOR: + cmd->details.aggregator.child_death_task = + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ), + &maint_child_death, + state); + cmd->details.aggregator.aggregator_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-aggregator", + "taler-exchange-aggregator", + "-c", config_filename, + "-t", /* enable temporary tables */ + NULL); + if (NULL == cmd->details.aggregator.aggregator_proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start taler-exchange-aggregator. Check $PATH.\n"); + GNUNET_break (0); + fail (cmd); + return; + } + return; + case OPCODE_RUN_WIREWATCH: + cmd->details.wirewatch.child_death_task = + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ), + &maint_child_death, + state); + cmd->details.wirewatch.wirewatch_proc + = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-wirewatch", + "taler-exchange-wirewatch", + "-c", config_filename, + "-T", /* run in test mode, exit instead of looping */ + NULL); + if (NULL == cmd->details.wirewatch.wirewatch_proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start taler-exchange-wirewatch. Check $PATH.\n"); + GNUNET_break (0); + fail (cmd); + return; + } + return; + case OPCODE_RUN_TRANSFER: + { + struct TALER_Amount amount; + + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.run_transfer.amount, + &amount)) + { + GNUNET_break (0); + fail (cmd); + return; + } + GNUNET_assert (NULL != cmd->details.run_transfer.subject); + cmd->details.run_transfer.serial_id + = TALER_FAKEBANK_make_transfer (fb, + cmd->details.run_transfer.debit_account, + cmd->details.run_transfer.credit_account, + &amount, + cmd->details.run_transfer.subject, + "https://exchange.taler.net/"); + next_command (state); + return; + } + case OPCODE_WAIT: + state->ioff++; + GNUNET_assert (NULL == int_task); + int_task = GNUNET_SCHEDULER_add_delayed (cmd->details.wait_delay, + &interpreter, + state); + return; + case OPCODE_EXPECT_TRANSFER: + { + struct TALER_Amount want_amount; + + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.expect_transfer.amount, + &want_amount)) + { + GNUNET_break (0); + fail (cmd); + return; + } + if (GNUNET_OK != + TALER_FAKEBANK_check_debit (fb, + &want_amount, + cmd->details.expect_transfer.debit_account, + cmd->details.expect_transfer. + credit_account, + cmd->details.expect_transfer. + exchange_base_url, + &cmd->details.expect_transfer.wtid)) + { + fail (cmd); + return; + } + next_command (state); + return; + } + case OPCODE_EXPECT_TRANSFERS_EMPTY: + if (GNUNET_OK != TALER_FAKEBANK_check_empty (fb)) + { + fail (cmd); + return; + } + next_command (state); + return; + case OPCODE_TERMINATE_SUCCESS: + result = 0; + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure with configuration + */ +static void +run (void *cls) +{ + static struct Command commands[] = { + /* test running with empty DB */ + { + .opcode = OPCODE_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-transactions-on-start" + }, + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-on-empty" + }, + { + .opcode = OPCODE_RUN_WIREWATCH, + .label = "run-wirewatch-on-empty" + }, + { + .opcode = OPCODE_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-transactions-after-dry-run" + }, + /* fill exchange's reserve at bank */ + { + .opcode = OPCODE_RUN_TRANSFER, + .label = "run-transfer-good-to-exchange", + .details.run_transfer.debit_account = 4, + .details.run_transfer.credit_account = 3, + .details.run_transfer.subject = + "SRB8VQHNTNJWSSG7BXT24Z063ZSXN7T0MHCQCBAFC1V17BZH10D0", + .details.run_transfer.amount = "EUR:5.00" + }, + /* creates reserve */ + { + .opcode = OPCODE_RUN_WIREWATCH, + .label = "run-wirewatch-on-good-transfer" + }, + /* clear first transfer from DLL */ + { + .opcode = OPCODE_EXPECT_TRANSFER, + .label = "clear-good-transfer-to-exchange", + .details.expect_transfer.debit_account = 4, + .details.expect_transfer.credit_account = 3, + .details.expect_transfer.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transfer.amount = "EUR:5.00" + }, + /* should do NOTHING, it is too early... */ + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-non-expired-reserve" + }, + /* check nothing happened */ + { + .opcode = OPCODE_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-transactions-1" + }, + /* Configuration says reserves expire after 5s! */ + { + .opcode = OPCODE_WAIT, + .label = "wait (5s)", + .details.wait_delay = { 1000LL * 1000 * 6 } /* 6s */ + }, + /* This time the reserve expired, so the money should go back... */ + { + .opcode = OPCODE_RUN_AGGREGATOR, + .label = "run-aggregator-non-expired-reserve" + }, + /* Check exchange sent money back, minus closing fee of EUR:0.01 */ + { + .opcode = OPCODE_EXPECT_TRANSFER, + .label = "check-reserve-expiration-transfer", + .details.expect_transfer.debit_account = 3, + .details.expect_transfer.credit_account = 4, + .details.expect_transfer.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transfer.amount = "EUR:4.99" + }, + /* check nothing else happened */ + { + .opcode = OPCODE_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-transactions-1" + }, + /* This cannot work unless #5077 is implemented. */ +#if TEST_5077 + { + .opcode = OPCODE_RUN_TRANSFER, + .label = "run-transfer-bad-to-exchange", + .details.run_transfer.debit_account = 4, + .details.run_transfer.credit_account = 3, + .details.run_transfer.subject = "random junk", + .details.run_transfer.amount = "EUR:5.00" + }, + { + .opcode = OPCODE_RUN_WIREWATCH, + .label = "run-wirewatch-on-bad-transfer" + }, + { + .opcode = OPCODE_EXPECT_TRANSFER, + .label = "expect-bad-transfer-to-exchange", + .details.expect_transfer.debit_account = 4, + .details.expect_transfer.credit_account = 3, + .details.expect_transfer.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transfer.amount = "EUR:5.00" + }, + { + .opcode = OPCODE_EXPECT_TRANSFER, + .label = "expect-rewire-transfer-from-exchange", + .details.expect_transfer.debit_account = 3, + .details.expect_transfer.credit_account = 4, + .details.expect_transfer.exchange_base_url = + "https://exchange.taler.net/", + .details.expect_transfer.amount = "EUR:5.00" + }, + { + .opcode = OPCODE_EXPECT_TRANSFERS_EMPTY, + .label = "expect-empty-transactions-1" + }, +#endif + + { + .opcode = OPCODE_TERMINATE_SUCCESS, + .label = "testcase-complete-terminating-with-success" + } + }; + static struct State state = { + .commands = commands + }; + + GNUNET_SCHEDULER_add_shutdown (&shutdown_action, + &state); + timeout_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, + &timeout_action, + &state); + result = 1; /* test failed for undefined reason */ + fb = TALER_FAKEBANK_start (8082); + if (NULL == fb) + { + GNUNET_SCHEDULER_shutdown (); + result = 77; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching interpreter\n"); + int_task = GNUNET_SCHEDULER_add_now (&interpreter, + &state); +} + + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle + (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; /* restore errno */ +} + + +int +main (int argc, + char *const argv[]) +{ + const char *plugin_name; + char *testname; + struct GNUNET_OS_Process *proc; + struct GNUNET_CONFIGURATION_Handle *cfg; + struct GNUNET_SIGNAL_Context *shc_chld; + + result = -1; + if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) + { + GNUNET_break (0); + return -1; + } + plugin_name++; + (void) GNUNET_asprintf (&testname, + "test-taler-exchange-wirewatch-%s", + plugin_name); + (void) GNUNET_asprintf (&config_filename, + "%s.conf", + testname); + /* these might get in the way */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test_taler_exchange_wirewatch", + "WARNING", + NULL); + /* check database is working */ + { + struct GNUNET_PQ_Context *conn; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + conn = GNUNET_PQ_connect ("postgres:///talercheck", + es, + NULL); + if (NULL == conn) + return 77; + GNUNET_PQ_disconnect (conn); + } + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", config_filename, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-dbinit", + "taler-exchange-dbinit", + "-c", config_filename, + "-r", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + 8082)) + { + fprintf (stderr, + "Required port %u not available, skipping.\n", + (unsigned int) 8082); + return 77; + } + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse (cfg, + config_filename)) + { + GNUNET_break (0); + GNUNET_free (config_filename); + GNUNET_free (testname); + return 2; + } + sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, + GNUNET_NO, GNUNET_NO); + GNUNET_assert (NULL != sigpipe); + shc_chld = + GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, + &sighandler_child_death); + GNUNET_SCHEDULER_run (&run, + cfg); + GNUNET_SIGNAL_handler_uninstall (shc_chld); + GNUNET_DISK_pipe_close (sigpipe); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (config_filename); + GNUNET_free (testname); + return result; +} + + +/* end of test_taler_exchange_wirewatch.c */ -- cgit v1.2.3