diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/Makefile.am | 194 | ||||
| -rw-r--r-- | src/lib/test-taler-exchange-aggregator-postgres.conf | 87 | ||||
| -rw-r--r-- | src/lib/test-taler-exchange-wirewatch-postgres.conf | 73 | ||||
| -rw-r--r-- | src/lib/test_taler_exchange_aggregator.c | 1339 | ||||
| -rw-r--r-- | src/lib/test_taler_exchange_wirewatch.c | 876 | 
5 files changed, 2487 insertions, 82 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 231e049a..8ffee92b 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 00000000..b6c18fd5 --- /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 00000000..7f8cc479 --- /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 00000000..a5f03e1d --- /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 00000000..9c089a54 --- /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 */  | 
