summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am194
-rw-r--r--src/lib/test-taler-exchange-aggregator-postgres.conf87
-rw-r--r--src/lib/test-taler-exchange-wirewatch-postgres.conf73
-rw-r--r--src/lib/test_taler_exchange_aggregator.c1339
-rw-r--r--src/lib/test_taler_exchange_wirewatch.c876
5 files changed, 2487 insertions, 82 deletions
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 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/test_taler_exchange_aggregator.c
+ * @brief Tests for taler-exchange-aggregator logic
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include <microhttpd.h>
+#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 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @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 <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include "taler_json_lib.h"
+#include <microhttpd.h>
+#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 */