gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated: major libtalertesting API refact


From: gnunet
Subject: [taler-exchange] branch master updated: major libtalertesting API refactoring, including no longer having taler-specific logic in the test engine core
Date: Wed, 07 Jun 2023 23:11:13 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new a2dde02b major libtalertesting API refactoring, including no longer 
having taler-specific logic in the test engine core
a2dde02b is described below

commit a2dde02b64a8ee75c9243632eb45a6ceb9b62dd5
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Wed Jun 7 23:11:05 2023 +0200

    major libtalertesting API refactoring, including no longer having 
taler-specific logic in the test engine core
---
 src/auditor/auditor.conf                           |   2 +-
 src/auditor/taler-auditor-httpd.c                  |  16 +-
 src/benchmark/Makefile.am                          |   7 +-
 src/benchmark/benchmark-common.conf                |   4 +-
 src/benchmark/taler-bank-benchmark.c               |  14 +-
 src/benchmark/taler-exchange-benchmark.c           | 329 +-------
 src/exchange-tools/exchange-offline.conf           |   4 +-
 src/include/taler_auditor_service.h                |   1 +
 src/include/taler_crypto_lib.h                     |   2 +-
 src/include/taler_exchange_service.h               |   9 +-
 src/include/taler_extensions.h                     |   4 +-
 src/include/taler_testing_lib.h                    | 926 +++++++--------------
 src/lib/auditor_api_handle.c                       |  30 +-
 src/lib/exchange_api_handle.c                      |  22 +-
 src/testing/.gitignore                             |   9 +
 src/testing/Makefile.am                            |  46 +-
 .../{test_exchange_api-cs.conf => coins-cs.conf}   |   4 +-
 .../{test_exchange_api-rsa.conf => coins-rsa.conf} |   4 +-
 .../taler-unified-setup.sh}                        |  94 ++-
 .../test-taler-exchange-aggregator-postgres.conf   |  41 +-
 .../test-taler-exchange-wirewatch-postgres.conf    |  31 +-
 src/testing/test_auditor_api-cs.conf               | 141 +---
 src/testing/test_auditor_api-rsa.conf              | 147 +---
 src/testing/test_auditor_api.c                     | 223 +++--
 src/testing/test_auditor_api_version.c             |  15 +-
 src/testing/test_bank_api.c                        | 186 ++---
 src/testing/test_bank_api.conf                     |  13 +
 src/testing/test_bank_api_fakebank.conf            |  17 +-
 src/testing/test_bank_api_fakebank_twisted.conf    |  24 +-
 src/testing/test_bank_api_nexus.conf               |  18 +-
 src/testing/test_bank_api_twisted.c                | 201 ++---
 src/testing/test_exchange_api-cs.conf              | 118 +--
 src/testing/test_exchange_api-rsa.conf             | 128 +--
 src/testing/test_exchange_api.c                    | 301 +++----
 src/testing/test_exchange_api.conf                 | 102 +--
 .../taler/exchange-offline/master.priv             |   1 +
 .../test_exchange_api_keys_cherry_picking.c        |  92 +-
 .../test_exchange_api_keys_cherry_picking.conf     |  39 +-
 .../test_exchange_api_overlapping_keys_bug.c       |  88 +-
 src/testing/test_exchange_api_revocation.c         | 107 +--
 src/testing/test_exchange_api_twisted.c            | 122 ++-
 src/testing/test_exchange_management_api.c         | 101 +--
 src/testing/test_exchange_p2p.c                    | 139 +---
 src/testing/test_kyc_api.c                         | 122 +--
 src/testing/test_kyc_api.conf                      | 202 +----
 src/testing/test_taler_exchange_aggregator.c       | 257 ++----
 src/testing/test_taler_exchange_wirewatch.c        | 134 +--
 src/testing/testing_api_cmd_auditor_add.c          |  72 +-
 .../testing_api_cmd_auditor_add_denom_sig.c        |  72 +-
 src/testing/testing_api_cmd_auditor_del.c          |  59 +-
 .../testing_api_cmd_auditor_deposit_confirmation.c |  53 +-
 src/testing/testing_api_cmd_auditor_exchanges.c    |  54 +-
 .../testing_api_cmd_bank_admin_add_incoming.c      |  29 +-
 src/testing/testing_api_cmd_bank_admin_check.c     |  28 +-
 src/testing/testing_api_cmd_bank_check.c           |  57 +-
 src/testing/testing_api_cmd_bank_check_empty.c     |  26 +-
 src/testing/testing_api_cmd_bank_history_credit.c  | 321 +++----
 src/testing/testing_api_cmd_bank_history_debit.c   | 317 +++----
 src/testing/testing_api_cmd_bank_transfer.c        |  24 +-
 src/testing/testing_api_cmd_batch.c                |  36 +-
 src/testing/testing_api_cmd_batch_deposit.c        |  24 +-
 src/testing/testing_api_cmd_batch_withdraw.c       |  38 +-
 src/testing/testing_api_cmd_change_auth.c          | 153 ----
 src/testing/testing_api_cmd_check_aml_decision.c   |  43 +-
 src/testing/testing_api_cmd_check_aml_decisions.c  |  38 +-
 src/testing/testing_api_cmd_check_keys.c           | 168 ++--
 src/testing/testing_api_cmd_connect_with_state.c   | 191 +++++
 src/testing/testing_api_cmd_contract_get.c         |  24 +-
 src/testing/testing_api_cmd_deposit.c              |  29 +-
 src/testing/testing_api_cmd_deposits_get.c         |  39 +-
 src/testing/testing_api_cmd_get_auditor.c          | 280 +++++++
 src/testing/testing_api_cmd_get_exchange.c         | 308 +++++++
 src/testing/testing_api_cmd_insert_deposit.c       |  83 +-
 src/testing/testing_api_cmd_kyc_check_get.c        |  26 +-
 src/testing/testing_api_cmd_kyc_proof.c            |  25 +-
 src/testing/testing_api_cmd_kyc_wallet_get.c       |  36 +-
 src/testing/testing_api_cmd_purse_create_deposit.c |  24 +-
 src/testing/testing_api_cmd_purse_delete.c         |  24 +-
 src/testing/testing_api_cmd_purse_deposit.c        |  30 +-
 src/testing/testing_api_cmd_purse_get.c            |  12 +-
 src/testing/testing_api_cmd_purse_merge.c          |  39 +-
 src/testing/testing_api_cmd_recoup.c               |  21 +-
 src/testing/testing_api_cmd_recoup_refresh.c       |  21 +-
 src/testing/testing_api_cmd_refresh.c              | 138 ++-
 src/testing/testing_api_cmd_refund.c               |  29 +-
 src/testing/testing_api_cmd_reserve_attest.c       |  12 +-
 src/testing/testing_api_cmd_reserve_close.c        |  12 +-
 src/testing/testing_api_cmd_reserve_get.c          |  12 +-
 .../testing_api_cmd_reserve_get_attestable.c       |  12 +-
 src/testing/testing_api_cmd_reserve_history.c      | 162 ++--
 src/testing/testing_api_cmd_reserve_open.c         |  12 +-
 src/testing/testing_api_cmd_reserve_purse.c        |  41 +-
 src/testing/testing_api_cmd_reserve_status.c       | 155 ++--
 src/testing/testing_api_cmd_revoke_denom_key.c     |  48 +-
 src/testing/testing_api_cmd_revoke_sign_key.c      |  48 +-
 src/testing/testing_api_cmd_rewind.c               | 197 -----
 src/testing/testing_api_cmd_run_fakebank.c         | 211 +++++
 src/testing/testing_api_cmd_serialize_keys.c       | 157 +---
 src/testing/testing_api_cmd_set_officer.c          |  71 +-
 src/testing/testing_api_cmd_set_wire_fee.c         |  54 +-
 src/testing/testing_api_cmd_stat.c                 |  17 +-
 src/testing/testing_api_cmd_system_start.c         |  22 +-
 src/testing/testing_api_cmd_take_aml_decision.c    |  40 +-
 src/testing/testing_api_cmd_transfer_get.c         |  47 +-
 src/testing/testing_api_cmd_twister_exec_client.c  |   2 +-
 src/testing/testing_api_cmd_wire.c                 |  22 +-
 src/testing/testing_api_cmd_wire_add.c             |  56 +-
 src/testing/testing_api_cmd_wire_del.c             |  54 +-
 src/testing/testing_api_cmd_withdraw.c             |  61 +-
 src/testing/testing_api_helpers_bank.c             |  41 -
 src/testing/testing_api_helpers_exchange.c         | 192 -----
 src/testing/testing_api_loop.c                     | 866 +++++++++++--------
 src/testing/testing_api_misc.c                     | 377 +++++++++
 src/testing/testing_api_traits.c                   |  29 +-
 src/util/age_restriction.c                         |   4 +-
 115 files changed, 4930 insertions(+), 5732 deletions(-)

diff --git a/src/auditor/auditor.conf b/src/auditor/auditor.conf
index 27083628..5ec70346 100644
--- a/src/auditor/auditor.conf
+++ b/src/auditor/auditor.conf
@@ -17,7 +17,7 @@ AUDITOR_PRIV_FILE = 
${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
 
 # What is the Web site of the auditor (i.e. to file complaints about
 # a misbehaving exchange)?
-# BASE_URL = https://auditor.taler.net/
+BASE_URL = http://localhost:8083/
 
 
 # Network configuration for the normal API/service HTTP server
diff --git a/src/auditor/taler-auditor-httpd.c 
b/src/auditor/taler-auditor-httpd.c
index a212eddc..68316082 100644
--- a/src/auditor/taler-auditor-httpd.c
+++ b/src/auditor/taler-auditor-httpd.c
@@ -132,7 +132,7 @@ handle_mhd_completion_callback (void *cls,
 
 
 /**
- * Handle a "/version" request.
+ * Handle a "/config" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -142,11 +142,11 @@ handle_mhd_completion_callback (void *cls,
  * @return MHD result code
   */
 static MHD_RESULT
-handle_version (struct TAH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size)
+handle_config (struct TAH_RequestHandler *rh,
+               struct MHD_Connection *connection,
+               void **connection_cls,
+               const char *upload_data,
+               size_t *upload_data_size)
 {
   static json_t *ver; /* we build the response only once, keep around for next 
query! */
 
@@ -207,9 +207,9 @@ handle_mhd_request (void *cls,
     { "/exchanges", MHD_HTTP_METHOD_GET, "application/json",
       NULL, 0,
       &TAH_EXCHANGES_handler, MHD_HTTP_OK },
-    { "/version", MHD_HTTP_METHOD_GET, "application/json",
+    { "/config", MHD_HTTP_METHOD_GET, "application/json",
       NULL, 0,
-      &handle_version, MHD_HTTP_OK },
+      &handle_config, MHD_HTTP_OK },
     /* Landing page, for now tells humans to go away
      * (NOTE: ideally, the reverse proxy will respond with a nicer page) */
     { "/", MHD_HTTP_METHOD_GET, "text/plain",
diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am
index de93cc74..3c4ee117 100644
--- a/src/benchmark/Makefile.am
+++ b/src/benchmark/Makefile.am
@@ -12,11 +12,9 @@ endif
 
 bin_PROGRAMS = \
   taler-aggregator-benchmark \
-  taler-bank-benchmark \
-  taler-exchange-benchmark
+  taler-bank-benchmark
 
-bin_SCRIPTS = \
-  taler-benchmark-setup.sh
+# taler-exchange-benchmark
 
 
 taler_aggregator_benchmark_SOURCES = \
@@ -75,5 +73,4 @@ EXTRA_DIST = \
   bank-benchmark-rsa.conf \
   coins-cs.conf \
   coins-rsa.conf \
-  $(bin_SCRIPTS) \
   exchange_benchmark_home/.local/share/taler/exchange/offline-keys/master.priv
diff --git a/src/benchmark/benchmark-common.conf 
b/src/benchmark/benchmark-common.conf
index 5ac4a397..b1794c28 100644
--- a/src/benchmark/benchmark-common.conf
+++ b/src/benchmark/benchmark-common.conf
@@ -58,11 +58,11 @@ MAX_DEBT_BANK=EUR:1000000000000000.0
 DATABASE=bank-db.sqlite3
 
 [libeufin-nexus]
-#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix."
+#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432"
 DB_CONNECTION="jdbc:sqlite:libeufin-nexus.sqlite3"
 
 [libeufin-sandbox]
-#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix."
+#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432"
 DB_CONNECTION="jdbc:sqlite:libeufin-sandbox.sqlite3"
 
 [auditor]
diff --git a/src/benchmark/taler-bank-benchmark.c 
b/src/benchmark/taler-bank-benchmark.c
index 584df489..5c9c46af 100644
--- a/src/benchmark/taler-bank-benchmark.c
+++ b/src/benchmark/taler-bank-benchmark.c
@@ -322,11 +322,8 @@ launch_clients (void)
   if (1 == howmany_clients)
   {
     /* do everything in this process */
-    result = TALER_TESTING_setup (&run,
-                                  NULL,
-                                  cfg,
-                                  NULL,
-                                  GNUNET_NO);
+    result = TALER_TESTING_loop (&run,
+                                 NULL);
     if (verbose)
       print_stats ();
     return result;
@@ -340,11 +337,8 @@ launch_clients (void)
       GNUNET_log_setup ("benchmark-worker",
                         NULL == loglev ? "INFO" : loglev,
                         logfile);
-      result = TALER_TESTING_setup (&run,
-                                    NULL,
-                                    cfg,
-                                    NULL,
-                                    GNUNET_NO);
+      result = TALER_TESTING_loop (&run,
+                                   NULL);
       if (verbose)
         print_stats ();
       if (GNUNET_OK != result)
diff --git a/src/benchmark/taler-exchange-benchmark.c 
b/src/benchmark/taler-exchange-benchmark.c
index e29e117d..daae5ba6 100644
--- a/src/benchmark/taler-exchange-benchmark.c
+++ b/src/benchmark/taler-exchange-benchmark.c
@@ -85,11 +85,6 @@ enum BenchmarkMode
  */
 static const struct TALER_EXCHANGEDB_AccountInfo *exchange_bank_account;
 
-/**
- * Configuration of our exchange.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
 /**
  * Hold information about a user at the bank.
  */
@@ -146,11 +141,6 @@ static unsigned int refresh_rate = 10;
  */
 static unsigned int howmany_clients = 1;
 
-/**
- * Bank configuration to use.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
 /**
  * Log level used during the run.
  */
@@ -474,45 +464,6 @@ print_stats (void)
 }
 
 
-/**
- * Stop the fakebank.
- *
- * @param cls fakebank handle
- */
-static void
-stop_fakebank (void *cls)
-{
-  struct TALER_FAKEBANK_Handle *fakebank = cls;
-
-  TALER_FAKEBANK_stop (fakebank);
-}
-
-
-/**
- * Start the fakebank.
- *
- * @param cls NULL
- */
-static void
-launch_fakebank (void *cls)
-{
-  struct TALER_FAKEBANK_Handle *fakebank;
-
-  (void) cls;
-  fakebank
-    = TALER_TESTING_run_fakebank (
-        exchange_bank_account->auth->wire_gateway_url,
-        currency);
-  if (NULL == fakebank)
-  {
-    GNUNET_break (0);
-    return;
-  }
-  GNUNET_SCHEDULER_add_shutdown (&stop_fakebank,
-                                 fakebank);
-}
-
-
 /**
  * Run the benchmark in parallel in many (client) processes
  * and summarize result.
@@ -531,148 +482,6 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
   pid_t cpids[howmany_clients];
   pid_t fakebank = -1;
   int wstatus;
-  struct GNUNET_OS_Process *bankd = NULL;
-  struct GNUNET_OS_Process *auditord = NULL;
-  struct GNUNET_OS_Process *exchanged = NULL;
-  struct GNUNET_OS_Process *wirewatch = NULL;
-  struct GNUNET_OS_Process *exchange_slave = NULL;
-  struct GNUNET_DISK_PipeHandle *exchange_slave_pipe;
-
-  if ( (MODE_CLIENT == mode) ||
-       (MODE_BOTH == mode) )
-  {
-    if (use_fakebank)
-    {
-      /* start fakebank */
-      fakebank = fork ();
-      if (0 == fakebank)
-      {
-        GNUNET_log_setup ("benchmark-fakebank",
-                          NULL == loglev ? "INFO" : loglev,
-                          logfile);
-        GNUNET_SCHEDULER_run (&launch_fakebank,
-                              NULL);
-        exit (0);
-      }
-      if (-1 == fakebank)
-      {
-        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
-                             "fork");
-        return GNUNET_SYSERR;
-      }
-    }
-    else
-    {
-      /* start bank */
-      if (GNUNET_OK !=
-          TALER_TESTING_prepare_bank (cfg_filename,
-                                      GNUNET_NO,
-                                      "exchange-account-test",
-                                      &bc))
-      {
-        return 1;
-      }
-      bankd = TALER_TESTING_run_bank (cfg_filename,
-                                      "http://localhost:8082/";);
-      if (NULL == bankd)
-        return 77;
-    }
-  }
-
-  if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
-  {
-    /* start exchange */
-    exchanged = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
-                                         NULL, NULL, NULL,
-                                         "taler-exchange-httpd",
-                                         "taler-exchange-httpd",
-                                         "-c", config_file,
-                                         "-C",
-                                         NULL);
-    if ( (NULL == exchanged) &&
-         (MODE_BOTH == mode) )
-    {
-      if (-1 != fakebank)
-      {
-        kill (fakebank,
-              SIGTERM);
-        waitpid (fakebank,
-                 &wstatus,
-                 0);
-      }
-      if (NULL != bankd)
-      {
-        GNUNET_OS_process_kill (bankd,
-                                SIGTERM);
-        GNUNET_OS_process_destroy (bankd);
-      }
-      return 77;
-    }
-    /* start auditor */
-    auditord = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
-                                        NULL, NULL, NULL,
-                                        "taler-auditor-httpd",
-                                        "taler-auditor-httpd",
-                                        "-c", config_file,
-                                        NULL);
-    if (NULL == auditord)
-    {
-      GNUNET_OS_process_kill (exchanged,
-                              SIGTERM);
-      if (MODE_BOTH == mode)
-      {
-        if (-1 != fakebank)
-        {
-          kill (fakebank,
-                SIGTERM);
-          waitpid (fakebank,
-                   &wstatus,
-                   0);
-        }
-        if (NULL != bankd)
-        {
-          GNUNET_OS_process_kill (bankd,
-                                  SIGTERM);
-          GNUNET_OS_process_destroy (bankd);
-        }
-      }
-      GNUNET_OS_process_destroy (exchanged);
-      return 77;
-    }
-    /* start exchange wirewatch */
-    wirewatch = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
-                                         NULL, NULL, NULL,
-                                         "taler-exchange-wirewatch",
-                                         "taler-exchange-wirewatch",
-                                         "-c", config_file,
-                                         NULL);
-    if (NULL == wirewatch)
-    {
-      GNUNET_OS_process_kill (auditord,
-                              SIGTERM);
-      GNUNET_OS_process_kill (exchanged,
-                              SIGTERM);
-      if (MODE_BOTH == mode)
-      {
-        if (-1 != fakebank)
-        {
-          kill (fakebank,
-                SIGTERM);
-          waitpid (fakebank,
-                   &wstatus,
-                   0);
-        }
-        if (NULL != bankd)
-        {
-          GNUNET_OS_process_kill (bankd,
-                                  SIGTERM);
-          GNUNET_OS_process_destroy (bankd);
-        }
-      }
-      GNUNET_OS_process_destroy (exchanged);
-      return 77;
-    }
-  }
 
   if (MODE_CLIENT == mode)
   {
@@ -710,58 +519,16 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Failed to detect running exchange at `%s'\n",
                 ec.exchange_url);
-    GNUNET_OS_process_kill (exchanged,
-                            SIGTERM);
-    if ( (MODE_BOTH == mode) || (MODE_CLIENT == mode))
-    {
-      if (-1 != fakebank)
-      {
-        kill (fakebank,
-              SIGTERM);
-        waitpid (fakebank,
-                 &wstatus,
-                 0);
-      }
-      if (NULL != bankd)
-      {
-        GNUNET_OS_process_kill (bankd,
-                                SIGTERM);
-        GNUNET_OS_process_destroy (bankd);
-      }
-    }
-    GNUNET_OS_process_wait (exchanged);
-    GNUNET_OS_process_destroy (exchanged);
-    if (NULL != wirewatch)
-    {
-      GNUNET_OS_process_kill (wirewatch,
-                              SIGTERM);
-      GNUNET_OS_process_wait (wirewatch);
-      GNUNET_OS_process_destroy (wirewatch);
-    }
-    if (NULL != auditord)
-    {
-      GNUNET_OS_process_kill (auditord,
-                              SIGTERM);
-      GNUNET_OS_process_wait (auditord);
-      GNUNET_OS_process_destroy (auditord);
-    }
     return 77;
   }
   if ( (MODE_CLIENT == mode) || (MODE_BOTH == mode) )
   {
-    if (-1 != fakebank)
-      sleep (1); /* make sure fakebank process is ready before continuing */
-
     start_time = GNUNET_TIME_absolute_get ();
     result = GNUNET_OK;
-
     if (1 == howmany_clients)
     {
-      result = TALER_TESTING_setup (main_cb,
-                                    main_cb_cls,
-                                    cfg,
-                                    exchanged,
-                                    GNUNET_YES);
+      result = TALER_TESTING_run (main_cb,
+                                  main_cb_cls);
       print_stats ();
     }
     else
@@ -775,11 +542,8 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
                             NULL == loglev ? "INFO" : loglev,
                             logfile);
 
-          result = TALER_TESTING_setup (main_cb,
-                                        main_cb_cls,
-                                        cfg,
-                                        exchanged,
-                                        GNUNET_YES);
+          result = TALER_TESTING_run (main_cb,
+                                      main_cb_cls);
           print_stats ();
           if (GNUNET_OK != result)
             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -845,58 +609,6 @@ parallel_benchmark (TALER_TESTING_Main main_cb,
     GNUNET_OS_process_destroy (exchange_slave);
   }
 
-  if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
-  {
-    GNUNET_assert (NULL != wirewatch);
-    GNUNET_assert (NULL != exchanged);
-    GNUNET_assert (NULL != auditord);
-    /* stop wirewatch */
-    GNUNET_break (0 ==
-                  GNUNET_OS_process_kill (wirewatch,
-                                          SIGTERM));
-    GNUNET_break (GNUNET_OK ==
-                  GNUNET_OS_process_wait (wirewatch));
-    GNUNET_OS_process_destroy (wirewatch);
-    /* stop auditor */
-    GNUNET_break (0 ==
-                  GNUNET_OS_process_kill (auditord,
-                                          SIGTERM));
-    GNUNET_break (GNUNET_OK ==
-                  GNUNET_OS_process_wait (auditord));
-    GNUNET_OS_process_destroy (auditord);
-    /* stop exchange */
-    GNUNET_break (0 ==
-                  GNUNET_OS_process_kill (exchanged,
-                                          SIGTERM));
-    GNUNET_break (GNUNET_OK ==
-                  GNUNET_OS_process_wait (exchanged));
-    GNUNET_OS_process_destroy (exchanged);
-  }
-
-  if ( (MODE_CLIENT == mode) || (MODE_BOTH == mode) )
-  {
-    /* stop fakebank */
-    if (-1 != fakebank)
-    {
-      kill (fakebank,
-            SIGTERM);
-      waitpid (fakebank,
-               &wstatus,
-               0);
-      if ( (! WIFEXITED (wstatus)) ||
-           (0 != WEXITSTATUS (wstatus)) )
-      {
-        GNUNET_break (0);
-        result = GNUNET_SYSERR;
-      }
-    }
-    if (NULL != bankd)
-    {
-      GNUNET_OS_process_kill (bankd,
-                              SIGTERM);
-      GNUNET_OS_process_destroy (bankd);
-    }
-  }
   return result;
 }
 
@@ -1066,39 +778,8 @@ main (int argc,
     GNUNET_free (cfg_filename);
     return BAD_CONFIG_FILE;
   }
-  if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
-  {
-    /* If we use the fakebank, we MUST reset the database as the fakebank
-       will have forgotten everything... */
-    if (GNUNET_OK !=
-        TALER_TESTING_prepare_exchange (cfg_filename,
-                                        (GNUNET_YES == use_fakebank)
-                                        ? GNUNET_YES
-                                        : GNUNET_NO,
-                                        &ec))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to prepare the exchange for launch\n");
-      GNUNET_free (cfg_filename);
-      return BAD_CONFIG_FILE;
-    }
-  }
-  else
-  {
-    if (GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_string (cfg,
-                                               "exchange",
-                                               "BASE_URL",
-                                               &ec.exchange_url))
-    {
-      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                                 "exchange",
-                                 "base_url");
-      GNUNET_CONFIGURATION_destroy (cfg);
-      GNUNET_free (cfg_filename);
-      return BAD_CONFIG_FILE;
-    }
 
+  {
     if (GNUNET_OK !=
         GNUNET_CONFIGURATION_get_value_string (cfg,
                                                "benchmark-remote-exchange",
diff --git a/src/exchange-tools/exchange-offline.conf 
b/src/exchange-tools/exchange-offline.conf
index cfd5c98e..020eb34b 100644
--- a/src/exchange-tools/exchange-offline.conf
+++ b/src/exchange-tools/exchange-offline.conf
@@ -3,10 +3,10 @@
 [exchange-offline]
 
 # Where do we store the offline master private key of the exchange?
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange-offline/master.priv
+MASTER_PRIV_FILE = ${TALER_DATA_HOME}exchange-offline/master.priv
 
 # Where do we store the TOFU key material?
-SECM_TOFU_FILE = ${TALER_DATA_HOME}/exchange-offline/secm_tofus.pub
+SECM_TOFU_FILE = ${TALER_DATA_HOME}exchange-offline/secm_tofus.pub
 
 # Base32-encoded public key of the RSA helper.
 # SECM_DENOM_PUBKEY =
diff --git a/src/include/taler_auditor_service.h 
b/src/include/taler_auditor_service.h
index c20b789c..0beff983 100644
--- a/src/include/taler_auditor_service.h
+++ b/src/include/taler_auditor_service.h
@@ -154,6 +154,7 @@ struct TALER_AUDITOR_HttpResponse
  * @param vi basic information about the auditor
  * @param compat protocol compatibility information
  */
+// FIXME: bad API!
 typedef void
 (*TALER_AUDITOR_VersionCallback) (
   void *cls,
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index 16644076..ea53c2fc 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -5873,7 +5873,7 @@ TALER_age_commitment_hash (
 enum GNUNET_GenericReturnValue
 TALER_age_restriction_commit (
   const struct TALER_AgeMask *mask,
-  const uint8_t age,
+  uint8_t age,
   const struct GNUNET_HashCode *seed,
   struct TALER_AgeCommitmentProof *comm_proof);
 
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index c6c1d3a1..3e0206eb 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -623,15 +623,20 @@ enum TALER_EXCHANGE_CheckKeysFlags
 
 /**
  * Check if our current response for /keys is valid, and if
- * not, trigger /keys download.
+ * not, trigger /keys download.  If @a cb is given, changes
+ * the @a exchange callback for the /keys response.
  *
  * @param exchange exchange to check keys for
  * @param flags options controlling when to download what
+ * @param cb function to call with the /keys response, can be NULL
+ * @param cb_cls closure for @a cb
  * @return until when the existing response is current, 0 if we are 
re-downloading now
  */
 struct GNUNET_TIME_Timestamp
 TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
-                                   enum TALER_EXCHANGE_CheckKeysFlags flags);
+                                   enum TALER_EXCHANGE_CheckKeysFlags flags,
+                                   TALER_EXCHANGE_CertificationCallback cb,
+                                   void *cb_cls);
 
 
 /**
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
index cd9d7ddd..fe1eac27 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -219,12 +219,12 @@ struct TALER_Extensions
  * Generic functions for extensions
  */
 
-/*
+/**
  * @brief Loads the extensions as shared libraries, as specified in the given
  * TALER configuration.
  *
  * @param cfg Handle to the TALER configuration
- * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if unknown extensions were 
found
  *         or any particular configuration couldn't be parsed.
  */
 enum GNUNET_GenericReturnValue
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index 23a4c252..28213d1d 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -27,11 +27,13 @@
 #define TALER_TESTING_LIB_H
 
 #include "taler_util.h"
-#include "taler_exchange_service.h"
+#include <microhttpd.h>
 #include <gnunet/gnunet_json_lib.h>
 #include "taler_json_lib.h"
+#include "taler_auditor_service.h"
 #include "taler_bank_service.h"
-#include <microhttpd.h>
+#include "taler_exchange_service.h"
+#include "taler_fakebank_lib.h"
 
 
 /* ********************* Helper functions ********************* */
@@ -50,153 +52,124 @@
 
 
 /**
- * Allocate and return a piece of wire-details.  Combines
- * a @a payto -URL and adds some salt to create the JSON.
+ * Log an error message about us receiving an unexpected HTTP
+ * status code at the current command and fail the test.
  *
- * @param payto payto://-URL to encapsulate
- * @return JSON describing the account, including the
- *         payto://-URL of the account, must be manually decref'd
+ * @param is interpreter to fail
+ * @param status unexpected HTTP status code received
  */
-json_t *
-TALER_TESTING_make_wire_details (const char *payto);
+#define TALER_TESTING_unexpected_status(is,status)                      \
+  do {                                                                  \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Unexpected response code %u to command %s in %s:%u\n", \
+                status,                                                 \
+                TALER_TESTING_interpreter_get_current_label (is),       \
+                __FILE__,                                               \
+                __LINE__);                                              \
+    TALER_TESTING_interpreter_fail (is);                                \
+  } while (0)
 
 
 /**
- * Find denomination key matching the given amount.
+ * Log an error message about a command not having
+ * run to completion.
  *
- * @param keys array of keys to search
- * @param amount coin value to look for
- * @param age_restricted must the denomination be age restricted?
- * @return NULL if no matching key was found
+ * @param is interpreter
+ * @param label command label of the incomplete command
  */
-const struct TALER_EXCHANGE_DenomPublicKey *
-TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
-                       const struct TALER_Amount *amount,
-                       bool age_restricted);
+#define TALER_TESTING_command_incomplete(is,label)                      \
+  do {                                                                  \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Command %s (%s:%u) did not complete (at %s)\n",        \
+                label,                                                  \
+                __FILE__,                                               \
+                __LINE__,                                               \
+                TALER_TESTING_interpreter_get_current_label (is));      \
+  }while (0)
 
 
 /**
- * Configuration data for an exchange.
+ * Common credentials used in a test.
  */
-struct TALER_TESTING_ExchangeConfiguration
+struct TALER_TESTING_Credentials
 {
   /**
-   * Exchange base URL as it appears in the configuration.  Note
-   * that it might differ from the one where the exchange actually
-   * listens from.
+   * Bank authentication details for the exchange bank
+   * account.
    */
-  char *exchange_url;
+  struct TALER_BANK_AuthenticationData ba;
 
   /**
-   * Auditor base URL as it appears in the configuration.  Note
-   * that it might differ from the one where the auditor actually
-   * listens from.
+   * Configuration file data.
    */
-  char *auditor_url;
-
-};
+  struct GNUNET_CONFIGURATION_Handle *cfg;
 
-/**
- * Connection to the database: aggregates
- * plugin and session handles.
- */
-struct TALER_TESTING_DatabaseConnection
-{
   /**
-   * Database plugin.
+   * Base URL of the exchange.
    */
-  struct TALER_EXCHANGEDB_Plugin *plugin;
+  char *exchange_url;
 
-};
+  /**
+   * Base URL of the auditor.
+   */
+  char *auditor_url;
 
-struct TALER_TESTING_LibeufinServices
-{
   /**
-   * Nexus
+   * RFC 8905 URI of the exchange.
    */
-  struct GNUNET_OS_Process *nexus;
+  char *exchange_payto;
 
   /**
-   * Sandbox
+   * RFC 8905 URI of a user.
    */
-  struct GNUNET_OS_Process *sandbox;
+  char *user42_payto;
 
+  /**
+   * RFC 8905 URI of a user.
+   */
+  char *user43_payto;
 };
 
-/**
- * Prepare launching an exchange.  Checks that the configured
- * port is available, runs taler-exchange-keyup,
- * taler-auditor-sign and taler-exchange-dbinit.  Does not
- * launch the exchange process itself.
- *
- * @param config_filename configuration file to use
- * @param reset_db should we reset the database
- * @param[out] ec will be set to the exchange configuration data
- * @return #GNUNET_OK on success, #GNUNET_NO if test should be
- *         skipped, #GNUNET_SYSERR on test failure
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_exchange (const char *config_filename,
-                                int reset_db,
-                                struct TALER_TESTING_ExchangeConfiguration 
*ec);
-
 
 /**
- * "Canonical" cert_cb used when we are connecting to the
- * Exchange.
- *
- * @param cls closure, typically, the "run" method containing
- *        all the commands to be run, and a closure for it.
- * @param kr response details
+ * What type of bank are we using?
  */
-void
-TALER_TESTING_cert_cb (void *cls,
-                       const struct TALER_EXCHANGE_KeysResponse *kr);
-
-
-/**
- * Wait for the exchange to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
- *
- * @param base_url what URL should we expect the exchange
- *        to be running at
- * @return 0 on success
- */
-int
-TALER_TESTING_wait_exchange_ready (const char *base_url);
-
-
-/**
- * Wait for an HTTPD service to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
- *
- * @param base_url what URL should we expect the exchange
- *        to be running at
- * @return 0 on success
- */
-int
-TALER_TESTING_wait_httpd_ready (const char *base_url);
+enum TALER_TESTING_BankSystem
+{
+  TALER_TESTING_BS_FAKEBANK = 1,
+  TALER_TESTING_BS_IBAN = 2
+};
 
 
 /**
- * Wait for the auditor to have started. Waits for at
- * most 10s, after that returns 77 to indicate an error.
+ * Obtain bank credentials for a given @a cfg_file using
+ * @a exchange_account_section as the basis for the
+ * exchange account.
  *
- * @param base_url what URL should we expect the auditor
- *        to be running at
- * @return 0 on success
+ * @param cfg_file name of configuration to parse
+ * @param exchange_account_section configuration section name for the exchange 
account to use
+ * @param bs type of bank to use
+ * @param[out] ua where to write user account details
+ *         and other credentials
  */
-int
-TALER_TESTING_wait_auditor_ready (const char *base_url);
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+  const char *cfg_file,
+  const char *exchange_account_section,
+  enum TALER_TESTING_BankSystem bs,
+  struct TALER_TESTING_Credentials *ua);
 
 
 /**
- * Remove files from previous runs
+ * Allocate and return a piece of wire-details.  Combines
+ * a @a payto -URL and adds some salt to create the JSON.
  *
- * @param config_name configuration file to use+
+ * @param payto payto://-URL to encapsulate
+ * @return JSON describing the account, including the
+ *         payto://-URL of the account, must be manually decref'd
  */
-void
-TALER_TESTING_cleanup_files (const char *config_name);
+json_t *
+TALER_TESTING_make_wire_details (const char *payto);
 
 
 /**
@@ -212,65 +185,17 @@ TALER_TESTING_cleanup_files_cfg (void *cls,
 
 
 /**
- * Run `taler-exchange-offline`.
- *
- * @param config_filename configuration file to use
- * @param payto_uri bank account to enable, can be NULL
- * @param auditor_pub public key of auditor to enable, can be NULL
- * @param auditor_url URL of auditor to enable, can be NULL
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_run_exchange_offline (const char *config_filename,
-                                    const char *payto_uri,
-                                    const char *auditor_pub,
-                                    const char *auditor_url);
-
-
-/**
- * Run `taler-auditor-dbinit -r` (reset auditor database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_auditor_db_reset (const char *config_filename);
-
-
-/**
- * Run `taler-exchange-dbinit -r` (reset exchange database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_exchange_db_reset (const char *config_filename);
-
-
-/**
- * Run `taler-auditor-offline` tool.
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_run_auditor_offline (const char *config_filename);
-
-
-/**
- * Run `taler-auditor-exchange`.
+ * Find denomination key matching the given amount.
  *
- * @param config_filename configuration file to use
- * @param exchange_master_pub master public key of the exchange
- * @param exchange_base_url what is the base URL of the exchange
- * @param do_remove #GNUNET_NO to add exchange, #GNUNET_YES to remove
- * @return #GNUNET_OK on success
+ * @param keys array of keys to search
+ * @param amount coin value to look for
+ * @param age_restricted must the denomination be age restricted?
+ * @return NULL if no matching key was found
  */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_run_auditor_exchange (const char *config_filename,
-                                    const char *exchange_master_pub,
-                                    const char *exchange_base_url,
-                                    int do_remove);
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
+                       const struct TALER_Amount *amount,
+                       bool age_restricted);
 
 
 /**
@@ -283,194 +208,13 @@ enum GNUNET_GenericReturnValue
 TALER_TESTING_url_port_free (const char *url);
 
 
-/**
- * Configuration data for a bank.
- */
-struct TALER_TESTING_BankConfiguration
-{
-
-  /**
-   * Authentication data for the exchange user at the bank.
-   */
-  struct TALER_BANK_AuthenticationData exchange_auth;
-
-  /**
-   * Payto URL of the exchange's account ("2")
-   */
-  char *exchange_payto;
-
-  /**
-   * Payto URL of a user account ("42")
-   */
-  char *user42_payto;
-
-  /**
-   * Payto URL of another user's account ("43")
-   */
-  char *user43_payto;
-
-};
-
-/**
- * Prepare launching a fakebank.  Check that the configuration
- * file has the right option, and that the port is available.
- * If everything is OK, return the configuration data of the fakebank.
- *
- * @param config_filename configuration file to use
- * @param config_section which account to use
- *                       (must match x-taler-bank)
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_fakebank (const char *config_filename,
-                                const char *config_section,
-                                struct TALER_TESTING_BankConfiguration *bc);
-
-
 /* ******************* Generic interpreter logic ************ */
 
 /**
  * Global state of the interpreter, used by a command
  * to access information about other commands.
  */
-struct TALER_TESTING_Interpreter
-{
-
-  /**
-   * Commands the interpreter will run.
-   */
-  struct TALER_TESTING_Command *commands;
-
-  /**
-   * Interpreter task (if one is scheduled).
-   */
-  struct GNUNET_SCHEDULER_Task *task;
-
-  /**
-   * ID of task called whenever we get a SIGCHILD.
-   * Used for #TALER_TESTING_wait_for_sigchld().
-   */
-  struct GNUNET_SCHEDULER_Task *child_death_task;
-
-  /**
-   * Main execution context for the main loop.
-   */
-  struct GNUNET_CURL_Context *ctx;
-
-  /**
-   * Our configuration.
-   */
-  const struct GNUNET_CONFIGURATION_Handle *cfg;
-
-  /**
-   * Context for running the CURL event loop.
-   */
-  struct GNUNET_CURL_RescheduleContext *rc;
-
-  /**
-   * Handle to our fakebank, if #TALER_TESTING_run_with_fakebank()
-   * was used.  Otherwise NULL.
-   */
-  struct TALER_FAKEBANK_Handle *fakebank;
-
-  /**
-   * Task run on timeout.
-   */
-  struct GNUNET_SCHEDULER_Task *timeout_task;
-
-  /**
-   * Function to call for cleanup at the end. Can be NULL.
-   */
-  GNUNET_SCHEDULER_TaskCallback final_cleanup_cb;
-
-  /**
-   * Closure for #final_cleanup_cb().
-   */
-  void *final_cleanup_cb_cls;
-
-  /**
-   * Instruction pointer.  Tells #interpreter_run() which instruction to run
-   * next.  Need (signed) int because it gets -1 when rewinding the
-   * interpreter to the first CMD.
-   */
-  int ip;
-
-  /**
-   * Result of the testcases, #GNUNET_OK on success
-   */
-  int result;
-
-  /**
-   * Handle to the exchange.
-   */
-  struct TALER_EXCHANGE_Handle *exchange;
-
-  /**
-   * Handle to the auditor.  NULL unless specifically initialized
-   * as part of #TALER_TESTING_auditor_setup().
-   */
-  struct TALER_AUDITOR_Handle *auditor;
-
-  /**
-   * Handle to exchange process; some commands need it
-   * to send signals.  E.g. to trigger the key state reload.
-   */
-  struct GNUNET_OS_Process *exchanged;
-
-  /**
-   * Public key of the auditor.
-   */
-  struct TALER_AuditorPublicKeyP auditor_pub;
-
-  /**
-   * Private key of the auditor.
-   */
-  struct TALER_AuditorPrivateKeyP auditor_priv;
-
-  /**
-   * Private offline signing key.
-   */
-  struct TALER_MasterPrivateKeyP master_priv;
-
-  /**
-   * Public offline signing key.
-   */
-  struct TALER_MasterPublicKeyP master_pub;
-
-  /**
-   * URL of the auditor (as per configuration).
-   */
-  char *auditor_url;
-
-  /**
-   * URL of the exchange (as per configuration).
-   */
-  char *exchange_url;
-
-  /**
-   * Is the interpreter running (#GNUNET_YES) or waiting
-   * for /keys (#GNUNET_NO)?
-   */
-  int working;
-
-  /**
-   * Is the auditor running (#GNUNET_YES) or waiting
-   * for /version (#GNUNET_NO)?
-   */
-  int auditor_working;
-
-  /**
-   * How often have we gotten a /keys response so far?
-   */
-  unsigned int key_generation;
-
-  /**
-   * Exchange keys from last download.
-   */
-  const struct TALER_EXCHANGE_Keys *keys;
-
-};
+struct TALER_TESTING_Interpreter;
 
 
 /**
@@ -490,6 +234,11 @@ struct TALER_TESTING_Command
    */
   const char *label;
 
+  /**
+   * Variable name for the command, NULL for none.
+   */
+  const char *name;
+
   /**
    * Runs the command.  Note that upon return, the interpreter
    * will not automatically run the next command, as the command
@@ -574,7 +323,41 @@ TALER_TESTING_interpreter_lookup_command (struct 
TALER_TESTING_Interpreter *is,
 
 
 /**
- * Obtain main execution context for the main loop.
+ * Get command from hash map by variable name.
+ *
+ * @param is interpreter state.
+ * @param name name of the variable to get command by
+ * @return the command, if it is found, or NULL.
+ */
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
+                                       const char *name);
+
+
+/**
+ * Update the last request time of the current command
+ * to the current time.
+ *
+ * @param[in,out] is interpreter state where to show
+ *       that we are doing something
+ */
+void
+TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Increment the 'num_tries' counter for the current
+ * command.
+ *
+ * @param[in,out] is interpreter state where to
+ *   increment the counter
+ */
+void
+TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Obtain CURL context for the main loop.
  *
  * @param is interpreter state.
  * @return CURL execution context.
@@ -594,15 +377,6 @@ TALER_TESTING_interpreter_get_current_label (
   struct TALER_TESTING_Interpreter *is);
 
 
-/**
- * Get connection handle to the fakebank.
- *
- * @param is interpreter state.
- * @return the handle.
- */
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_interpreter_get_fakebank (struct TALER_TESTING_Interpreter *is);
-
 /**
  * Current command is done, run the next one.
  *
@@ -619,14 +393,6 @@ TALER_TESTING_interpreter_next (struct 
TALER_TESTING_Interpreter *is);
 void
 TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is);
 
-/**
- * Create command array terminator.
- *
- * @return a end-command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_end (void);
-
 
 /**
  * Make the instruction pointer point to @a target_label
@@ -682,20 +448,6 @@ TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
                     struct GNUNET_TIME_Relative timeout);
 
 
-/**
- * First launch the fakebank, then schedule the first CMD
- * in the array of all the CMDs to execute.
- *
- * @param is interpreter state.
- * @param commands array of all the commands to execute.
- * @param bank_url base URL of the fake bank.
- */
-void
-TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is,
-                                 struct TALER_TESTING_Command *commands,
-                                 const char *bank_url);
-
-
 /**
  * The function that contains the array of all the CMDs to run,
  * which is then on charge to call some fashion of
@@ -711,250 +463,93 @@ typedef void
 
 
 /**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
- *
- * @param main_cb the "run" method which coontains all the
- *        commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param cfg configuration to use
- * @param exchanged exchange process handle: will be put in the
- *        state as some commands - e.g. revoke - need to send
- *        signal to it, for example to let it know to reload the
- *        key state. If NULL, the interpreter will run without
- *        trying to connect to the exchange first.
- * @param exchange_connect #GNUNET_YES if the test should connect
- *        to the exchange, #GNUNET_NO otherwise
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- *         non-#GNUNET_OK codes are #GNUNET_SYSERR most of the
- *         times.
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup (TALER_TESTING_Main main_cb,
-                     void *main_cb_cls,
-                     const struct GNUNET_CONFIGURATION_Handle *cfg,
-                     struct GNUNET_OS_Process *exchanged,
-                     int exchange_connect);
-
-
-/**
- * Install signal handlers plus schedules the main wrapper
- * around the "run" method.
- *
- * @param main_cb the "run" method which contains all the
- *        commands.
- * @param main_cb_cls a closure for "run", typically NULL.
- * @param config_filename configuration filename.
- * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
- *         non-GNUNET_OK codes are #GNUNET_SYSERR most of the
- *         times.
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_auditor_setup (TALER_TESTING_Main main_cb,
-                             void *main_cb_cls,
-                             const char *config_filename);
-
-
-/**
- * Closure for #TALER_TESTING_setup_with_exchange_cfg().
- */
-struct TALER_TESTING_SetupContext
-{
-  /**
-   * Main function of the test to run.
-   */
-  TALER_TESTING_Main main_cb;
-
-  /**
-   * Closure for @e main_cb.
-   */
-  void *main_cb_cls;
-
-  /**
-   * Name of the configuration file.
-   */
-  const char *config_filename;
-};
-
-
-/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
+ * Run Taler testing loop.  Starts the GNUnet SCHEDULER (event loop).
  *
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
+ * @param main_cb main function to run
+ * @param main_cb_cls closure for @a main_cb
  */
 enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_exchange_cfg (
-  void *cls,
-  const struct GNUNET_CONFIGURATION_Handle *cfg);
+TALER_TESTING_loop (TALER_TESTING_Main main_cb,
+                    void *main_cb_cls);
 
 
 /**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
+ * Convenience function to run a test.
  *
- * @param main_cb main method.
- * @param main_cb_cls main method closure.
- * @param config_file configuration file name.  Is is used
- *        by both this function and the exchange itself.  In the
- *        first case it gives out the exchange port number and
- *        the exchange base URL so as to check whether the port
- *        is available and the exchange responds when requested
- *        at its base URL.
- * @return #GNUNET_OK if no errors occurred.
+ * @param argv command-line arguments given
+ * @param loglevel log level to use
+ * @param cfg_file configuration file to use
+ * @param exchange_account_section configuration section
+ *   with exchange bank account to use
+ * @param bs bank system to use
+ * @param[in,out] cred global credentials to initialize
+ * @param main_cb main test function to run
+ * @param main_cb_cls closure for @a main_cb
+ * @return 0 on success, 77 on setup trouble, non-zero process status code 
otherwise
  */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb,
-                                   void *main_cb_cls,
-                                   const char *config_file);
+int
+TALER_TESTING_main (char *const *argv,
+                    const char *loglevel,
+                    const char *cfg_file,
+                    const char *exchange_account_section,
+                    enum TALER_TESTING_BankSystem bs,
+                    struct TALER_TESTING_Credentials *cred,
+                    TALER_TESTING_Main main_cb,
+                    void *main_cb_cls);
 
 
 /**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using
- * the given configuration file.
+ * Callback over commands of an interpreter.
  *
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
+ * @param cls closure
+ * @param cmd a command to process
  */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_auditor_and_exchange_cfg (
+typedef void
+(*TALER_TESTING_CommandIterator)(
   void *cls,
-  const struct GNUNET_CONFIGURATION_Handle *cfg);
-
-
-/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the auditor and exchange using
- * the given configuration file.
- *
- * @param main_cb main method.
- * @param main_cb_cls main method closure.
- * @param config_file configuration file name.  Is is used
- *        by both this function and the exchange itself.  In the
- *        first case it gives out the exchange port number and
- *        the exchange base URL so as to check whether the port
- *        is available and the exchange responds when requested
- *        at its base URL.
- * @return #GNUNET_OK if no errors occurred.
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb,
-                                               void *main_cb_cls,
-                                               const char *config_file);
-
-
-/**
- * Start the (Python) bank process.  Assume the port
- * is available and the database is clean.  Use the "prepare
- * bank" function to do such tasks.
- *
- * @param config_filename configuration filename.
- * @param bank_url base URL of the bank, used by `wget' to check
- *        that the bank was started right.
- * @return the process, or NULL if the process could not
- *         be started.
- */
-struct GNUNET_OS_Process *
-TALER_TESTING_run_bank (const char *config_filename,
-                        const char *bank_url);
-
-
-/**
- * Prepare libeufin sandbox execution.  Check if the port is available and
- * reset database.
- *
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param config_section which configuration section should be used
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_libeufin (const char *config_filename,
-                                bool reset_db,
-                                const char *config_section,
-                                struct TALER_TESTING_BankConfiguration *bc);
+  const struct TALER_TESTING_Command *cmd);
 
 
 /**
- * Start the (nexus) bank process.  Assume the port
- * is available and the database is clean.  Use the "prepare
- * bank" function to do such tasks.  This function is also
- * responsible to create the exchange EBICS subscriber at
- * the nexus.
+ * Iterates over all of the top-level commands of an
+ * interpreter.
  *
- * @param bc bank configuration of the bank
- * @return the process, or NULL if the process could not
- *         be started.
+ * @param[in] interpreter to iterate over
+ * @param asc true in execution order, false for reverse execution order
+ * @param cb function to call on each command
+ * @param cb_cls closure for cb
  */
-struct TALER_TESTING_LibeufinServices
-TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc);
-
-
-/**
- * Runs the Fakebank by guessing / extracting the portnumber
- * from the base URL.
- *
- * @param bank_url bank's base URL.
- * @param currency currency the bank uses
- * @return the fakebank process handle, or NULL if any
- *         error occurs.
- */
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_run_fakebank (const char *bank_url,
-                            const char *currency);
-
-
-/**
- * Prepare the bank execution.  Check if the port is available
- * and reset database.
- *
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param config_section which configuration section should be used
- * @param[out] bc set to the bank's configuration data
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_bank (const char *config_filename,
-                            bool reset_db,
-                            const char *config_section,
-                            struct TALER_TESTING_BankConfiguration *bc);
+void
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+                       bool asc,
+                       TALER_TESTING_CommandIterator cb,
+                       void *cb_cls);
 
-/**
- * Prepare the Nexus execution.  Check if the port is available
- * and delete old database.
- *
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param config_section section of the configuration with the exchange's 
account
- * @param[out] bc set to the bank's configuration data
- * @return the base url, or NULL upon errors.  Must be freed
- *         by the caller.
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_nexus (const char *config_filename,
-                             int reset_db,
-                             const char *config_section,
-                             struct TALER_TESTING_BankConfiguration *bc);
 
 /**
  * Look for substring in a programs' name.
  *
  * @param prog program's name to look into
  * @param marker chunk to find in @a prog
+ * @return true if @a marker is in @a prog
  */
-enum GNUNET_GenericReturnValue
+bool
 TALER_TESTING_has_in_name (const char *prog,
                            const char *marker);
 
 
+/**
+ * Wait for an HTTPD service to have started. Waits for at
+ * most 10s, after that returns 77 to indicate an error.
+ *
+ * @param base_url what URL should we expect the exchange
+ *        to be running at
+ * @return 0 on success
+ */
+int
+TALER_TESTING_wait_httpd_ready (const char *base_url);
+
+
 /**
  * Parse reference to a coin.
  *
@@ -986,6 +581,28 @@ TALER_TESTING_history_entry_cmp (
 /* ************** Specific interpreter commands ************ */
 
 
+/**
+ * Create command array terminator.
+ *
+ * @return a end-command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void);
+
+
+/**
+ * Set variable to command as side-effect of
+ * running a command.
+ *
+ * @param name name of the variable to set
+ * @param cmd command to set to variable when run
+ * @return modified command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+                           struct TALER_TESTING_Command cmd);
+
+
 /**
  * Launch GNU Taler setup.
  *
@@ -1001,6 +618,55 @@ TALER_TESTING_cmd_system_start (
   ...);
 
 
+/**
+ * Connects to the exchange.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param wait_for_keys block until we got /keys
+ * @param load_private_key obtain private key from file indicated in @a cfg
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  bool wait_for_keys,
+  bool load_private_key);
+
+
+/**
+ * Connects to the auditor.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param load_auditor_keys obtain auditor keys from file indicated in @a cfg
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_auditor (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  bool load_auditor_keys);
+
+
+/**
+ * Runs the Fakebank in-process by guessing / extracting the portnumber
+ * from the base URL.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param exchange_account_section configuration section
+ *   to use to determine bank account of the exchange
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  const char *exchange_account_section);
+
+
 /**
  * Command to modify authorization header used in the CURL context.
  * This will destroy the existing CURL context and create a fresh
@@ -1115,7 +781,6 @@ TALER_TESTING_cmd_exec_auditor_dbinit (const char *label,
  * Create a "deposit-confirmation" command.
  *
  * @param label command label.
- * @param auditor auditor connection.
  * @param deposit_reference reference to any operation that can
  *        provide a coin.
  * @param coin_index if @a deposit_reference offers an array of
@@ -1128,7 +793,6 @@ TALER_TESTING_cmd_exec_auditor_dbinit (const char *label,
  */
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_deposit_confirmation (const char *label,
-                                        struct TALER_AUDITOR_Handle *auditor,
                                         const char *deposit_reference,
                                         unsigned int coin_index,
                                         const char *amount_without_fee,
@@ -1151,13 +815,11 @@ TALER_TESTING_cmd_deposit_confirmation_with_retry (
  * Create a "list exchanges" command.
  *
  * @param label command label.
- * @param auditor auditor connection.
  * @param expected_response_code expected HTTP response code.
  * @return the command.
  */
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_exchanges (const char *label,
-                             struct TALER_AUDITOR_Handle *auditor,
                              unsigned int expected_response_code);
 
 
@@ -2129,13 +1791,10 @@ TALER_TESTING_cmd_wait_service (const char *label,
  * Make a "check keys" command.
  *
  * @param label command label
- * @param generation how many /keys responses are expected to
- *        have been returned when this CMD will be run.
  * @return the command.
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
-                              unsigned int generation);
+TALER_TESTING_cmd_check_keys (const char *label);
 
 
 /**
@@ -2143,16 +1802,10 @@ TALER_TESTING_cmd_check_keys (const char *label,
  * just redownload the whole /keys.
  *
  * @param label command label
- * @param generation when this command is run, exactly @a
- *        generation /keys downloads took place.  If the number
- *        of downloads is less than @a generation, the logic will
- *        first make sure that @a generation downloads are done,
- *        and _then_ execute the rest of the command.
  * @return the command.
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
-                                            unsigned int generation);
+TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label);
 
 
 /**
@@ -2160,11 +1813,6 @@ TALER_TESTING_cmd_check_keys_pull_all_keys (const char 
*label,
  * used in the request for /keys.
  *
  * @param label command label
- * @param generation when this command is run, exactly @a
- *        generation /keys downloads took place.  If the number
- *        of downloads is less than @a generation, the logic will
- *        first make sure that @a generation downloads are done,
- *        and _then_ execute the rest of the command.
  * @param last_denom_date_ref previous /keys command to use to
  *        obtain the "last_denom_date" value from; "zero" can be used
  *        as a special value to force an absolute time of zero to be
@@ -2174,7 +1822,6 @@ TALER_TESTING_cmd_check_keys_pull_all_keys (const char 
*label,
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_check_keys_with_last_denom (
   const char *label,
-  unsigned int generation,
   const char *last_denom_date_ref);
 
 
@@ -2203,13 +1850,18 @@ TALER_TESTING_cmd_batch (const char *label,
 bool
 TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd);
 
+
 /**
  * Advance internal pointer to next command.
  *
  * @param is interpreter state.
+ * @param[in,out] cls closure of the batch
+ * @return true to advance IP in parent
  */
-void
-TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is);
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+                              void *cls);
+
 
 /**
  * Obtain what command the batch is at.
@@ -2255,11 +1907,12 @@ struct TALER_TESTING_Command
 TALER_TESTING_cmd_connect_with_state (const char *label,
                                       const char *state_reference);
 
+
 /**
  * Make the "insert-deposit" CMD.
  *
  * @param label command label.
- * @param dbc collects plugin and session handles
+ * @param db_cfg configuration to talk to the DB
  * @param merchant_name Human-readable name of the merchant.
  * @param merchant_account merchant's account name (NOT a payto:// URI)
  * @param exchange_timestamp when did the exchange receive the deposit
@@ -2272,7 +1925,7 @@ TALER_TESTING_cmd_connect_with_state (const char *label,
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_insert_deposit (
   const char *label,
-  const struct TALER_TESTING_DatabaseConnection *dbc,
+  const struct GNUNET_CONFIGURATION_Handle *db_cfg,
   const char *merchant_name,
   const char *merchant_account,
   struct GNUNET_TIME_Timestamp exchange_timestamp,
@@ -2993,9 +2646,13 @@ TALER_TESTING_get_trait (const struct 
TALER_TESTING_Trait *traits,
   op (bank_row, const uint64_t)                                    \
   op (officer_pub, const struct TALER_AmlOfficerPublicKeyP)        \
   op (officer_priv, const struct TALER_AmlOfficerPrivateKeyP)      \
-  op (officer_name, const char *)                                  \
+  op (officer_name, const char)                                  \
   op (aml_decision, enum TALER_AmlDecisionState)                   \
-  op (aml_justification, const char *)                             \
+  op (aml_justification, const char)                             \
+  op (auditor_priv, const struct TALER_AuditorPrivateKeyP)     \
+  op (auditor_pub, const struct TALER_AuditorPublicKeyP)       \
+  op (master_priv, const struct TALER_MasterPrivateKeyP)     \
+  op (master_pub, const struct TALER_MasterPublicKeyP)       \
   op (purse_priv, const struct TALER_PurseContractPrivateKeyP)     \
   op (purse_pub, const struct TALER_PurseContractPublicKeyP)       \
   op (merge_priv, const struct TALER_PurseMergePrivateKeyP)        \
@@ -3011,21 +2668,23 @@ TALER_TESTING_get_trait (const struct 
TALER_TESTING_Trait *traits,
   op (merchant_pub, const struct TALER_MerchantPublicKeyP)         \
   op (merchant_sig, const struct TALER_MerchantSignatureP)         \
   op (wtid, const struct TALER_WireTransferIdentifierRawP)         \
+  op (bank_auth_data, const struct TALER_BANK_AuthenticationData)  \
   op (contract_terms, const json_t)                                \
   op (wire_details, const json_t)                                  \
   op (exchange_keys, const json_t)                                 \
-  op (exchange_url, const char *)                                  \
-  op (exchange_bank_account_url, const char *)                     \
-  op (taler_uri, const char *)                                     \
-  op (payto_uri, const char *)                                     \
-  op (kyc_url, const char *)                                       \
-  op (web_url, const char *)                                       \
+  op (exchange_url, const char)                                    \
+  op (auditor_url, const char)                                     \
+  op (exchange_bank_account_url, const char)                       \
+  op (taler_uri, const char)                                       \
+  op (payto_uri, const char)                                       \
+  op (kyc_url, const char)                                         \
+  op (web_url, const char)                                         \
   op (row, const uint64_t)                                         \
   op (legi_requirement_row, const uint64_t)                        \
   op (array_length, const unsigned int)                            \
-  op (credit_payto_uri, const char *)                              \
-  op (debit_payto_uri, const char *)                               \
-  op (order_id, const char *)                                      \
+  op (credit_payto_uri, const char)                                \
+  op (debit_payto_uri, const char)                                 \
+  op (order_id, const char)                                        \
   op (amount, const struct TALER_Amount)                           \
   op (amount_with_fee, const struct TALER_Amount)                  \
   op (batch_cmds, struct TALER_TESTING_Command *)                  \
@@ -3033,6 +2692,9 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
   op (fresh_coins, const struct TALER_TESTING_FreshCoinData *)     \
   op (claim_token, const struct TALER_ClaimTokenP)                 \
   op (relative_time, const struct GNUNET_TIME_Relative)            \
+  op (auditor, struct TALER_AUDITOR_Handle)                        \
+  op (exchange, struct TALER_EXCHANGE_Handle)                      \
+  op (fakebank, struct TALER_FAKEBANK_Handle)                      \
   op (process, struct GNUNET_OS_Process *)
 
 
@@ -3067,4 +2729,16 @@ TALER_TESTING_SIMPLE_TRAITS 
(TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
 TALER_TESTING_INDEXED_TRAITS (TALER_TESTING_MAKE_DECL_INDEXED_TRAIT)
 
 
+/* ****************** convenience functions ************** */
+
+/**
+ * Get exchange handle from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange handle, or NULL on error
+ */
+struct TALER_EXCHANGE_Handle *
+TALER_TESTING_get_exchange (struct TALER_TESTING_Interpreter *is);
+
+
 #endif
diff --git a/src/lib/auditor_api_handle.c b/src/lib/auditor_api_handle.c
index 9edb1115..14869de4 100644
--- a/src/lib/auditor_api_handle.c
+++ b/src/lib/auditor_api_handle.c
@@ -102,7 +102,7 @@ struct TALER_AUDITOR_Handle
   void *version_cb_cls;
 
   /**
-   * Data for the request to get the /version of a auditor,
+   * Data for the request to get the /config of a auditor,
    * NULL once we are past stage #MHS_INIT.
    */
   struct GNUNET_CURL_Job *vr;
@@ -113,12 +113,12 @@ struct TALER_AUDITOR_Handle
   char *vr_url;
 
   /**
-   * Task for retrying /version request.
+   * Task for retrying /config request.
    */
   struct GNUNET_SCHEDULER_Task *retry_task;
 
   /**
-   * /version data of the auditor, only valid if
+   * /config data of the auditor, only valid if
    * @e handshake_complete is past stage #MHS_VERSION.
    */
   char *version;
@@ -129,7 +129,7 @@ struct TALER_AUDITOR_Handle
   struct TALER_AUDITOR_VersionInformation vi;
 
   /**
-   * Retry /version frequency.
+   * Retry /config frequency.
    */
   struct GNUNET_TIME_Relative retry_delay;
 
@@ -141,10 +141,10 @@ struct TALER_AUDITOR_Handle
 };
 
 
-/* ***************** Internal /version fetching ************* */
+/* ***************** Internal /config fetching ************* */
 
 /**
- * Decode the JSON in @a resp_obj from the /version response and store the data
+ * Decode the JSON in @a resp_obj from the /config response and store the data
  * in the @a key_data.
  *
  * @param[in] resp_obj JSON object to parse
@@ -216,16 +216,16 @@ decode_version_json (const json_t *resp_obj,
 
 
 /**
- * Initiate download of /version from the auditor.
+ * Initiate download of /config from the auditor.
  *
- * @param cls auditor where to download /version from
+ * @param cls auditor where to download /config from
  */
 static void
 request_version (void *cls);
 
 
 /**
- * Callback used when downloading the reply to a /version request
+ * Callback used when downloading the reply to a /config request
  * is complete.
  *
  * @param cls the `struct TALER_AUDITOR_Handle`
@@ -267,7 +267,7 @@ version_completed_cb (void *cls,
     if (NULL == resp_obj)
     {
       GNUNET_break_op (0);
-      TALER_LOG_WARNING ("NULL body for a 200-OK /version\n");
+      TALER_LOG_WARNING ("NULL body for a 200-OK /config\n");
       hr.http_status = 0;
       hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
       break;
@@ -295,7 +295,7 @@ version_completed_cb (void *cls,
   if (MHD_HTTP_OK != response_code)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "/version failed for auditor %s: %u!\n",
+                "/config failed for auditor %s: %u!\n",
                 auditor->url,
                 (unsigned int) response_code);
     auditor->state = MHS_FAILED;
@@ -309,7 +309,7 @@ version_completed_cb (void *cls,
   TALER_LOG_DEBUG ("Switching auditor state to 'version'\n");
   auditor->state = MHS_VERSION;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Auditor %s is now READY!\n",
+              "Auditor %s is ready!\n",
               auditor->url);
   /* notify application about the key information */
   auditor->version_cb (auditor->version_cb_cls,
@@ -320,9 +320,9 @@ version_completed_cb (void *cls,
 
 
 /**
- * Initiate download of /version from the auditor.
+ * Initiate download of /config from the auditor.
  *
- * @param cls auditor where to download /version from
+ * @param cls auditor where to download /config from
  */
 static void
 request_version (void *cls)
@@ -406,7 +406,7 @@ TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
   auditor->ctx = ctx;
   auditor->url = GNUNET_strdup (url);
   auditor->vr_url = TALER_AUDITOR_path_to_url_ (auditor,
-                                                "/version");
+                                                "/config");
   if (NULL == auditor->vr_url)
   {
     GNUNET_break (0);
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index d78b6185..20bac43a 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -1268,14 +1268,24 @@ TALER_EXCHANGE_set_last_denom (struct 
TALER_EXCHANGE_Handle *exchange,
 
 struct GNUNET_TIME_Timestamp
 TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
-                                   enum TALER_EXCHANGE_CheckKeysFlags flags)
+                                   enum TALER_EXCHANGE_CheckKeysFlags flags,
+                                   TALER_EXCHANGE_CertificationCallback cb,
+                                   void *cb_cls)
 {
   bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
   bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS);
 
+  if ( (NULL != cb) &&
+       ( (exchange->cert_cb != cb) ||
+         (exchange->cert_cb_cls != cb_cls) ) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Changing target of exchange certification callback\n");
+    exchange->cert_cb = cb;
+    exchange->cert_cb_cls = cb_cls;
+  }
   if (NULL != exchange->kr)
     return GNUNET_TIME_UNIT_ZERO_TS;
-
   if (pull_all_keys)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2269,7 +2279,9 @@ const struct TALER_EXCHANGE_Keys *
 TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
 {
   (void) TALER_EXCHANGE_check_keys_current (exchange,
-                                            TALER_EXCHANGE_CKF_NONE);
+                                            TALER_EXCHANGE_CKF_NONE,
+                                            NULL,
+                                            NULL);
   return &exchange->key_data;
 }
 
@@ -2278,7 +2290,9 @@ json_t *
 TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
 {
   (void) TALER_EXCHANGE_check_keys_current (exchange,
-                                            TALER_EXCHANGE_CKF_NONE);
+                                            TALER_EXCHANGE_CKF_NONE,
+                                            NULL,
+                                            NULL);
   return json_deep_copy (exchange->key_data_raw);
 }
 
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 6a19ba00..e18e900a 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -46,3 +46,12 @@ test_exchange_api_twisted_cs
 test_exchange_api_twisted_rsa
 test_exchange_p2p_cs
 test_exchange_p2p_rsa
+*.edited
+tmp-last-response.*
+test_exchange_api_home/taler/auditor/
+test_exchange_api_home/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_home/taler/exchange-secmod-cs/
+test_exchange_api_home/taler/exchange-secmod-eddsa/
+test_exchange_api_home/taler/exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/taler/
+test_taler_exchange_httpd_home/taler/
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 8aa3ac1b..090358c3 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -13,6 +13,9 @@ endif
 clean-local:
        rm -rf report*
 
+bin_SCRIPTS = \
+  taler-unified-setup.sh
+
 # Libraries
 
 lib_LTLIBRARIES = \
@@ -54,11 +57,11 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_batch.c \
   testing_api_cmd_batch_deposit.c \
   testing_api_cmd_batch_withdraw.c \
-  testing_api_cmd_change_auth.c \
   testing_api_cmd_check_aml_decision.c \
   testing_api_cmd_check_aml_decisions.c \
   testing_api_cmd_check_keys.c \
   testing_api_cmd_common.c \
+  testing_api_cmd_connect_with_state.c \
   testing_api_cmd_contract_get.c \
   testing_api_cmd_deposit.c \
   testing_api_cmd_deposits_get.c \
@@ -70,6 +73,8 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_exec_transfer.c \
   testing_api_cmd_exec_wget.c \
   testing_api_cmd_exec_wirewatch.c \
+  testing_api_cmd_get_auditor.c \
+  testing_api_cmd_get_exchange.c \
   testing_api_cmd_insert_deposit.c \
   testing_api_cmd_kyc_check_get.c \
   testing_api_cmd_kyc_proof.c \
@@ -85,7 +90,6 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_purse_deposit.c \
   testing_api_cmd_purse_get.c \
   testing_api_cmd_purse_merge.c \
-  testing_api_cmd_set_wire_fee.c \
   testing_api_cmd_recoup.c \
   testing_api_cmd_recoup_refresh.c \
   testing_api_cmd_refund.c \
@@ -101,9 +105,10 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_revoke.c \
   testing_api_cmd_revoke_denom_key.c \
   testing_api_cmd_revoke_sign_key.c \
-  testing_api_cmd_rewind.c \
+  testing_api_cmd_run_fakebank.c \
   testing_api_cmd_serialize_keys.c \
   testing_api_cmd_set_officer.c \
+  testing_api_cmd_set_wire_fee.c \
   testing_api_cmd_signal.c \
   testing_api_cmd_sleep.c \
   testing_api_cmd_stat.c \
@@ -115,14 +120,15 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_wire_add.c \
   testing_api_cmd_wire_del.c \
   testing_api_cmd_withdraw.c \
-  testing_api_helpers_auditor.c \
-  testing_api_helpers_bank.c \
-  testing_api_helpers_exchange.c \
   testing_api_loop.c \
+  testing_api_misc.c \
   testing_api_traits.c
+
+
 libtalertesting_la_LIBADD = \
   $(top_builddir)/src/lib/libtalerauditor.la \
   $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/mhd/libtalermhd.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -141,15 +147,16 @@ libtalertesting_la_LIBADD = \
 
 AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
 
+# removed for now, due to bug(s) in libeufin!
+noinst_PROGRAMS = \
+  test_bank_api_with_nexus
 
 .NOTPARALLEL:
 check_PROGRAMS = \
   test_auditor_api_cs \
   test_auditor_api_rsa \
-  test_auditor_api_version_rsa \
-  test_auditor_api_version_cs \
+  test_auditor_api_version \
   test_bank_api_with_fakebank \
-  test_bank_api_with_nexus \
   test_exchange_api_cs \
   test_exchange_api_rsa \
   test_exchange_api_keys_cherry_picking_cs \
@@ -212,22 +219,9 @@ test_auditor_api_rsa_LDADD = \
   $(XLIB)
 
 
-test_auditor_api_version_cs_SOURCES = \
-  test_auditor_api_version.c
-test_auditor_api_version_cs_LDADD = \
-  libtalertesting.la \
-  $(top_builddir)/src/lib/libtalerauditor.la \
-  $(LIBGCRYPT_LIBS) \
-  $(top_builddir)/src/util/libtalerutil.la \
-  -lgnunettesting \
-  -lgnunetcurl \
-  -lgnunetutil \
-  -ljansson \
-  $(XLIB)
-
-test_auditor_api_version_rsa_SOURCES = \
+test_auditor_api_version_SOURCES = \
   test_auditor_api_version.c
-test_auditor_api_version_rsa_LDADD = \
+test_auditor_api_version_LDADD = \
   libtalertesting.la \
   $(top_builddir)/src/lib/libtalerauditor.la \
   $(LIBGCRYPT_LIBS) \
@@ -543,10 +537,14 @@ test_kyc_api_LDADD = \
 # Distribution
 
 EXTRA_DIST = \
+  $(bin_SCRIPTS) \
+  coins-cs.conf \
+  coins-rsa.conf \
   test_auditor_api-cs.conf \
   test_auditor_api-rsa.conf \
   test_auditor_api_expire_reserve_now-cs.conf \
   test_auditor_api_expire_reserve_now-rsa.conf \
+  test_bank_api.conf \
   test_bank_api_fakebank.conf \
   test_bank_api_fakebank_twisted.conf \
   test_bank_api_nexus.conf \
diff --git a/src/testing/test_exchange_api-cs.conf b/src/testing/coins-cs.conf
similarity index 97%
copy from src/testing/test_exchange_api-cs.conf
copy to src/testing/coins-cs.conf
index 1e1b3dec..92163baa 100644
--- a/src/testing/test_exchange_api-cs.conf
+++ b/src/testing/coins-cs.conf
@@ -1,6 +1,3 @@
-# This file is in the public domain.
-#
-@INLINE@ test_exchange_api.conf
 
 # Sections starting with "coin_" specify which denominations
 # the exchange should support (and their respective fee structure)
@@ -59,6 +56,7 @@ fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 CIPHER = CS
 
+
 [coin_eur_ct_1_age_restricted]
 value = EUR:0.01
 duration_withdraw = 7 days
diff --git a/src/testing/test_exchange_api-rsa.conf b/src/testing/coins-rsa.conf
similarity index 98%
copy from src/testing/test_exchange_api-rsa.conf
copy to src/testing/coins-rsa.conf
index 4248ecc5..7a21a343 100644
--- a/src/testing/test_exchange_api-rsa.conf
+++ b/src/testing/coins-rsa.conf
@@ -1,6 +1,4 @@
 # This file is in the public domain.
-#
-@INLINE@ test_exchange_api.conf
 
 # Sections starting with "coin_" specify which denominations
 # the exchange should support (and their respective fee structure)
@@ -127,4 +125,4 @@ fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
 age_restricted = YES
-CIPHER = RSA
+CIPHER = RSA
\ No newline at end of file
diff --git a/src/benchmark/taler-benchmark-setup.sh 
b/src/testing/taler-unified-setup.sh
similarity index 87%
rename from src/benchmark/taler-benchmark-setup.sh
rename to src/testing/taler-unified-setup.sh
index 70a7654b..a52aebff 100755
--- a/src/benchmark/taler-benchmark-setup.sh
+++ b/src/testing/taler-unified-setup.sh
@@ -63,13 +63,15 @@ START_NEXUS=0
 START_SANDBOX=0
 START_TRANSFER=0
 START_WIREWATCH=0
+USE_ACCOUNT="exchange-account-1"
 USE_VALGRIND=""
+WIRE_DOMAIN="x-taler-bank"
 CONF_ORIG="~/.config/taler.conf"
 LOGLEVEL="DEBUG"
 DEFAULT_SLEEP="0.2"
 
 # Parse command-line options
-while getopts ':abc:efghl:mnstvw' OPTION; do
+while getopts ':abc:d:efghl:mnr:stu:vw' OPTION; do
     case "$OPTION" in
         a)
             START_AUDITOR="1"
@@ -80,6 +82,9 @@ while getopts ':abc:efghl:mnstvw' OPTION; do
         c)
             CONF_ORIG="$OPTARG"
             ;;
+        c)
+            WIRE_DOMAIN="$OPTARG"
+            ;;
         e)
             START_EXCHANGE="1"
             ;;
@@ -91,14 +96,17 @@ while getopts ':abc:efghl:mnstvw' OPTION; do
             echo '  -a           -- start auditor'
             echo '  -b           -- start backup/sync'
             echo '  -c $CONF     -- set configuration'
+            echo '  -d $METHOD   -- use wire method (default: x-taler-bank)'
             echo '  -e           -- start exchange'
             echo '  -f           -- start fakebank'
             echo '  -h           -- print this help'
             echo '  -l $LOGLEVEL -- set log level'
             echo '  -m           -- start merchant'
             echo '  -n           -- start nexus'
+            echo '  -r $MEX      -- which exchange to use at the merchant 
(optional)'
             echo '  -s           -- start sandbox'
             echo '  -t           -- start transfer'
+            echo '  -u $SECTION  -- exchange account to use'
             echo '  -v           -- use valgrind'
             echo '  -w           -- start wirewatch'
             exit 0
@@ -115,12 +123,18 @@ while getopts ':abc:efghl:mnstvw' OPTION; do
         n)
             START_NEXUS="1"
             ;;
+        r)
+            USE_MERCHANT_EXCHANGE="$OPTARG"
+            ;;
         s)
             START_SANDBOX="1"
             ;;
         t)
             START_TRANSFER="1"
             ;;
+        u)
+            USE_ACCOUNT="$OPTARG"
+            ;;
         v)
             USE_VALGRIND="valgrind --leak-check=yes"
             DEFAULT_SLEEP="2"
@@ -170,6 +184,12 @@ then
     echo " FOUND"
 fi
 
+# FIXME-MS: when run twice using
+# taler-unified-setup.sh -c test_bank_api_nexus.conf -ns
+# libeufin fails with a 502 failure (sandbox happy, nexus dies) below.
+# Work-around is to delete the database every time. Very unclean. => needs a 
fix!
+rm -f *.sqlite3
+
 EXCHANGE_URL=$(taler-config -c "$CONF" -s "EXCHANGE" -o "BASE_URL")
 CURRENCY=$(taler-config -c "$CONF" -s "TALER" -o "CURRENCY")
 
@@ -249,7 +269,7 @@ then
     export LIBEUFIN_SANDBOX_USERNAME="exchange"
     export LIBEUFIN_SANDBOX_PASSWORD="x"
     EXCHANGE_PAYTO=$(libeufin-cli sandbox demobank info --bank-account 
exchange | jq --raw-output '.paytoUri')
-    taler-config -c "$CONF" -s exchange-account-1 -o "PAYTO_URI" -V 
"$EXCHANGE_PAYTO"
+    taler-config -c "$CONF" -s "$USE_ACCOUNT" -o "PAYTO_URI" -V 
"$EXCHANGE_PAYTO"
     echo " OK"
 
     echo -n "Setting this exchange as the bank's default ..."
@@ -319,6 +339,7 @@ then
     export LIBEUFIN_NEXUS_USERNAME=exchange
     export LIBEUFIN_NEXUS_PASSWORD=x
     echo -n "Creating a EBICS connection at Nexus ..."
+    # FIXME-MS: '||true' should be removed after we make 
'new-ebics-connection' idempotent!
     libeufin-cli connections new-ebics-connection \
       --ebics-url "http://localhost:$SANDBOX_PORT/ebicsweb"; \
       --host-id talerebics \
@@ -328,7 +349,7 @@ then
     echo "OK"
 
     echo -n "Setup EBICS keying ..."
-    libeufin-cli connections connect talerconn > /dev/null
+    libeufin-cli connections connect talerconn
     echo "OK"
     echo -n "Download bank account name from Sandbox ..."
     libeufin-cli connections download-bank-accounts talerconn
@@ -383,7 +404,11 @@ then
     MASTER_PRIV_FILE=$(taler-config -f -c "${CONF}" -s "EXCHANGE-OFFLINE" -o 
"MASTER_PRIV_FILE")
     MASTER_PRIV_DIR=$(dirname "$MASTER_PRIV_FILE")
     mkdir -p "${MASTER_PRIV_DIR}"
-    gnunet-ecc -g1 "$MASTER_PRIV_FILE" > /dev/null 2> /dev/null
+    if [ ! -e "$MASTER_PRIV_FILE" ]
+    then
+        gnunet-ecc -g1 "$MASTER_PRIV_FILE" > /dev/null 2> /dev/null
+        echo -n "."
+    fi
     MASTER_PUB=$(gnunet-ecc -p "${MASTER_PRIV_FILE}")
     MPUB=$(taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY)
     if [ "$MPUB" != "$MASTER_PUB" ]
@@ -391,7 +416,7 @@ then
         echo -n " patching master_pub ($MASTER_PUB)..."
         taler-config -c "$CONF" -s exchange -o MASTER_PUBLIC_KEY -V 
"$MASTER_PUB"
     fi
-    taler-exchange-dbinit -c "$CONF"
+    taler-exchange-dbinit -c "$CONF" --reset
     $USE_VALGRIND taler-exchange-secmod-eddsa -c "$CONF" -L "$LOGLEVEL" 2> 
taler-exchange-secmod-eddsa.log &
     $USE_VALGRIND taler-exchange-secmod-rsa -c "$CONF" -L "$LOGLEVEL" 2> 
taler-exchange-secmod-rsa.log &
     $USE_VALGRIND taler-exchange-secmod-cs -c "$CONF" -L "$LOGLEVEL" 2> 
taler-exchange-secmod-cs.log &
@@ -427,16 +452,19 @@ fi
 if [ "1" = "$START_MERCHANT" ]
 then
     echo -n "Starting merchant ..."
-    MEPUB=$(taler-config -c "$CONF" -s merchant-exchange-benchmark -o 
MASTER_KEY)
-    MXPUB=${MASTER_PUB:-$(taler-config -c "$CONF" -s exchange -o 
MASTER_PUBLIC_KEY)}
-    if [ "$MEPUB" != "$MXPUB" ]
+    if [ ! -z "${USE_MERCHANT_EXCHANGE+x}" ]
     then
-        echo -n " patching master_pub ($MXPUB)..."
-        taler-config -c "$CONF" -s merchant-exchange-benchmark -o MASTER_KEY 
-V "$MXPUB"
+        MEPUB=$(taler-config -c "$CONF" -s "${USE_MERCHANT_EXCHANGE}" -o 
MASTER_KEY)
+        MXPUB=${MASTER_PUB:-$(taler-config -c "$CONF" -s exchange -o 
MASTER_PUBLIC_KEY)}
+        if [ "$MEPUB" != "$MXPUB" ]
+        then
+            echo -n " patching master_pub ($MXPUB)..."
+            taler-config -c "$CONF" -s "${USE_MERCHANT_EXCHANGE}" -o 
MASTER_KEY -V "$MXPUB"
+        fi
     fi
     MERCHANT_PORT=$(taler-config -c "$CONF" -s MERCHANT -o PORT)
     MERCHANT_URL="http://localhost:${MERCHANT_PORT}/";
-    taler-merchant-dbinit -c "$CONF"
+    taler-merchant-dbinit -c "$CONF" -L "$LOGLEVEL" --reset &> 
taler-merchant-dbinit.log
     $USE_VALGRIND taler-merchant-httpd -c "$CONF" -L "$LOGLEVEL" 2> 
taler-merchant-httpd.log &
     MERCHANT_HTTPD_PID=$!
     $USE_VALGRIND taler-merchant-webhook -c "$CONF" -L "$LOGLEVEL" 2> 
taler-merchant-webhook.log &
@@ -449,7 +477,7 @@ then
     echo -n "Starting sync ..."
     SYNC_PORT=$(taler-config -c "$CONF" -s SYNC -o PORT)
     SYNC_URL="http://localhost:${SYNC_PORT}/";
-    sync-dbinit -c "$CONF"
+    sync-dbinit -c "$CONF" --reset
     $USE_VALGRIND sync-httpd -c "$CONF" -L "$LOGLEVEL" 2> sync-httpd.log &
     echo " DONE"
 fi
@@ -458,14 +486,18 @@ fi
 if [ "1" = "$START_AUDITOR" ]
 then
     echo -n "Starting auditor ..."
-    AUDITOR_URL="http://localhost:8083/";
+    AUDITOR_URL=$(taler-config -c "$CONF" -s AUDITOR -o BASE_URL)
     AUDITOR_PRIV_FILE=$(taler-config -f -c "$CONF" -s AUDITOR -o 
AUDITOR_PRIV_FILE)
     AUDITOR_PRIV_DIR=$(dirname "$AUDITOR_PRIV_FILE")
     mkdir -p "$AUDITOR_PRIV_DIR"
-    gnunet-ecc -g1 "$AUDITOR_PRIV_FILE" > /dev/null 2> /dev/null
+    if [ ! -e "$AUDITOR_PRIV_FILE" ]
+    then
+        gnunet-ecc -g1 "$AUDITOR_PRIV_FILE" > /dev/null 2> /dev/null
+        echo -n "."
+    fi
     AUDITOR_PUB=$(gnunet-ecc -p "${AUDITOR_PRIV_FILE}")
     MAPUB=${MASTER_PUB:-$(taler-config -c "$CONF" -s exchange -o 
MASTER_PUBLIC_KEY)}
-    taler-auditor-dbinit -c "$CONF"
+    taler-auditor-dbinit -c "$CONF" --reset
     taler-auditor-exchange -c "$CONF" -m "$MAPUB" -u "$EXCHANGE_URL"
     $USE_VALGRIND taler-auditor-httpd -L "$LOGLEVEL" -c "$CONF" 2> 
taler-auditor-httpd.log &
     echo " DONE"
@@ -503,20 +535,21 @@ echo -n "Waiting for Taler services ..."
 # Wait for all other taler services to be available
 for n in $(seq 1 20)
 do
-    echo -n "."
     sleep "$DEFAULT_SLEEP"
     OK="0"
     if [ "1" = "$START_EXCHANGE" ]
     then
+        echo -n "E"
         wget \
             --tries=1 \
             --timeout=1 \
-            "http://localhost:8081/config"; \
+            "${EXCHANGE_URL}config" \
             -o /dev/null \
             -O /dev/null >/dev/null || continue
     fi
     if [ "1" = "$START_MERCHANT" ]
     then
+        echo -n "M"
         wget \
             --tries=1 \
             --timeout=1 \
@@ -526,6 +559,7 @@ do
     fi
     if [ "1" = "$START_BACKUP" ]
     then
+        echo -n "S"
         wget \
             --tries=1 \
             --timeout=1 \
@@ -535,6 +569,7 @@ do
     fi
     if [ "1" = "$START_AUDITOR" ]
     then
+        echo -n "A"
         wget \
             --tries=1 \
             --timeout=1 \
@@ -584,23 +619,20 @@ then
     taler-exchange-offline -c "$CONF" \
       download \
       sign \
-      wire-fee now iban "$CURRENCY:0.01" "$CURRENCY:0.01" \
+      wire-fee now "$WIRE_DOMAIN" "$CURRENCY:0.01" "$CURRENCY:0.01" \
       global-fee now "$CURRENCY:0.01" "$CURRENCY:0.01" "$CURRENCY:0.01" 1h 
1year 5 \
       upload &> taler-exchange-offline.log
     echo "OK"
-    for ASEC in $(taler-config -c "$CONF" -S | grep -i "exchange-account-")
-    do
-        ENABLED=$(taler-config -c "$CONF" -s "$ASEC" -o "ENABLE_CREDIT")
-        if [ "YES" = "$ENABLED" ]
-        then
-            echo -n "Configuring bank account $ASEC ..."
-            EXCHANGE_PAYTO_URI=$(taler-config -c "$CONF" -s "$ASEC" -o 
"PAYTO_URI")
-            taler-exchange-offline -c "$CONF" \
-              enable-account "$EXCHANGE_PAYTO_URI" \
-              upload &> "taler-exchange-offline-account-$ASEC.log"
-            echo " OK"
-        fi
-    done
+    ENABLED=$(taler-config -c "$CONF" -s "$USE_ACCOUNT" -o "ENABLE_CREDIT")
+    if [ "YES" = "$ENABLED" ]
+    then
+        echo -n "Configuring bank account $USE_ACCOUNT ..."
+        EXCHANGE_PAYTO_URI=$(taler-config -c "$CONF" -s "$USE_ACCOUNT" -o 
"PAYTO_URI")
+        taler-exchange-offline -c "$CONF" \
+          enable-account "$EXCHANGE_PAYTO_URI" \
+          upload &> "taler-exchange-offline-account.log"
+        echo " OK"
+    fi
     if [ "1" = "$START_AUDITOR" ]
     then
         echo -n "Enabling auditor ..."
diff --git a/src/testing/test-taler-exchange-aggregator-postgres.conf 
b/src/testing/test-taler-exchange-aggregator-postgres.conf
index 7cb2fba5..e9f7f487 100644
--- a/src/testing/test-taler-exchange-aggregator-postgres.conf
+++ b/src/testing/test-taler-exchange-aggregator-postgres.conf
@@ -1,75 +1,43 @@
+# This file is in the public domain.
+
 [PATHS]
 # Persistent data storage for the testcase
 TALER_TEST_HOME = test_taler_exchange_httpd_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
 
 [taler-exchange-secmod-rsa]
 # Reduce from 1 year to speed up test
 LOOKAHEAD_SIGN = 24 days
 
 [taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
 LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
 DURATION = 14 days
 
 [taler]
-# Currency supported by the exchange (can only be one)
 CURRENCY = EUR
 CURRENCY_ROUND_UNIT = EUR:0.01
 
 [exchange]
 AML_THRESHOLD = EUR:1000000
-
-# 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 = "http://localhost:8081/";
 
 [auditor]
 BASE_URL = "http://auditor.example.com/";
+PORT = 8083
 
 [auditordb-postgres]
 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
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
 
 [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
-
 [exchange-account-1]
-
 # What is the account URL?
 PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
 ENABLE_DEBIT = YES
@@ -81,7 +49,6 @@ WIRE_GATEWAY_AUTH_METHOD = basic
 USERNAME = Exchange
 PASSWORD = x
 
-
 [bank]
 HTTP_PORT = 8082
 
diff --git a/src/testing/test-taler-exchange-wirewatch-postgres.conf 
b/src/testing/test-taler-exchange-wirewatch-postgres.conf
index 9c755c6e..a00aa97f 100644
--- a/src/testing/test-taler-exchange-wirewatch-postgres.conf
+++ b/src/testing/test-taler-exchange-wirewatch-postgres.conf
@@ -1,62 +1,41 @@
+# This file is in the public domain.
+
 [PATHS]
 # Persistent data storage for the testcase
 TALER_TEST_HOME = test_taler_exchange_httpd_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
 
 [taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
 LOOKAHEAD_SIGN = 24 days
 
 [taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
 LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
 DURATION = 14 days
 
 [taler]
-# Currency supported by the exchange (can only be one)
 CURRENCY = EUR
 CURRENCY_ROUND_UNIT = EUR:0.01
 
 [exchange]
 AML_THRESHOLD = EUR:1000000
-
-# 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 = "http://localhost:8081/";
 
 [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 = 4 s
 
 [exchangedb-postgres]
-#The connection string the plugin has to use for connecting to the database
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
 CONFIG = "postgres:///talercheck"
 
 [auditor]
 BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
 PORT = 8083
 
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
 [exchange-account-1]
 # What is the account URL?
 PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
diff --git a/src/testing/test_auditor_api-cs.conf 
b/src/testing/test_auditor_api-cs.conf
index f0095e38..b80696fb 100644
--- a/src/testing/test_auditor_api-cs.conf
+++ b/src/testing/test_auditor_api-cs.conf
@@ -1,141 +1,4 @@
-
 # This file is in the public domain.
 #
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-cs]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
-PORT = 8083
-PUBLIC_KEY = XNYZPJJ6YPSQ4C6QPW120ACG9B5E5GBTTSYWXDMDB6G4X74TDBPG
-TINY_AMOUNT = EUR:0.01
-
-[exchange]
-AML_THRESHOLD = EUR:1000000
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
-BASE_URL = "http://localhost:8081/";
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
-
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange.  The "URL" specifies the account in
-# payto://-format.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/42/";
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-# Authentication information for basic authentication
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/";
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-
-
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
+@INLINE@ coins-cs.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_auditor_api-rsa.conf 
b/src/testing/test_auditor_api-rsa.conf
index dddbf091..671e8110 100644
--- a/src/testing/test_auditor_api-rsa.conf
+++ b/src/testing/test_auditor_api-rsa.conf
@@ -1,147 +1,4 @@
-
 # This file is in the public domain.
 #
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
-PORT = 8083
-PUBLIC_KEY = XNYZPJJ6YPSQ4C6QPW120ACG9B5E5GBTTSYWXDMDB6G4X74TDBPG
-
-TINY_AMOUNT = EUR:0.01
-
-[exchange]
-AML_THRESHOLD = EUR:1000000
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
-BASE_URL = "http://localhost:8081/";
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
-
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange.  The "URL" specifies the account in
-# payto://-format.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/42/";
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-# Authentication information for basic authentication
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/";
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-
-
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c
index 10e54766..4e643f17 100644
--- a/src/testing/test_auditor_api.c
+++ b/src/testing/test_auditor_api.c
@@ -45,14 +45,9 @@ static char *config_file;
 static char *config_file_expire_reserve_now;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
 
 /**
  * Execute the taler-exchange-wirewatch command with
@@ -83,8 +78,8 @@ static struct TALER_TESTING_BankConfiguration bc;
  */
 #define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
   TALER_TESTING_cmd_admin_add_incoming (label, amount,           \
-                                        &bc.exchange_auth,       \
-                                        bc.user42_payto)
+                                        &cred.ba,       \
+                                        cred.user42_payto)
 
 /**
  * Run the taler-auditor.
@@ -116,7 +111,7 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer
       ("check-create-reserve-1",
-      "EUR:5.01", bc.user42_payto, bc.exchange_payto,
+      "EUR:5.01", cred.user42_payto, cred.exchange_payto,
       "create-reserve-1"),
     /**
      * Make a reserve exist, according to the previous transfer.
@@ -140,7 +135,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-simple",
                                "withdraw-coin-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -157,7 +152,7 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer
       ("check-refresh-create-reserve-1",
-      "EUR:5.01", bc.user42_payto, bc.exchange_payto,
+      "EUR:5.01", cred.user42_payto, cred.exchange_payto,
       "refresh-create-reserve-1"),
     /**
      * Make previous command effective.
@@ -178,7 +173,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
                                "refresh-withdraw-coin-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                
"{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:1\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -203,7 +198,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
                                "refresh-reveal-1",
                                3,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.1",
@@ -225,75 +220,75 @@ run (void *cls,
      */
     TALER_TESTING_cmd_check_bank_transfer (
       "check_bank_transfer-499c",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:4.98",
-      bc.exchange_payto,
-      bc.user42_payto),
+      cred.exchange_payto,
+      cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer (
       "check_bank_transfer-99c1",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto,
-      bc.user42_payto),
+      cred.exchange_payto,
+      cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer (
       "check_bank_transfer-99c",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.08",
-      bc.exchange_payto,
-      bc.user43_payto),
+      cred.exchange_payto,
+      cred.user43_payto),
 
     /* The following transactions got originated within
      * the "massive deposit confirms" batch.  */
     TALER_TESTING_cmd_check_bank_transfer (
       "check-massive-transfer-1",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-2",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-3",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-4",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-5",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-6",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-7",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-8",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-9",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer
       ("check-massive-transfer-10",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:0.98",
-      bc.exchange_payto, bc.user43_payto),
+      cred.exchange_payto, cred.user43_payto),
     TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
     TALER_TESTING_cmd_end ()
   };
@@ -311,8 +306,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check_bank_transfer-unaggregated",
       "EUR:5.01",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "create-reserve-unaggregated"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
                                        "create-reserve-unaggregated",
@@ -322,7 +317,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-unaggregated",
                                "withdraw-coin-unaggregated",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_relative_multiply
                                  (GNUNET_TIME_UNIT_YEARS,
@@ -359,7 +354,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-1",
                                "withdraw-coin-r1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                
"{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_MINUTES,
                                "EUR:5",
@@ -376,7 +371,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-2",
                                "withdraw-coin-r1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                
"{\"items\":[{\"name\":\"more\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:4.99",
@@ -466,7 +461,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
                                "recoup-withdraw-coin-2a",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.5",
@@ -493,7 +488,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check-massive-transfer",
       "EUR:10.10",
-      bc.user42_payto, bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "massive-reserve"),
     CMD_EXEC_WIREWATCH ("massive-wirewatch"),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1",
@@ -550,7 +546,7 @@ run (void *cls,
       "massive-deposit-1",
       "massive-withdraw-1",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -559,7 +555,7 @@ run (void *cls,
       ("massive-deposit-2",
       "massive-withdraw-2",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -568,7 +564,7 @@ run (void *cls,
       ("massive-deposit-3",
       "massive-withdraw-3",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -577,7 +573,7 @@ run (void *cls,
       ("massive-deposit-4",
       "massive-withdraw-4",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -586,7 +582,7 @@ run (void *cls,
       ("massive-deposit-5",
       "massive-withdraw-5",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -595,7 +591,7 @@ run (void *cls,
       ("massive-deposit-6",
       "massive-withdraw-6",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -604,7 +600,7 @@ run (void *cls,
       ("massive-deposit-7",
       "massive-withdraw-7",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -613,7 +609,7 @@ run (void *cls,
       ("massive-deposit-8",
       "massive-withdraw-8",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -622,7 +618,7 @@ run (void *cls,
       ("massive-deposit-9",
       "massive-withdraw-9",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -631,13 +627,12 @@ run (void *cls,
       "massive-deposit-10",
       "massive-withdraw-10",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
       MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit_confirmation ("deposit-confirmation",
-                                            is->auditor,
                                             "massive-deposit-10",
                                             0,
                                             "EUR:0.99",
@@ -648,21 +643,22 @@ run (void *cls,
   };
 
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                              config_file,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                2),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-u", "exchange-account-2",
+                                    "-ae",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_get_auditor ("get-auditor",
+                                   cred.cfg,
+                                   true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     TALER_TESTING_cmd_exec_auditor_offline ("auditor-offline",
                                             config_file),
     CMD_RUN_AUDITOR ("virgin-auditor"),
@@ -690,9 +686,8 @@ run (void *cls,
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     commands);
 }
 
 
@@ -700,60 +695,28 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_auditor_api-%s.conf",
-                   cipher);
-  GNUNET_asprintf (&config_file_expire_reserve_now,
-                   "test_auditor_api_expire_reserve_now-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  /* Check fakebank port is available and get configuration data. */
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 78;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_auditor_setup (&run,
-                                     NULL,
-                                     config_file))
-      return 2;
-    break;
-  default:
-    GNUNET_break (0);
-    return 3;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_auditor_api-%s.conf",
+                     cipher);
+    GNUNET_asprintf (&config_file_expire_reserve_now,
+                     "test_auditor_api_expire_reserve_now-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_auditor_api_version.c 
b/src/testing/test_auditor_api_version.c
index 1b60b15d..252369fa 100644
--- a/src/testing/test_auditor_api_version.c
+++ b/src/testing/test_auditor_api_version.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2014-2023 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
@@ -39,7 +39,7 @@
  * Configuration file we use.  One (big) configuration is used
  * for the various components for this test.
  */
-#define CONFIG_FILE "test_auditor_api.conf"
+#define CONFIG_FILE "test_auditor_api-rsa.conf"
 
 static struct TALER_AUDITOR_Handle *ah;
 
@@ -51,6 +51,7 @@ static int global_ret;
 
 static struct GNUNET_SCHEDULER_Task *tt;
 
+
 static void
 do_shutdown (void *cls)
 {
@@ -148,15 +149,16 @@ main (int argc,
                                   "taler-auditor-httpd",
                                   "taler-auditor-httpd",
                                   "-c", CONFIG_FILE,
+                                  "-L", "INFO",
                                   NULL);
   if (NULL == proc)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to run `taler-auditor-httpd`,"
-                " is your PATH correct?\n");
+                "Failed to run `taler-auditor-httpd`, is your PATH 
correct?\n");
     return 77;
   }
-  if (0 != TALER_TESTING_wait_auditor_ready ("http://localhost:8083/";))
+  global_ret = TALER_TESTING_wait_httpd_ready ("http://localhost:8083/";);
+  if (0 != global_ret)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Failed to launch `taler-auditor-httpd`\n");
@@ -166,7 +168,8 @@ main (int argc,
     GNUNET_SCHEDULER_run (&run,
                           NULL);
   }
-  GNUNET_OS_process_kill (proc, SIGTERM);
+  GNUNET_OS_process_kill (proc,
+                          SIGTERM);
   GNUNET_OS_process_wait (proc);
   GNUNET_OS_process_destroy (proc);
   return global_ret;
diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c
index ed2d0035..a2afdf06 100644
--- a/src/testing/test_bank_api.c
+++ b/src/testing/test_bank_api.c
@@ -34,6 +34,7 @@
 #include "taler_testing_lib.h"
 
 #define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank.conf"
+
 #define CONFIG_FILE_NEXUS "test_bank_api_nexus.conf"
 
 
@@ -41,28 +42,19 @@
  * Configuration file.  It changes based on
  * whether Nexus or Fakebank are used.
  */
-const char *cfgfile;
+static const char *cfgfile;
 
 /**
- * Bank configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
 
 /**
- * Flag indicating whether the test is running against the
- * Fakebank.  Set up at runtime.
+ * Which bank is the test running against?
+ * Set up at runtime.
  */
-static int with_fakebank;
+static enum TALER_TESTING_BankSystem bs;
 
-/**
- * Handles to the libeufin services.
- */
-static struct TALER_TESTING_LibeufinServices libeufin_services;
-
-/**
- * Needed to shutdown differently.
- */
-static int with_libeufin;
 
 /**
  * Main function that will tell the interpreter what commands to
@@ -75,20 +67,36 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_WireTransferIdentifierRawP wtid;
+  const char *ssoptions;
 
   (void) cls;
-  memset (&wtid, 42, sizeof (wtid));
+  switch (bs)
+  {
+  case TALER_TESTING_BS_FAKEBANK:
+    ssoptions = "-f";
+    break;
+  case TALER_TESTING_BS_IBAN:
+    ssoptions = "-ns";
+    break;
+  }
+  memset (&wtid,
+          42,
+          sizeof (wtid));
 
   {
     struct TALER_TESTING_Command commands[] = {
+      TALER_TESTING_cmd_system_start ("start-taler",
+                                      cfgfile,
+                                      ssoptions,
+                                      NULL),
       TALER_TESTING_cmd_bank_credits ("history-0",
-                                      &bc.exchange_auth,
+                                      &cred.ba,
                                       NULL,
                                       1),
       TALER_TESTING_cmd_admin_add_incoming ("credit-1",
-                                            "KUDOS:5.01",
-                                            &bc.exchange_auth,
-                                            bc.user42_payto),
+                                            "EUR:5.01",
+                                            &cred.ba,
+                                            cred.user42_payto),
       /**
        * This CMD doesn't care about the HTTP response code; that's
        * because Fakebank and euFin behaves differently when a reserve
@@ -96,9 +104,9 @@ run (void *cls,
        * with 200 but it bounces the payment back to the customer.
        */
       TALER_TESTING_cmd_admin_add_incoming_with_ref ("credit-1-fail",
-                                                     "KUDOS:2.01",
-                                                     &bc.exchange_auth,
-                                                     bc.user42_payto,
+                                                     "EUR:2.01",
+                                                     &cred.ba,
+                                                     cred.user42_payto,
                                                      "credit-1",
                                                      -1),
       TALER_TESTING_cmd_sleep ("Waiting 4s for 'credit-1' to settle",
@@ -108,28 +116,28 @@ run (void *cls,
        * reserve public key didn't make it to the exchange.
        */
       TALER_TESTING_cmd_bank_credits ("history-1c",
-                                      &bc.exchange_auth,
+                                      &cred.ba,
                                       NULL,
                                       5),
       TALER_TESTING_cmd_bank_debits ("history-1d",
-                                     &bc.exchange_auth,
+                                     &cred.ba,
                                      NULL,
                                      5),
       TALER_TESTING_cmd_admin_add_incoming ("credit-2",
-                                            "KUDOS:3.21",
-                                            &bc.exchange_auth,
-                                            bc.user42_payto),
+                                            "EUR:3.21",
+                                            &cred.ba,
+                                            cred.user42_payto),
       TALER_TESTING_cmd_transfer ("debit-1",
-                                  "KUDOS:3.22",
-                                  &bc.exchange_auth,
-                                  bc.exchange_payto,
-                                  bc.user42_payto,
+                                  "EUR:3.22",
+                                  &cred.ba,
+                                  cred.exchange_payto,
+                                  cred.user42_payto,
                                   &wtid,
                                   "http://exchange.example.com/";),
 
       TALER_TESTING_cmd_sleep ("Waiting 5s for 'debit-1' to settle",
                                5),
-      with_libeufin
+      (bs == TALER_TESTING_BS_IBAN)
       ? TALER_TESTING_cmd_nexus_fetch_transactions (
         "fetch-transactions-at-nexus",
         "exchange", /* from taler-nexus-prepare */
@@ -139,7 +147,7 @@ run (void *cls,
       : TALER_TESTING_cmd_sleep ("nop",
                                  0),
       TALER_TESTING_cmd_bank_debits ("history-2b",
-                                     &bc.exchange_auth,
+                                     &cred.ba,
                                      NULL,
                                      5),
       TALER_TESTING_cmd_end ()
@@ -147,116 +155,44 @@ run (void *cls,
 
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Bank serves at `%s'\n",
-                bc.exchange_auth.wire_gateway_url);
-    if (GNUNET_YES == with_fakebank)
-      TALER_TESTING_run_with_fakebank (is,
-                                       commands,
-                                       bc.exchange_auth.wire_gateway_url);
-    else
-      TALER_TESTING_run (is,
-                         commands);
+                cred.ba.wire_gateway_url);
+    TALER_TESTING_run (is,
+                       commands);
   }
 }
 
 
-/**
- * Runs #TALER_TESTING_setup() using the configuration.
- *
- * @param cls unused
- * @param cfg configuration to use
- * @return status code
- */
-static int
-setup_with_cfg (void *cls,
-                const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-  (void) cls;
-  return TALER_TESTING_setup (&run,
-                              NULL,
-                              cfg,
-                              NULL,
-                              GNUNET_NO);
-}
-
-
 int
 main (int argc,
       char *const *argv)
 {
-  int rv;
-
   (void) argc;
-  (void) argv;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup ("test-bank-api",
-                    "INFO",
-                    NULL);
-
-  with_fakebank = TALER_TESTING_has_in_name (argv[0],
-                                             "_with_fakebank");
-  if (GNUNET_YES == with_fakebank)
+  if (TALER_TESTING_has_in_name (argv[0],
+                                 "_with_fakebank"))
   {
-    TALER_LOG_DEBUG ("Running against the Fakebank.\n");
+    bs = TALER_TESTING_BS_FAKEBANK;
     cfgfile = CONFIG_FILE_FAKEBANK;
-    if (GNUNET_OK !=
-        TALER_TESTING_prepare_fakebank (CONFIG_FILE_FAKEBANK,
-                                        "exchange-account-2",
-                                        &bc))
-    {
-      GNUNET_break (0);
-      return 77;
-    }
   }
-  else if (GNUNET_YES == TALER_TESTING_has_in_name (argv[0],
-                                                    "_with_nexus"))
+  else if (TALER_TESTING_has_in_name (argv[0],
+                                      "_with_nexus"))
   {
-    TALER_LOG_DEBUG ("Running with Nexus.\n");
-    with_libeufin = GNUNET_YES;
+    bs = TALER_TESTING_BS_IBAN;
     cfgfile = CONFIG_FILE_NEXUS;
-    if (GNUNET_OK !=
-        TALER_TESTING_prepare_libeufin (CONFIG_FILE_NEXUS,
-                                        GNUNET_YES,
-                                        "exchange-account-2",
-                                        &bc))
-    {
-      GNUNET_break (0);
-      return 77;
-    }
-    libeufin_services = TALER_TESTING_run_libeufin (&bc);
-    if ( (NULL == libeufin_services.nexus) ||
-         (NULL == libeufin_services.sandbox) )
-      return 77;
   }
   else
   {
-    /* no bank service was ever invoked.  */
+    /* no bank service was specified.  */
+    GNUNET_break (0);
     return 77;
   }
-
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_parse_and_run (cfgfile,
-                                          &setup_with_cfg,
-                                          NULL))
-    rv = 1;
-  else
-    rv = 0;
-
-  if (with_libeufin)
-  {
-    GNUNET_OS_process_kill (libeufin_services.nexus,
-                            SIGKILL);
-    GNUNET_OS_process_wait (libeufin_services.nexus);
-    GNUNET_OS_process_destroy (libeufin_services.nexus);
-
-    GNUNET_OS_process_kill (libeufin_services.sandbox,
-                            SIGKILL);
-    GNUNET_OS_process_wait (libeufin_services.sandbox);
-    GNUNET_OS_process_destroy (libeufin_services.sandbox);
-  }
-
-  return rv;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             cfgfile,
+                             "exchange-account-2",
+                             bs,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_bank_api.conf b/src/testing/test_bank_api.conf
new file mode 100644
index 00000000..0f5a8abe
--- /dev/null
+++ b/src/testing/test_bank_api.conf
@@ -0,0 +1,13 @@
+# This file is in the public domain
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+currency = EUR
+
+[bank]
+# not (!) used by the nexus, only by the helper
+# check to make sure the port is free for Nexus.
+SERVE = http
+HTTP_PORT = 8082
diff --git a/src/testing/test_bank_api_fakebank.conf 
b/src/testing/test_bank_api_fakebank.conf
index 1e5a4d18..ad7671d1 100644
--- a/src/testing/test_bank_api_fakebank.conf
+++ b/src/testing/test_bank_api_fakebank.conf
@@ -1,21 +1,14 @@
 # This file is in the public domain.
+@INLINE@ test_bank_api.conf
 
-[taler]
-currency = KUDOS
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/1?receiver-name=1"
 
 [exchange-account-2]
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/2?receiver-name=2"
 
 [exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8081/2/";
+WIRE_GATEWAY_URL = "http://localhost:8082/2/";
 WIRE_GATEWAY_AUTH_METHOD = basic
 USERNAME = Exchange
 PASSWORD = x
-
-[bank]
-SERVE = http
-HTTP_PORT = 8081
-DATABASE = postgres:///talercheck
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
diff --git a/src/testing/test_bank_api_fakebank_twisted.conf 
b/src/testing/test_bank_api_fakebank_twisted.conf
index 6d316c14..bc66c0d8 100644
--- a/src/testing/test_bank_api_fakebank_twisted.conf
+++ b/src/testing/test_bank_api_fakebank_twisted.conf
@@ -1,12 +1,15 @@
+# This file is in the public domain.
+
+@INLINE@ test_bank_api_fakebank.conf
 
 [twister]
+
 # HTTP listen port for twister
 HTTP_PORT = 8888
 SERVE = tcp
-
 # HTTP Destination for twister.  The test-Webserver needs
 # to listen on the port used here.  Note: no trailing '/'!
-DESTINATION_BASE_URL = "http://localhost:8081";
+DESTINATION_BASE_URL = "http://localhost:8082";
 
 # Control port for TCP
 # PORT = 8889
@@ -18,20 +21,3 @@ ACCEPT_FROM6 = ::1;
 UNIXPATH = /tmp/taler-service-twister.sock
 UNIX_MATCH_UID = NO
 UNIX_MATCH_GID = YES
-
-[taler]
-currency = KUDOS
-
-[bank]
-serve = http
-http_port = 8081
-database = postgres:///talercheck
-
-[exchange-account-1]
-PAYTO_URI = "payto://x-taler-bank/localhost:8081/1?receiver-name=1"
-
-[exchange-account-2]
-PAYTO_URI = "payto://x-taler-bank/localhost:8081/2?receiver-name=2"
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
diff --git a/src/testing/test_bank_api_nexus.conf 
b/src/testing/test_bank_api_nexus.conf
index 016399d5..ecc19668 100644
--- a/src/testing/test_bank_api_nexus.conf
+++ b/src/testing/test_bank_api_nexus.conf
@@ -1,21 +1,19 @@
 # This file is in the public domain.
-
-[taler]
-currency = TESTKUDOS
+@INLINE@ test_bank_api.conf
 
 [exchange-account-2]
 PAYTO_URI = payto://iban/BIC/ES9121000418450200051332?receiver-name=Exchange
 
 [exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = http://localhost:5001/facades/my-facade/taler-wire-gateway/
+WIRE_GATEWAY_URL = 
http://localhost:8082/facades/test-facade/taler-wire-gateway/
 WIRE_GATEWAY_AUTH_METHOD = basic
 USERNAME = exchange
 PASSWORD = x
 
-[bank]
-# not (!) used by the nexus, only by the helper
-# check to make sure the port is free for Nexus.
-HTTP_PORT = 5001
+[libeufin-nexus]
+#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432"
+DB_CONNECTION="jdbc:sqlite:libeufin-nexus.sqlite3"
 
-[auditor]
-BASE_URL = "http://localhost:8083/";
+[libeufin-sandbox]
+#DB_CONNECTION="jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432"
+DB_CONNECTION="jdbc:sqlite:libeufin-sandbox.sqlite3"
diff --git a/src/testing/test_bank_api_twisted.c 
b/src/testing/test_bank_api_twisted.c
index 84379ad1..3ac63a5f 100644
--- a/src/testing/test_bank_api_twisted.c
+++ b/src/testing/test_bank_api_twisted.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2014-2023 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
@@ -17,7 +17,7 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file testing/test_bank_api_with_fakebank_twisted.c
+ * @file testing/test_bank_api_twisted.c
  * @author Marcello Stanisci
  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
  * @author Christian Grothoff
@@ -42,14 +42,20 @@
 #define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank_twisted.conf"
 
 /**
- * True when the test runs against Fakebank.
+ * Configuration file we use.
  */
-static int with_fakebank;
+static const char *cfgfile;
 
 /**
- * Bank configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Which bank is the test running against?
+ * Set up at runtime.
+ */
+static enum TALER_TESTING_BankSystem bs;
 
 /**
  * (real) Twister URL.  Used at startup time to check if it runs.
@@ -61,11 +67,6 @@ static char *twister_url;
  */
 static struct GNUNET_OS_Process *twisterd;
 
-/**
- * Python bank process handle.
- */
-static struct GNUNET_OS_Process *bankd;
-
 
 /**
  * Main function that will tell
@@ -78,156 +79,118 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_WireTransferIdentifierRawP wtid;
-  /* Route our commands through twister. */
+  /* Authentication data to route our commands through twister. */
   struct TALER_BANK_AuthenticationData exchange_auth_twisted;
+  const char *systype = NULL;
 
   (void) cls;
   memset (&wtid,
           0x5a,
           sizeof (wtid));
   GNUNET_memcpy (&exchange_auth_twisted,
-                 &bc.exchange_auth,
+                 &cred.ba,
                  sizeof (struct TALER_BANK_AuthenticationData));
-  if (with_fakebank)
-    exchange_auth_twisted.wire_gateway_url =
-      "http://localhost:8888/2/";;
-  else
-    exchange_auth_twisted.wire_gateway_url =
-      "http://localhost:8888/taler-wire-gateway/Exchange/";;
-
-  struct TALER_TESTING_Command commands[] = {
-    /* Test retrying transfer after failure. */
-    TALER_TESTING_cmd_malform_response ("malform-transfer",
-                                        CONFIG_FILE_FAKEBANK),
-    TALER_TESTING_cmd_transfer_retry (
-      TALER_TESTING_cmd_transfer ("debit-1",
-                                  "KUDOS:3.22",
-                                  &exchange_auth_twisted,
-                                  bc.exchange_payto,
-                                  bc.user42_payto,
-                                  &wtid,
-                                  "http://exchange.example.com/";)),
-    TALER_TESTING_cmd_end ()
-  };
-
-  if (GNUNET_YES == with_fakebank)
-    TALER_TESTING_run_with_fakebank (is,
-                                     commands,
-                                     bc.exchange_auth.wire_gateway_url);
-  else
+  switch (bs)
+  {
+  case TALER_TESTING_BS_FAKEBANK:
+    exchange_auth_twisted.wire_gateway_url
+      = "http://localhost:8888/2/";;
+    systype = "-f";
+    break;
+  case TALER_TESTING_BS_IBAN:
+    exchange_auth_twisted.wire_gateway_url
+      = "http://localhost:8888/taler-wire-gateway/Exchange/";;
+    systype = "-ns";
+    break;
+  }
+  GNUNET_assert (NULL != systype);
+
+  {
+    struct TALER_TESTING_Command commands[] = {
+      TALER_TESTING_cmd_system_start ("start-taler",
+                                      cfgfile,
+                                      systype,
+                                      NULL),
+      /* Test retrying transfer after failure. */
+      TALER_TESTING_cmd_malform_response ("malform-transfer",
+                                          cfgfile),
+      TALER_TESTING_cmd_transfer_retry (
+        TALER_TESTING_cmd_transfer ("debit-1",
+                                    "EUR:3.22",
+                                    &exchange_auth_twisted,
+                                    cred.exchange_payto,
+                                    cred.user42_payto,
+                                    &wtid,
+                                    "http://exchange.example.com/";)),
+      TALER_TESTING_cmd_end ()
+    };
+
     TALER_TESTING_run (is,
                        commands);
+  }
 }
 
 
 /**
  * Kill, wait, and destroy convenience function.
  *
- * @param process process to purge.
+ * @param[in] process process to purge.
  */
 static void
 purge_process (struct GNUNET_OS_Process *process)
 {
-  GNUNET_OS_process_kill (process, SIGINT);
+  GNUNET_OS_process_kill (process,
+                          SIGINT);
   GNUNET_OS_process_wait (process);
   GNUNET_OS_process_destroy (process);
 }
 
 
-/**
- * Runs #TALER_TESTING_setup() using the configuration.
- *
- * @param cls unused
- * @param cfg configuration to use
- * @return status code
- */
-static int
-setup_with_cfg (void *cls,
-                const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-  (void) cls;
-  return TALER_TESTING_setup (&run,
-                              NULL,
-                              cfg,
-                              NULL,
-                              GNUNET_NO);
-}
-
-
 int
 main (int argc,
       char *const *argv)
 {
   int ret;
-  const char *cfgfilename;
 
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup ("test-bank-api-with-(fake)bank-twisted",
-                    "INFO",
-                    NULL);
-
-  with_fakebank = TALER_TESTING_has_in_name (argv[0],
-                                             "_with_fakebank");
-
-  if (with_fakebank)
-    cfgfilename = CONFIG_FILE_FAKEBANK;
-  else
-    GNUNET_assert (0);
-  if (NULL == (twister_url = TALER_TWISTER_prepare_twister (
-                 cfgfilename)))
-  {
-    GNUNET_break (0);
-    return 77;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "twister_url is %s\n",
-              twister_url);
-  if (NULL == (twisterd = TALER_TWISTER_run_twister (cfgfilename)))
+  if (TALER_TESTING_has_in_name (argv[0],
+                                 "_with_fakebank"))
   {
-    GNUNET_break (0);
-    GNUNET_free (twister_url);
-    return 77;
+    bs = TALER_TESTING_BS_FAKEBANK;
+    cfgfile = CONFIG_FILE_FAKEBANK;
   }
-  if (GNUNET_YES == with_fakebank)
+  else if (TALER_TESTING_has_in_name (argv[0],
+                                      "_with_nexus"))
   {
-    TALER_LOG_DEBUG ("Running against the Fakebank.\n");
-    if (GNUNET_OK !=
-        TALER_TESTING_prepare_fakebank (cfgfilename,
-                                        "exchange-account-2",
-                                        &bc))
-    {
-      GNUNET_break (0);
-      GNUNET_free (twister_url);
-      return 77;
-    }
+    GNUNET_assert (0); /* FIXME: test with nexus not yet implemented */
+    bs = TALER_TESTING_BS_IBAN;
+    /* cfgfile = CONFIG_FILE_NEXUS; */
   }
   else
   {
-    GNUNET_assert (0);
+    /* no bank service was specified.  */
+    GNUNET_break (0);
+    return 77;
   }
 
-  sleep (5);
-  ret = GNUNET_CONFIGURATION_parse_and_run (cfgfilename,
-                                            &setup_with_cfg,
-                                            NULL);
+  /* FIXME: introduce commands for twister! */
+  twister_url = TALER_TWISTER_prepare_twister (cfgfile);
+  if (NULL == twister_url)
+    return 77;
+  twisterd = TALER_TWISTER_run_twister (cfgfile);
+  if (NULL == twisterd)
+    return 77;
+  ret = TALER_TESTING_main (argv,
+                            "INFO",
+                            cfgfile,
+                            "exchange-account-2",
+                            bs,
+                            &cred,
+                            &run,
+                            NULL);
   purge_process (twisterd);
-
-  if (GNUNET_NO == with_fakebank)
-  {
-    GNUNET_OS_process_kill (bankd,
-                            SIGKILL);
-    GNUNET_OS_process_wait (bankd);
-    GNUNET_OS_process_destroy (bankd);
-  }
-
   GNUNET_free (twister_url);
-  if (GNUNET_OK == ret)
-    return 0;
-
-  return 1;
+  return ret;
 }
 
 
diff --git a/src/testing/test_exchange_api-cs.conf 
b/src/testing/test_exchange_api-cs.conf
index 1e1b3dec..b80696fb 100644
--- a/src/testing/test_exchange_api-cs.conf
+++ b/src/testing/test_exchange_api-cs.conf
@@ -1,120 +1,4 @@
 # This file is in the public domain.
 #
+@INLINE@ coins-cs.conf
 @INLINE@ test_exchange_api.conf
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_ct_1_age_restricted]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-age_restricted = YES
-CIPHER = CS
-
-[coin_eur_ct_10_age_restricted]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-age_restricted = YES
-CIPHER = CS
-
-[coin_eur_1_age_restricted]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-age_restricted = YES
-CIPHER = CS
-
-[coin_eur_5_age_restricted]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-age_restricted = YES
-CIPHER = CS
-
-[coin_eur_10_age_restricted]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-age_restricted = YES
-CIPHER = CS
diff --git a/src/testing/test_exchange_api-rsa.conf 
b/src/testing/test_exchange_api-rsa.conf
index 4248ecc5..c7f48447 100644
--- a/src/testing/test_exchange_api-rsa.conf
+++ b/src/testing/test_exchange_api-rsa.conf
@@ -1,130 +1,4 @@
 # This file is in the public domain.
 #
 @INLINE@ test_exchange_api.conf
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_ct_1_age_restricted]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-age_restricted = YES
-CIPHER = RSA
-
-[coin_eur_ct_10_age_restricted]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-age_restricted = YES
-CIPHER = RSA
-
-[coin_eur_1_age_restricted]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-age_restricted = YES
-CIPHER = RSA
-
-[coin_eur_5_age_restricted]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-age_restricted = YES
-CIPHER = RSA
-
-[coin_eur_10_age_restricted]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-rsa_keysize = 1024
-age_restricted = YES
-CIPHER = RSA
+@INLINE@ coins-rsa.conf
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index ec9ccb74..eb131d65 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -49,14 +49,9 @@ static char *config_file;
 static char *config_file_expire_reserve_now;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
 
 /**
  * Some tests behave differently when using CS as we cannot
@@ -97,8 +92,8 @@ static bool uses_cs;
  */
 #define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
   TALER_TESTING_cmd_admin_add_incoming (label, amount, \
-                                        &bc.exchange_auth,                \
-                                        bc.user42_payto)
+                                        &cred.ba,                \
+                                        cred.user42_payto)
 
 /**
  * Main function that will tell the interpreter what commands to
@@ -142,8 +137,8 @@ run (void *cls,
                                     MHD_HTTP_OK),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
                                                  "EUR:6.02",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-1"),
     /**
      * Make a reserve exist, according to the previous
@@ -201,7 +196,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-simple",
                                "withdraw-coin-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -219,7 +214,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-reused-coin-key-failure",
                                "withdraw-coin-1x",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -232,7 +227,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-double-1",
                                "withdraw-coin-1",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -246,7 +241,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-double-1",
                                "withdraw-coin-1",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -257,7 +252,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-double-2",
                                "withdraw-coin-1",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":2}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -282,8 +277,8 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("ck-refresh-create-reserve-1",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "refresh-create-reserve-1"),
     /**
      * Make previous command effective.
@@ -304,7 +299,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
                                "refresh-withdraw-coin-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -341,7 +336,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1a",
                                "refresh-reveal-1-idempotency",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -352,7 +347,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
                                "refresh-reveal-1",
                                3,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.1",
@@ -390,8 +385,8 @@ run (void *cls,
                               "EUR:6.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
                                                  "EUR:6.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-age"),
     /**
      * Make a reserve exist, according to the previous
@@ -417,7 +412,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-simple-age",
                                "withdraw-coin-age-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:4.99",
@@ -464,53 +459,53 @@ run (void *cls,
      * Check all the transfers took place.
      */
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:4.98",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:4.97",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.98",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c2",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.98",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c3",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.98",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c4",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.98",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.08",
-                                           bc.exchange_payto,
-                                           bc.user43_payto),
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c2",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.08",
-                                           bc.exchange_payto,
-                                           bc.user43_payto),
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
     /* In case of CS, one transaction above succeeded that
        failed for RSA, hence we need to check for an extra transfer here */
     uses_cs
     ? TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-98c",
-                                             ec.exchange_url,
+                                             cred.exchange_url,
                                              "EUR:0.98",
-                                             bc.exchange_payto,
-                                             bc.user42_payto)
+                                             cred.exchange_payto,
+                                             cred.user42_payto)
     : TALER_TESTING_cmd_sleep ("dummy",
                                0),
     TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
@@ -544,8 +539,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check-create-reserve-unaggregated",
       "EUR:5.01",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "create-reserve-unaggregated"),
     CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
@@ -556,7 +551,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-unaggregated",
                                "withdraw-coin-unaggregated",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_relative_multiply (
                                  GNUNET_TIME_UNIT_YEARS,
@@ -578,8 +573,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "ck-refresh-create-reserve-age-1",
       "EUR:6.01",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "refresh-create-reserve-age-1"),
     /**
      * Make previous command effective.
@@ -600,7 +595,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-partial-age",
                                "refresh-withdraw-coin-age-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -637,7 +632,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1a",
                                "refresh-reveal-age-1-idempotency",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -648,7 +643,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1b",
                                "refresh-reveal-age-1",
                                3,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.1",
@@ -680,8 +675,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check-create-reserve-aggtest",
       "EUR:5.01",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "create-reserve-aggtest"),
     CMD_EXEC_WIREWATCH ("wirewatch-aggtest"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",
@@ -692,7 +687,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-aggtest-1",
                                "withdraw-coin-aggtest",
                                0,
-                               bc.user43_payto,
+                               cred.user43_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:2",
@@ -700,7 +695,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit_with_ref ("deposit-aggtest-2",
                                         "withdraw-coin-aggtest",
                                         0,
-                                        bc.user43_payto,
+                                        cred.user43_payto,
                                         "{\"items\":[{\"name\":\"foo 
bar\",\"value\":1}]}",
                                         GNUNET_TIME_UNIT_ZERO,
                                         "EUR:2",
@@ -708,10 +703,10 @@ run (void *cls,
                                         "deposit-aggtest-1"),
     CMD_EXEC_AGGREGATOR ("aggregation-aggtest"),
     TALER_TESTING_cmd_check_bank_transfer ("check-bank-transfer-aggtest",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:3.97",
-                                           bc.exchange_payto,
-                                           bc.user43_payto),
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
     TALER_TESTING_cmd_check_bank_empty ("check-bank-empty-aggtest"),
     TALER_TESTING_cmd_end ()
   };
@@ -725,8 +720,8 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-r1",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-r1"),
     /**
      * Run wire-watch to trigger the reserve creation.
@@ -745,7 +740,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-1",
                                "withdraw-coin-r1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_MINUTES,
                                "EUR:5",
@@ -779,7 +774,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-insufficient-refund",
                                "withdraw-coin-r1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:4\"}]}",
                                GNUNET_TIME_UNIT_MINUTES,
                                "EUR:4",
@@ -796,7 +791,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-2",
                                "withdraw-coin-r1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:4.99",
@@ -810,10 +805,10 @@ run (void *cls,
      * Check that deposit did run.
      */
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-pre-refund",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:4.97",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     /**
      * Run failing refund, as past deadline & aggregation.
      */
@@ -830,8 +825,8 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-rb",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-rb"),
     CMD_EXEC_WIREWATCH ("wirewatch-rb"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
@@ -842,7 +837,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-refund-1b",
                                "withdraw-coin-rb",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -876,8 +871,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "recoup-create-reserve-1-check",
       "EUR:15.02",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "recoup-create-reserve-1"),
     /**
      * Run wire-watch to trigger the reserve creation.
@@ -1006,8 +1001,8 @@ run (void *cls,
                               "EUR:5.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-short-lived-reserve",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "short-lived-reserve"),
     TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation",
                                       config_file_expire_reserve_now),
@@ -1029,10 +1024,10 @@ run (void *cls,
                                        0, /* age restriction off */
                                        MHD_HTTP_CONFLICT),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:5",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     /* Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
      * config, then withdraw two coin, partially spend one, and
      * then have the rest paid back.  Check deposit of other coin
@@ -1042,8 +1037,8 @@ run (void *cls,
                               "EUR:2.02"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("ck-recoup-create-reserve-2",
                                                  "EUR:2.02",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "recoup-create-reserve-2"),
     /* Make previous command effective. */
     CMD_EXEC_WIREWATCH ("wirewatch-5"),
@@ -1062,7 +1057,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
                                "recoup-withdraw-coin-2a",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.5",
@@ -1090,7 +1085,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("recoup-deposit-revoked",
                                "recoup-withdraw-coin-2b",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -1102,7 +1097,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("recoup-deposit-partial-after-recoup",
                                "recoup-withdraw-coin-2a",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"extra ice 
cream\",\"value\":1}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:0.5",
@@ -1113,8 +1108,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check-recoup-create-reserve-3",
       "EUR:1.01",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "recoup-create-reserve-3"),
     CMD_EXEC_WIREWATCH ("wirewatch-6"),
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",
@@ -1144,8 +1139,8 @@ run (void *cls,
                                     MHD_HTTP_OK),
     TALER_TESTING_cmd_check_bank_admin_transfer 
("check-create-batch-reserve-1",
                                                  "EUR:6.03",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-batch-reserve-1"),
     /*
      * Make a reserve exist, according to the previous
@@ -1184,7 +1179,7 @@ run (void *cls,
      * Spend the coins.
      */
     TALER_TESTING_cmd_batch_deposit ("batch-deposit-1",
-                                     bc.user42_payto,
+                                     cred.user42_payto,
                                      "{\"items\":[{\"name\":\"ice 
cream\",\"value\":5}]}",
                                      GNUNET_TIME_UNIT_ZERO,
                                      MHD_HTTP_OK,
@@ -1232,33 +1227,18 @@ run (void *cls,
 
   {
     struct TALER_TESTING_Command commands[] = {
-      /* setup exchange */
-      TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                     MHD_HTTP_NO_CONTENT,
-                                     false),
-      TALER_TESTING_cmd_exec_offline_sign_extensions 
("offline-sign-extensions",
-                                                      config_file),
-      TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                  
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                  MHD_HTTP_NO_CONTENT,
-                                  false),
-      TALER_TESTING_cmd_exec_offline_sign_global_fees (
-        "offline-sign-global-fees",
-        config_file,
-        "EUR:0.01",
-        "EUR:0.01",
-        "EUR:0.01",
-        GNUNET_TIME_UNIT_MINUTES,
-        GNUNET_TIME_UNIT_DAYS,
-        1),
-      TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                                config_file),
-      TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                                config_file,
-                                                "EUR:0.01",
-                                                "EUR:0.01"),
-      TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                  1),
+      TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                      cred.cfg,
+                                      "exchange-account-2"),
+      TALER_TESTING_cmd_system_start ("start-taler",
+                                      config_file,
+                                      "-e",
+                                      NULL),
+      TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                      cred.cfg,
+                                      true,
+                                      true),
+      TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
       TALER_TESTING_cmd_batch ("wire",
                                wire),
       TALER_TESTING_cmd_batch ("withdraw",
@@ -1291,9 +1271,8 @@ run (void *cls,
       TALER_TESTING_cmd_end ()
     };
 
-    TALER_TESTING_run_with_fakebank (is,
-                                     commands,
-                                     bc.exchange_auth.wire_gateway_url);
+    TALER_TESTING_run (is,
+                       commands);
   }
 }
 
@@ -1302,62 +1281,30 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  uses_cs = (0 == strcmp (cipher, "cs"));
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api-%s.conf",
-                   cipher);
-  GNUNET_asprintf (&config_file_expire_reserve_now,
-                   "test_exchange_api_expire_reserve_now-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-
-  /* Check fakebank port is available and get config */
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 78;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 2;
-    break;
-  default:
-    GNUNET_break (0);
-    return 3;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    uses_cs = (0 == strcmp (cipher,
+                            "cs"));
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api-%s.conf",
+                     cipher);
+    GNUNET_asprintf (&config_file_expire_reserve_now,
+                     "test_exchange_api_expire_reserve_now-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_exchange_api.conf 
b/src/testing/test_exchange_api.conf
index bf73d00a..9ed12412 100644
--- a/src/testing/test_exchange_api.conf
+++ b/src/testing/test_exchange_api.conf
@@ -2,99 +2,55 @@
 #
 
 [PATHS]
-# Persistent data storage for the testcase
 TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
 
 [taler]
-# Currency supported by the exchange (can only be one)
 CURRENCY = EUR
 CURRENCY_ROUND_UNIT = EUR:0.01
 
 [auditor]
 BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
 PORT = 8083
+PUBLIC_KEY = SA7JVMCW3MMN7SYAWJ9AB0BGJDX6MP3PNN2JWQ3T8233MDSQC7Z0
+TINY_AMOUNT = EUR:0.01
 
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
 
-[kyc-provider-test-oauth2]
-COST = 0
-LOGIC = oauth2
-USER_TYPE = INDIVIDUAL
-PROVIDED_CHECKS = DUMMY
-KYC_OAUTH2_VALIDITY = forever
-KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
-KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
-KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
-KYC_OAUTH2_CLIENT_ID = taler-exchange
-KYC_OAUTH2_CLIENT_SECRET = exchange-secret
-KYC_OAUTH2_POST_URL = http://example.com/
-KYC_OAUTH2_ATTRIBUTE_TEMPLATE = "{"full_name":"{{last_name}}, {{first_name}}"}"
-
-[kyc-legitimization-close]
-OPERATION_TYPE = CLOSE
-REQUIRED_CHECKS = DUMMY
-THRESHOLD = EUR:0
-TIMEFRAME = 1d
+[bank]
+HTTP_PORT = 8082
 
 [exchange]
-
 TERMS_ETAG = 0
 PRIVACY_ETAG = 0
-
 AML_THRESHOLD = EUR:1000000
-
-# HTTP port the exchange listens to
 PORT = 8081
-
-# Master public key used to sign the exchange's various keys
 MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
 DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
 BASE_URL = "http://localhost:8081/";
-
-# How big is an individual shard to be processed
-# by taler-exchange-expire (in time).  It may take
-# this much time for an expired purse to be really
-# cleaned up and the coins refunded.
 EXPIRE_SHARD_SIZE = 300 ms
-
 EXPIRE_IDLE_SLEEP_INTERVAL = 1 s
 
 [exchangedb-postgres]
 CONFIG = "postgres:///talercheck"
 
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = 24 days
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = 24 days
+DURATION = 14 days
+
 
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange.  The "URL" specifies the account in
-# payto://-format.
 [exchange-account-1]
-# What is the URL of our account?
 PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
-# ENABLE_CREDIT = YES
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
 
 [exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:9081/42/";
+WIRE_GATEWAY_URL = "http://localhost:8082/42/";
 
 [exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
 PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
 ENABLE_DEBIT = YES
 ENABLE_CREDIT = YES
@@ -103,13 +59,29 @@ ENABLE_CREDIT = YES
 WIRE_GATEWAY_AUTH_METHOD = basic
 USERNAME = Exchange
 PASSWORD = x
-WIRE_GATEWAY_URL = "http://localhost:9081/2/";
+WIRE_GATEWAY_URL = "http://localhost:8082/2/";
 
-[bank]
-HTTP_PORT = 9081
 
-# Enabled extensions
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
+KYC_OAUTH2_CLIENT_ID = taler-exchange
+KYC_OAUTH2_CLIENT_SECRET = exchange-secret
+KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_ATTRIBUTE_TEMPLATE = "{"full_name":"{{last_name}}, {{first_name}}"}"
+
+[kyc-legitimization-close]
+OPERATION_TYPE = CLOSE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
+
 [exchange-extension-age_restriction]
 ENABLED = YES
-# default age groups:
 #AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git 
a/src/testing/test_exchange_api_home/taler/exchange-offline/master.priv 
b/src/testing/test_exchange_api_home/taler/exchange-offline/master.priv
new file mode 100644
index 00000000..39492693
--- /dev/null
+++ b/src/testing/test_exchange_api_home/taler/exchange-offline/master.priv
@@ -0,0 +1 @@
+p�^�-�33��XX�!�\0q�����mU�_��
\ No newline at end of file
diff --git a/src/testing/test_exchange_api_keys_cherry_picking.c 
b/src/testing/test_exchange_api_keys_cherry_picking.c
index 4d61ed2e..b463eea8 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.c
+++ b/src/testing/test_exchange_api_keys_cherry_picking.c
@@ -43,9 +43,9 @@ lished
 static char *config_file;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
 
 
 /**
@@ -59,27 +59,19 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                              config_file,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("initial-/keys",
-                                                1),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("initial-/keys"),
     TALER_TESTING_cmd_sleep ("sleep",
                              6 /* seconds */),
-    TALER_TESTING_cmd_check_keys ("check-keys-1",
-                                  2 /* generation */),
+    TALER_TESTING_cmd_check_keys ("check-keys-1"),
     TALER_TESTING_cmd_check_keys_with_last_denom ("check-keys-2",
-                                                  3 /* generation */,
                                                   "check-keys-1"),
     TALER_TESTING_cmd_serialize_keys ("serialize-keys"),
     TALER_TESTING_cmd_connect_with_state ("reconnect-with-state",
@@ -88,8 +80,7 @@ run (void *cls,
      * Make sure we have the same keys situation as
      * it was before the serialization.
      */
-    TALER_TESTING_cmd_check_keys ("check-keys-after-deserialization",
-                                  4),
+    TALER_TESTING_cmd_check_keys ("check-keys-after-deserialization"),
     /**
      * Use one of the deserialized keys.
      */
@@ -110,50 +101,25 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api_keys_cherry_picking-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api_keys_cherry_picking-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_exchange_api_keys_cherry_picking.conf 
b/src/testing/test_exchange_api_keys_cherry_picking.conf
index 5637bb66..fafb747d 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.conf
+++ b/src/testing/test_exchange_api_keys_cherry_picking.conf
@@ -3,48 +3,21 @@
 [PATHS]
 # Persistent data storage for the testcase
 TALER_TEST_HOME = test_exchange_api_keys_cherry_picking_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-# Persistent data storage
-TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
-
-# Configuration files
-TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
-
-# Cached data, no big deal if lost
-TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
 
 [taler]
-# Currency supported by the exchange (can only be one)
 CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
 
 [taler-exchange-secmod-eddsa]
 OVERLAP_DURATION = 1 s
 DURATION = 30 s
 LOOKAHEAD_SIGN = 20 s
 
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
-PORT = 8083
-
 [exchange]
-
 AML_THRESHOLD = EUR:1000000
-
-# HTTP port the exchange listens to
 PORT = 8081
-
-# Master public key used to sign the exchange's various keys
 MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
 DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
 BASE_URL = "http://localhost:8081/";
 
 [exchangedb-postgres]
@@ -55,6 +28,8 @@ CONFIG = "postgres:///talercheck"
 
 [exchange-account-1]
 PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
 
 [exchange-accountcredentials-1]
 WIRE_GATEWAY_URL = "http://localhost:9082/42/";
@@ -66,11 +41,9 @@ ENABLE_CREDIT = YES
 
 [exchange-accountcredentials-2]
 WIRE_GATEWAY_URL = "http://localhost:9082/2/";
-
-# Authentication information for basic authentication
-TALER_BANK_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
 
 [bank]
 HTTP_PORT=8082
diff --git a/src/testing/test_exchange_api_overlapping_keys_bug.c 
b/src/testing/test_exchange_api_overlapping_keys_bug.c
index e620f280..7cbdd9b8 100644
--- a/src/testing/test_exchange_api_overlapping_keys_bug.c
+++ b/src/testing/test_exchange_api_overlapping_keys_bug.c
@@ -44,9 +44,9 @@
 static char *config_file;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
 
 
 /**
@@ -60,22 +60,21 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                1),
-    TALER_TESTING_cmd_check_keys ("first-download",
-                                  1),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
+    TALER_TESTING_cmd_check_keys ("first-download"),
     /* Causes GET /keys?last_denom_issue=0 */
     TALER_TESTING_cmd_check_keys_with_last_denom ("second-download",
-                                                  1,
                                                   "zero"),
     TALER_TESTING_cmd_end ()
   };
@@ -90,50 +89,25 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api_keys_cherry_picking-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api_keys_cherry_picking-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_exchange_api_revocation.c 
b/src/testing/test_exchange_api_revocation.c
index 33a92551..c1c1b319 100644
--- a/src/testing/test_exchange_api_revocation.c
+++ b/src/testing/test_exchange_api_revocation.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014--2020 Taler Systems SA
+  Copyright (C) 2014--2023 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
@@ -42,14 +42,9 @@
 static char *config_file;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
 
 
 /**
@@ -63,6 +58,18 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command revocation[] = {
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+#if 0
     TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
                                    MHD_HTTP_NO_CONTENT,
                                    false),
@@ -72,20 +79,20 @@ run (void *cls,
                                 false),
     TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
                                               config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                1),
+#endif
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     /**
      * Fill reserve with EUR:10.02, as withdraw fee is 1 ct per
      * config.
      */
     TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
                                           "EUR:10.02",
-                                          &bc.exchange_auth,
-                                          bc.user42_payto),
+                                          &cred.ba,
+                                          cred.user42_payto),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
                                                  "EUR:10.02",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-1"),
     /**
      * Run wire-watch to trigger the reserve creation.
@@ -109,7 +116,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-partial",
                                "withdraw-revocation-coin-1",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:1",
@@ -118,7 +125,7 @@ run (void *cls,
     TALER_TESTING_cmd_deposit ("deposit-full",
                                "withdraw-revocation-coin-2",
                                0,
-                               bc.user42_payto,
+                               cred.user42_payto,
                                "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:5\"}]}",
                                GNUNET_TIME_UNIT_ZERO,
                                "EUR:5",
@@ -241,9 +248,8 @@ run (void *cls,
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   revocation,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     revocation);
 }
 
 
@@ -251,56 +257,25 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  /* Check fakebank port is available and get config */
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_exchange_api_twisted.c 
b/src/testing/test_exchange_api_twisted.c
index 388c064a..3a7455eb 100644
--- a/src/testing/test_exchange_api_twisted.c
+++ b/src/testing/test_exchange_api_twisted.c
@@ -44,19 +44,14 @@
 static char *config_file;
 
 /**
- * (real) Twister URL.  Used at startup time to check if it runs.
+ * Our credentials.
  */
-static char *twister_url;
+static struct TALER_TESTING_Credentials cred;
 
 /**
- * Exchange configuration data.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
+ * (real) Twister URL.  Used at startup time to check if it runs.
  */
-static struct TALER_TESTING_BankConfiguration bc;
+static char *twister_url;
 
 /**
  * Twister process.
@@ -92,8 +87,8 @@ CMD_TRANSFER_TO_EXCHANGE (const char *label,
 {
   return TALER_TESTING_cmd_admin_add_incoming (label,
                                                amount,
-                                               &bc.exchange_auth,
-                                               bc.user42_payto);
+                                               &cred.ba,
+                                               cred.user42_payto);
 }
 
 
@@ -132,7 +127,7 @@ run (void *cls,
       "refresh-deposit-partial",
       "refresh-withdraw-coin",
       0,
-      bc.user42_payto,
+      cred.user42_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:1",
@@ -178,7 +173,7 @@ run (void *cls,
       "deposit-refund-1",
       "withdraw-coin-r1",
       0,
-      bc.user42_payto,
+      cred.user42_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}",
       GNUNET_TIME_UNIT_MINUTES,
       "EUR:5",
@@ -201,7 +196,7 @@ run (void *cls,
       "deposit-refund-to-fail",
       "withdraw-coin-r1",
       0,                          /* coin index.  */
-      bc.user42_payto,
+      cred.user42_payto,
       /* This parameter will make any comparison about
          h_contract_terms fail, when /refund will be handled.
          So in other words, this is h_contract mismatch.  */
@@ -251,17 +246,17 @@ run (void *cls,
 #endif
 
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_wire_add (
-      "add-wire-account",
-      "payto://x-taler-bank/localhost/2?receiver-name=2",
-      MHD_HTTP_NO_CONTENT,
-      false),
-    TALER_TESTING_cmd_exec_offline_sign_keys (
-      "offline-sign-future-keys",
-      config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys (
-      "refetch /keys",
-      1),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
     TALER_TESTING_cmd_batch (
       "refresh-reveal-409-conflict",
       refresh_409_conflict),
@@ -276,16 +271,15 @@ run (void *cls,
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     commands);
 }
 
 
 /**
  * Kill, wait, and destroy convenience function.
  *
- * @param process process to purge.
+ * @param[in] process process to purge.
  */
 static void
 purge_process (struct GNUNET_OS_Process *process)
@@ -301,57 +295,37 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
   int ret;
 
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api_twisted-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  if (NULL == (twister_url = TALER_TWISTER_prepare_twister
-                               (config_file)))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (NULL == (twisterd = TALER_TWISTER_run_twister (config_file)))
-      return 77;
-    ret = TALER_TESTING_setup_with_exchange (&run,
-                                             NULL,
-                                             config_file);
-    purge_process (twisterd);
-    GNUNET_free (twister_url);
+    char *cipher;
 
-    if (GNUNET_OK != ret)
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api_twisted-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  /* FIXME: introduce commands for twister! */
+  twister_url = TALER_TWISTER_prepare_twister (config_file);
+  if (NULL == twister_url)
+    return 77;
+  twisterd = TALER_TWISTER_run_twister (config_file);
+  if (NULL == twisterd)
+    return 77;
+  ret = TALER_TESTING_main (argv,
+                            "INFO",
+                            config_file,
+                            "exchange-account-2",
+                            TALER_TESTING_BS_FAKEBANK,
+                            &cred,
+                            &run,
+                            NULL);
+  purge_process (twisterd);
+  GNUNET_free (twister_url);
+  return ret;
 }
 
 
diff --git a/src/testing/test_exchange_management_api.c 
b/src/testing/test_exchange_management_api.c
index 3195b5a1..cabddcde 100644
--- a/src/testing/test_exchange_management_api.c
+++ b/src/testing/test_exchange_management_api.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2020-2022 Taler Systems SA
+  Copyright (C) 2020-2023 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
@@ -35,16 +35,10 @@
  */
 static char *config_file;
 
-
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_Credentials cred;
 
 
 /**
@@ -58,10 +52,20 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command commands[] = {
-    /* this currently fails, because the
-       auditor is already added by the test setup logic */
-    TALER_TESTING_cmd_auditor_del ("del-auditor-NOT-FOUND",
-                                   MHD_HTTP_NOT_FOUND,
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-u", "exchange-account-2",
+                                    "-ae",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_get_auditor ("get-auditor",
+                                   cred.cfg,
+                                   true),
+    TALER_TESTING_cmd_auditor_del ("del-auditor-FROM-SETUP",
+                                   MHD_HTTP_NO_CONTENT,
                                    false),
     TALER_TESTING_cmd_auditor_add ("add-auditor-BAD-SIG",
                                    MHD_HTTP_FORBIDDEN,
@@ -141,15 +145,13 @@ run (void *cls,
                                 false),
     TALER_TESTING_cmd_exec_offline_sign_keys ("download-future-keys",
                                               config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                1),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     TALER_TESTING_cmd_end ()
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     commands);
 }
 
 
@@ -157,56 +159,25 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-  /* Check fakebank port is available and get config */
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES, /* reset DB? */
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c
index 6b58dd7e..f5b11f60 100644
--- a/src/testing/test_exchange_p2p.c
+++ b/src/testing/test_exchange_p2p.c
@@ -42,14 +42,9 @@
 static char *config_file;
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
+struct TALER_TESTING_Credentials cred;
 
 /**
  * Some tests behave differently when using CS as we cannot
@@ -90,8 +85,8 @@ static bool uses_cs;
  */
 #define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
   TALER_TESTING_cmd_admin_add_incoming (label, amount, \
-                                        &bc.exchange_auth,                \
-                                        bc.user42_payto)
+                                        &cred.ba,                \
+                                        cred.user42_payto)
 
 /**
  * Main function that will tell the interpreter what commands to
@@ -122,13 +117,13 @@ run (void *cls,
                                     MHD_HTTP_OK),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
                                                  "EUR:5.04",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-1"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-2",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-2"),
     /**
      * Make a reserve exist, according to the previous
@@ -380,15 +375,15 @@ run (void *cls,
                               "EUR:1.04"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-100",
                                                  "EUR:1.04",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-100"),
     CMD_TRANSFER_TO_EXCHANGE ("create-reserve-101",
                               "EUR:1.04"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-101",
                                                  "EUR:1.04",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "create-reserve-101"),
     CMD_EXEC_WIREWATCH ("wirewatch-100"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-100",
@@ -500,32 +495,18 @@ run (void *cls,
   };
 
   struct TALER_TESTING_Command commands[] = {
-    /* setup exchange */
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions",
-                                                    config_file),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-wire-fees",
-                                              config_file,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    TALER_TESTING_cmd_exec_offline_sign_global_fees 
("offline-sign-global-fees",
-                                                     config_file,
-                                                     "EUR:0.01",
-                                                     "EUR:0.01",
-                                                     "EUR:0.01",
-                                                     GNUNET_TIME_UNIT_MINUTES,
-                                                     GNUNET_TIME_UNIT_DAYS,
-                                                     1),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              config_file),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                1),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_file,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     TALER_TESTING_cmd_batch ("withdraw",
                              withdraw),
     TALER_TESTING_cmd_batch ("push",
@@ -541,9 +522,8 @@ run (void *cls,
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     commands);
 }
 
 
@@ -551,59 +531,26 @@ int
 main (int argc,
       char *const *argv)
 {
-  char *cipher;
-
   (void) argc;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup (argv[0],
-                    "INFO",
-                    NULL);
-
-  cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
-  GNUNET_assert (NULL != cipher);
-  uses_cs = (0 == strcmp (cipher, "cs"));
-  GNUNET_asprintf (&config_file,
-                   "test_exchange_api-%s.conf",
-                   cipher);
-  GNUNET_free (cipher);
-
-  /* Check fakebank port is available and get config */
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_file,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (config_file);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (config_file,
-                                          GNUNET_YES,
-                                          &ec))
   {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 78;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           config_file))
-      return 2;
-    break;
-  default:
-    GNUNET_break (0);
-    return 3;
+    char *cipher;
+
+    cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    uses_cs = (0 == strcmp (cipher, "cs"));
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
   }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index 12f69569..eb66d9c8 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -41,14 +41,10 @@
 #define CONFIG_FILE "test_kyc_api.conf"
 
 /**
- * Exchange configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+struct TALER_TESTING_Credentials cred;
 
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
 
 /**
  * Execute the taler-exchange-wirewatch command with
@@ -79,8 +75,8 @@ static struct TALER_TESTING_BankConfiguration bc;
  */
 #define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
   TALER_TESTING_cmd_admin_add_incoming (label, amount,           \
-                                        &bc.exchange_auth,       \
-                                        bc.user42_payto)
+                                        &cred.ba,       \
+                                        cred.user42_payto)
 
 /**
  * Main function that will tell the interpreter what commands to
@@ -98,8 +94,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "check-create-reserve-1",
       "EUR:15.02",
-      bc.user42_payto,
-      bc.exchange_payto,
+      cred.user42_payto,
+      cred.exchange_payto,
       "create-reserve-1"),
     CMD_EXEC_WIREWATCH ("wirewatch-1"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc",
@@ -150,7 +146,7 @@ run (void *cls,
       "deposit-simple",
       "withdraw-coin-1",
       0,
-      bc.user43_payto,
+      cred.user43_payto,
       "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
       GNUNET_TIME_UNIT_ZERO,
       "EUR:5",
@@ -196,10 +192,10 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"),
     TALER_TESTING_cmd_check_bank_transfer (
       "check_bank_transfer-499c",
-      ec.exchange_url,
+      cred.exchange_url,
       "EUR:4.98",
-      bc.exchange_payto,
-      bc.user43_payto),
+      cred.exchange_payto,
+      cred.user43_payto),
     TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
     TALER_TESTING_cmd_end ()
   };
@@ -255,13 +251,13 @@ run (void *cls,
                                     MHD_HTTP_OK),
     TALER_TESTING_cmd_check_bank_admin_transfer ("p2p_check-create-reserve-1",
                                                  "EUR:5.04",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "p2p_create-reserve-1"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("p2p_check-create-reserve-2",
                                                  "EUR:5.01",
-                                                 bc.user42_payto,
-                                                 bc.exchange_payto,
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
                                                  "p2p_create-reserve-2"),
     /**
      * Make a reserve exist, according to the previous
@@ -518,29 +514,18 @@ run (void *cls,
   };
 
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                              CONFIG_FILE,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    TALER_TESTING_cmd_exec_offline_sign_global_fees 
("offline-sign-global-fees",
-                                                     CONFIG_FILE,
-                                                     "EUR:0.01",
-                                                     "EUR:0.01",
-                                                     "EUR:0.01",
-                                                     GNUNET_TIME_UNIT_MINUTES,
-                                                     GNUNET_TIME_UNIT_DAYS,
-                                                     1),
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              CONFIG_FILE),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                2),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-2"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    CONFIG_FILE,
+                                    "-e",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     TALER_TESTING_cmd_batch ("withdraw",
                              withdraw),
     TALER_TESTING_cmd_batch ("spend",
@@ -563,9 +548,8 @@ run (void *cls,
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     commands);
 }
 
 
@@ -574,48 +558,14 @@ main (int argc,
       char *const *argv)
 {
   (void) argc;
-  (void) argv;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup ("test-kyc-api",
-                    "INFO",
-                    NULL);
-  /* Check fakebank port is available and get configuration data. */
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (CONFIG_FILE,
-                                      "exchange-account-2",
-                                      &bc))
-    return 77;
-  TALER_TESTING_cleanup_files (CONFIG_FILE);
-  /* @helpers.  Run keyup, create tables, ... Note: it
-   * fetches the port number from config in order to see
-   * if it's available. */
-  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
-                                          GNUNET_YES,
-                                          &ec))
-  {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-  case GNUNET_OK:
-    if (GNUNET_OK !=
-        /* Set up event loop and reschedule context, plus
-         * start/stop the exchange.  It calls TALER_TESTING_setup
-         * which creates the 'is' object.
-         */
-        TALER_TESTING_setup_with_exchange (&run,
-                                           NULL,
-                                           CONFIG_FILE))
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
-  }
-  return 0;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             CONFIG_FILE,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_kyc_api.conf b/src/testing/test_kyc_api.conf
index abc7f3e4..90343990 100644
--- a/src/testing/test_kyc_api.conf
+++ b/src/testing/test_kyc_api.conf
@@ -1,51 +1,7 @@
-
 # This file is in the public domain.
 #
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[auditor]
-BASE_URL = "http://localhost:8083/";
-
-# HTTP port the auditor listens to
-PORT = 8083
-
-TINY_AMOUNT = EUR:0.01
-
-[exchange]
-AML_THRESHOLD = EUR:1000000
-
-# HTTP port the exchange listens to
-PORT = 8081
-
-# Master public key used to sign the exchange's various keys
-MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
-
-# How to access our database
-DB = postgres
-
-# Base URL of the exchange. Must be set to a URL where the
-# exchange (or the twister) is actually listening.
-BASE_URL = "http://localhost:8081/";
-
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf
 
 [kyc-provider-test-oauth2]
 COST = 0
@@ -83,157 +39,3 @@ OPERATION_TYPE = MERGE
 REQUIRED_CHECKS = DUMMY
 THRESHOLD = EUR:0
 TIMEFRAME = 1d
-
-[exchangedb-postgres]
-CONFIG = "postgres:///talercheck"
-
-[auditordb-postgres]
-CONFIG = "postgres:///talercheck"
-
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange.  The "URL" specifies the account in
-# payto://-format.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/42/";
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-# Authentication information for basic authentication
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/";
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-
-
-
-# Sections starting with "coin_" specify which denominations
-# the exchange should support (and their respective fee structure)
-[coin_eur_ct_1]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_ct_2]
-value = EUR:0.01
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.00
-fee_deposit = EUR:0.00
-fee_refresh = EUR:0.01
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_ct_10]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_ct_11]
-value = EUR:0.10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_1]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_2]
-value = EUR:1
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_5]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_6]
-value = EUR:5
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
-
-[coin_eur_10]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = RSA
-rsa_keysize = 1024
-
-[coin_eur_11]
-value = EUR:10
-duration_withdraw = 7 days
-duration_spend = 2 years
-duration_legal = 3 years
-fee_withdraw = EUR:0.01
-fee_deposit = EUR:0.01
-fee_refresh = EUR:0.03
-fee_refund = EUR:0.01
-CIPHER = CS
\ No newline at end of file
diff --git a/src/testing/test_taler_exchange_aggregator.c 
b/src/testing/test_taler_exchange_aggregator.c
index bee7e37e..2d7acc6d 100644
--- a/src/testing/test_taler_exchange_aggregator.c
+++ b/src/testing/test_taler_exchange_aggregator.c
@@ -31,24 +31,9 @@
 
 
 /**
- * Helper structure to keep exchange configuration values.
+ * Our credentials.
  */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
- * Contains plugin.
- */
-static struct TALER_TESTING_DatabaseConnection dbc;
-
-/**
- * Return value from main().
- */
-static int result;
+struct TALER_TESTING_Credentials cred;
 
 /**
  * Name of the configuration file to use.
@@ -70,24 +55,6 @@ static char *config_filename;
   TALER_TESTING_cmd_exec_transfer (label "-transfer", cfg_fn)
 
 
-/**
- * Function run on shutdown to unload the DB plugin.
- *
- * @param cls NULL
- */
-static void
-unload_db (void *cls)
-{
-  (void) cls;
-  if (NULL != dbc.plugin)
-  {
-    dbc.plugin->drop_tables (dbc.plugin->cls);
-    TALER_EXCHANGEDB_plugin_unload (dbc.plugin);
-    dbc.plugin = NULL;
-  }
-}
-
-
 /**
  * Collects all the tests.
  */
@@ -96,11 +63,13 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command all[] = {
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                              config_filename,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    // check no aggregation happens on a empty database
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-1"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_filename,
+                                    "-e",
+                                    NULL),
     CMD_EXEC_AGGREGATOR ("run-aggregator-on-empty-db",
                          config_filename),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"),
@@ -108,7 +77,7 @@ run (void *cls,
     /* check aggregation happens on the simplest case:
        one deposit into the database. */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-1",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -119,15 +88,15 @@ run (void *cls,
                          config_filename),
 
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.89",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-1"),
 
     /* check aggregation accumulates well. */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-2a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -136,7 +105,7 @@ run (void *cls,
                                       "EUR:0.1"),
 
     TALER_TESTING_cmd_insert_deposit ("do-deposit-2b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -148,15 +117,15 @@ run (void *cls,
                          config_filename),
 
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-2",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:1.79",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-2"),
 
     /* check that different merchants stem different aggregations. */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-3a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       "4",
                                       GNUNET_TIME_timestamp_get (),
@@ -164,7 +133,7 @@ run (void *cls,
                                       "EUR:1",
                                       "EUR:0.1"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-3b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       "5",
                                       GNUNET_TIME_timestamp_get (),
@@ -172,7 +141,7 @@ run (void *cls,
                                       "EUR:1",
                                       "EUR:0.1"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-3c",
-                                      &dbc,
+                                      cred.cfg,
                                       "alice",
                                       "4",
                                       GNUNET_TIME_timestamp_get (),
@@ -183,25 +152,25 @@ run (void *cls,
                          config_filename),
 
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3a",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.89",
-                                           bc.exchange_payto,
+                                           cred.exchange_payto,
                                            
"payto://x-taler-bank/localhost/4?receiver-name=4"),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3b",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.89",
-                                           bc.exchange_payto,
+                                           cred.exchange_payto,
                                            
"payto://x-taler-bank/localhost/4?receiver-name=4"),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3c",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.89",
-                                           bc.exchange_payto,
+                                           cred.exchange_payto,
                                            
"payto://x-taler-bank/localhost/5?receiver-name=5"),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-3"),
 
     /* checking that aggregator waits for the deadline. */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-4a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -211,7 +180,7 @@ run (void *cls,
                                       "EUR:0.2",
                                       "EUR:0.1"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-4b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -230,14 +199,14 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-4-delayed",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-4",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.19",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
 
     // test picking all deposits at earliest deadline
     TALER_TESTING_cmd_insert_deposit ("do-deposit-5a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -248,7 +217,7 @@ run (void *cls,
                                       "EUR:0.1"),
 
     TALER_TESTING_cmd_insert_deposit ("do-deposit-5b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -267,13 +236,13 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-5-delayed",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-5",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.19",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     /* Test NEVER running 'tiny' unless they make up minimum unit */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-6a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -285,7 +254,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-6a-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-6b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -293,7 +262,7 @@ run (void *cls,
                                       "EUR:0.102",
                                       "EUR:0.1"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-6c",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -305,7 +274,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-6c-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-6d",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -317,7 +286,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-6d-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-6e",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -327,14 +296,14 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6e",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-6",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.01",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
 
     /* Test profiteering if wire deadline is short */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-7a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -346,7 +315,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-7a-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-7b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -356,14 +325,14 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-profit",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.01",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
 
     /* Now check profit was actually taken */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-7c",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -373,14 +342,14 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-loss",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.01",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
 
     /* Test that aggregation would happen fully if wire deadline is long */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-8a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -394,7 +363,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-8a-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-8b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -410,7 +379,7 @@ run (void *cls,
 
     /* now trigger aggregate with large transaction and short deadline */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-8c",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -420,14 +389,14 @@ run (void *cls,
     CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-8",
                          config_filename),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-8",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.03",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
 
     /* Test aggregation with fees and rounding profits. */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-9a",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -441,7 +410,7 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_empty (
       "expect-empty-transactions-after-9a-tiny"),
     TALER_TESTING_cmd_insert_deposit ("do-deposit-9b",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -457,7 +426,7 @@ run (void *cls,
 
     /* now trigger aggregate with large transaction and short deadline */
     TALER_TESTING_cmd_insert_deposit ("do-deposit-9c",
-                                      &dbc,
+                                      cred.cfg,
                                       "bob",
                                       USER42_ACCOUNT,
                                       GNUNET_TIME_timestamp_get (),
@@ -468,49 +437,15 @@ run (void *cls,
                          config_filename),
     /* 0.009 + 0.009 + 0.022 - 0.001 - 0.002 - 0.008 = 0.029 => 0.02 */
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-9",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:0.01",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_end ()
   };
 
-  TALER_TESTING_run_with_fakebank (is,
-                                   all,
-                                   bc.exchange_auth.wire_gateway_url);
-}
-
-
-/**
- * Prepare database and launch the test.
- *
- * @param cls unused
- * @param is interpreter to use
- */
-static void
-prepare_database (void *cls,
-                  struct TALER_TESTING_Interpreter *is)
-{
-  dbc.plugin = TALER_EXCHANGEDB_plugin_load (is->cfg);
-  if (NULL == dbc.plugin)
-  {
-    GNUNET_break (0);
-    result = 77;
-    TALER_TESTING_interpreter_fail (is);
-    return;
-  }
-  if (GNUNET_OK !=
-      dbc.plugin->preflight (dbc.plugin->cls))
-  {
-    GNUNET_break (0);
-    result = 77;
-    TALER_TESTING_interpreter_fail (is);
-    return;
-  }
-  GNUNET_SCHEDULER_add_shutdown (&unload_db,
-                                 NULL);
-  run (NULL,
-       is);
+  TALER_TESTING_run (is,
+                     all);
 }
 
 
@@ -519,7 +454,6 @@ main (int argc,
       char *const argv[])
 {
   const char *plugin_name;
-  char *testname;
 
   if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
   {
@@ -527,52 +461,17 @@ main (int argc,
     return -1;
   }
   plugin_name++;
-  (void) GNUNET_asprintf (&testname,
-                          "test-taler-exchange-aggregator-%s",
-                          plugin_name);
   (void) GNUNET_asprintf (&config_filename,
-                          "%s.conf",
-                          testname);
-
-  GNUNET_log_setup ("test_taler_exchange_aggregator",
-                    "INFO",
-                    NULL);
-
-  /* these might get in the way */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-
-  TALER_TESTING_cleanup_files (config_filename);
-
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_exchange (config_filename,
-                                      GNUNET_YES,
-                                      &ec))
-  {
-    TALER_LOG_WARNING ("Could not prepare the exchange.\n");
-    return 77;
-  }
-
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_filename,
-                                      "exchange-account-1",
-                                      &bc))
-  {
-    TALER_LOG_WARNING ("Could not prepare the fakebank\n");
-    return 77;
-  }
-  result = GNUNET_OK;
-  if (GNUNET_OK !=
-      TALER_TESTING_setup_with_exchange (&prepare_database,
-                                         NULL,
-                                         config_filename))
-  {
-    TALER_LOG_WARNING ("Could not prepare database for tests.\n");
-    return result;
-  }
-  GNUNET_free (config_filename);
-  GNUNET_free (testname);
-  return GNUNET_OK == result ? 0 : 1;
+                          "test-taler-exchange-aggregator-%s.conf",
+                          plugin_name);
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_filename,
+                             "exchange-account-1",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/test_taler_exchange_wirewatch.c 
b/src/testing/test_taler_exchange_wirewatch.c
index eecb4fd6..86e104f4 100644
--- a/src/testing/test_taler_exchange_wirewatch.c
+++ b/src/testing/test_taler_exchange_wirewatch.c
@@ -34,14 +34,9 @@
 
 
 /**
- * Bank configuration data.
+ * Our credentials.
  */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
- * Helper structure to keep exchange configuration values.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
 
 /**
  * Name of the configuration file to use.
@@ -66,8 +61,8 @@ transfer_to_exchange (const char *label,
 {
   return TALER_TESTING_cmd_admin_add_incoming (label,
                                                amount,
-                                               &bc.exchange_auth,
-                                               bc.user42_payto);
+                                               &cred.ba,
+                                               cred.user42_payto);
 }
 
 
@@ -82,21 +77,19 @@ run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command all[] = {
-    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
-                                              config_filename,
-                                              "EUR:0.01",
-                                              "EUR:0.01"),
-    TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
-                                   MHD_HTTP_NO_CONTENT,
-                                   false),
-    TALER_TESTING_cmd_wire_add ("add-wire-account",
-                                
"payto://x-taler-bank/localhost/2?receiver-name=2",
-                                MHD_HTTP_NO_CONTENT,
-                                false),
-    TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
-                                              config_filename),
-    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
-                                                1),
+    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                    cred.cfg,
+                                    "exchange-account-1"),
+    TALER_TESTING_cmd_system_start ("start-taler",
+                                    config_filename,
+                                    "-e",
+                                    "-u", "exchange-account-1",
+                                    NULL),
+    TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                    cred.cfg,
+                                    true,
+                                    true),
+    TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys"),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"),
     CMD_EXEC_AGGREGATOR ("run-aggregator-on-empty"),
     TALER_TESTING_cmd_exec_wirewatch ("run-wirewatch-on-empty",
@@ -111,8 +104,8 @@ run (void *cls,
     TALER_TESTING_cmd_check_bank_admin_transfer (
       "clear-good-transfer-to-the-exchange",
       "EUR:5",
-      bc.user42_payto,                                            // debit
-      bc.exchange_payto,                                            // credit
+      cred.user42_payto,                                            // debit
+      cred.exchange_payto,                                            // credit
       "run-transfer-good-to-exchange"),
 
     TALER_TESTING_cmd_exec_closer ("run-closer-non-expired-reserve",
@@ -135,18 +128,17 @@ run (void *cls,
 
     CMD_EXEC_AGGREGATOR ("run-closer-on-expired-reserve"),
     TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1",
-                                           ec.exchange_url,
+                                           cred.exchange_url,
                                            "EUR:4.99",
-                                           bc.exchange_payto,
-                                           bc.user42_payto),
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
     TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-2"),
     TALER_TESTING_cmd_end ()
   };
 
   (void) cls;
-  TALER_TESTING_run_with_fakebank (is,
-                                   all,
-                                   bc.exchange_auth.wire_gateway_url);
+  TALER_TESTING_run (is,
+                     all);
 }
 
 
@@ -154,69 +146,29 @@ int
 main (int argc,
       char *const argv[])
 {
-  const char *plugin_name;
-
   (void) argc;
-  /* these might get in the way */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-  GNUNET_log_setup ("test_taler_exchange_wirewatch",
-                    "INFO",
-                    NULL);
-
-  if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
   {
-    GNUNET_break (0);
-    return -1;
-  }
-  plugin_name++;
-  {
-    char *testname;
-
-    GNUNET_asprintf (&testname,
-                     "test-taler-exchange-wirewatch-%s",
-                     plugin_name);
+    const char *plugin_name;
+
+    plugin_name = strrchr (argv[0], (int) '-');
+    if (NULL == plugin_name)
+    {
+      GNUNET_break (0);
+      return -1;
+    }
+    plugin_name++;
     GNUNET_asprintf (&config_filename,
-                     "%s.conf",
-                     testname);
-    GNUNET_free (testname);
-  }
-  /* 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",
-                              NULL,
-                              es,
-                              NULL);
-    if (NULL == conn)
-      return 77;
-    GNUNET_PQ_disconnect (conn);
-  }
-
-  TALER_TESTING_cleanup_files (config_filename);
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_exchange (config_filename,
-                                      GNUNET_YES,
-                                      &ec))
-  {
-    TALER_LOG_INFO ("Could not prepare the exchange\n");
-    return 77;
+                     "test-taler-exchange-wirewatch-%s.conf",
+                     plugin_name);
   }
-
-  if (GNUNET_OK !=
-      TALER_TESTING_prepare_fakebank (config_filename,
-                                      "exchange-account-1",
-                                      &bc))
-    return 77;
-
-  return (GNUNET_OK ==
-          TALER_TESTING_setup_with_exchange (&run,
-                                             NULL,
-                                             config_filename)) ? 0 : 1;
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_filename,
+                             "exchange-account-1",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
 }
 
 
diff --git a/src/testing/testing_api_cmd_auditor_add.c 
b/src/testing/testing_api_cmd_auditor_add.c
index 41182b7c..aaa95552 100644
--- a/src/testing/testing_api_cmd_auditor_add.c
+++ b/src/testing/testing_api_cmd_auditor_add.c
@@ -18,7 +18,7 @@
 */
 /**
  * @file testing/testing_api_cmd_auditor_add.c
- * @brief command for testing /auditor_add.
+ * @brief command for testing auditor_add
  * @author Christian Grothoff
  */
 #include "platform.h"
@@ -75,16 +75,8 @@ auditor_add_cb (
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -106,10 +98,43 @@ auditor_add_run (void *cls,
   struct AuditorAddState *ds = cls;
   struct GNUNET_TIME_Timestamp now;
   struct TALER_MasterSignatureP master_sig;
+  const struct TALER_AuditorPublicKeyP *auditor_pub;
+  const struct TALER_TESTING_Command *auditor_cmd;
+  const struct TALER_TESTING_Command *exchange_cmd;
+  const char *exchange_url;
+  const char *auditor_url;
 
   (void) cmd;
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
+
+  auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                       "auditor");
+  if (NULL == auditor_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+                                                      &auditor_pub));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor_url (auditor_cmd,
+                                                      &auditor_url));
+  exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                        "exchange");
+  if (NULL == exchange_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                       &exchange_url));
+
+
   if (ds->bad_sig)
   {
     memset (&master_sig,
@@ -118,17 +143,22 @@ auditor_add_run (void *cls,
   }
   else
   {
-    TALER_exchange_offline_auditor_add_sign (&is->auditor_pub,
-                                             is->auditor_url,
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
+    TALER_exchange_offline_auditor_add_sign (auditor_pub,
+                                             auditor_url,
                                              now,
-                                             &is->master_priv,
+                                             master_priv,
                                              &master_sig);
   }
   ds->dh = TALER_EXCHANGE_management_enable_auditor (
-    is->ctx,
-    is->exchange_url,
-    &is->auditor_pub,
-    is->auditor_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
+    auditor_pub,
+    auditor_url,
     "test-case auditor", /* human-readable auditor name */
     now,
     &master_sig,
@@ -158,10 +188,8 @@ auditor_add_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_enable_auditor_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_auditor_add_denom_sig.c 
b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
index 55b9f77a..2df15fde 100644
--- a/src/testing/testing_api_cmd_auditor_add_denom_sig.c
+++ b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
@@ -80,16 +80,8 @@ denom_sig_add_cb (
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -112,6 +104,11 @@ auditor_add_run (void *cls,
   struct TALER_AuditorSignatureP auditor_sig;
   struct TALER_DenominationHashP h_denom_pub;
   const struct TALER_EXCHANGE_DenomPublicKey *dk;
+  const struct TALER_AuditorPublicKeyP *auditor_pub;
+  const struct TALER_TESTING_Command *auditor_cmd;
+  const struct TALER_TESTING_Command *exchange_cmd;
+  const char *exchange_url;
+  const char *auditor_url;
 
   (void) cmd;
   /* Get denom pub from trait */
@@ -120,7 +117,6 @@ auditor_add_run (void *cls,
 
     denom_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                           ds->denom_ref);
-
     if (NULL == denom_cmd)
     {
       GNUNET_break (0);
@@ -133,6 +129,31 @@ auditor_add_run (void *cls,
                                                       &dk));
   }
   ds->is = is;
+  auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                       "auditor");
+  if (NULL == auditor_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+                                                      &auditor_pub));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor_url (auditor_cmd,
+                                                      &auditor_url));
+  exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                        "exchange");
+  if (NULL == exchange_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                       &exchange_url));
   if (ds->bad_sig)
   {
     memset (&auditor_sig,
@@ -141,28 +162,33 @@ auditor_add_run (void *cls,
   }
   else
   {
-    struct TALER_MasterPublicKeyP master_pub;
+    const struct TALER_MasterPublicKeyP *master_pub;
+    const struct TALER_AuditorPrivateKeyP *auditor_priv;
 
-    GNUNET_CRYPTO_eddsa_key_get_public (&is->master_priv.eddsa_priv,
-                                        &master_pub.eddsa_pub);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_pub (exchange_cmd,
+                                                       &master_pub));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_auditor_priv (auditor_cmd,
+                                                         &auditor_priv));
     TALER_auditor_denom_validity_sign (
-      is->auditor_url,
+      auditor_url,
       &dk->h_key,
-      &master_pub,
+      master_pub,
       dk->valid_from,
       dk->withdraw_valid_until,
       dk->expire_deposit,
       dk->expire_legal,
       &dk->value,
       &dk->fees,
-      &is->auditor_priv,
+      auditor_priv,
       &auditor_sig);
   }
   ds->dh = TALER_EXCHANGE_add_auditor_denomination (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     &h_denom_pub,
-    &is->auditor_pub,
+    auditor_pub,
     &auditor_sig,
     &denom_sig_add_cb,
     ds);
@@ -190,10 +216,8 @@ auditor_add_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_add_auditor_denomination_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_auditor_del.c 
b/src/testing/testing_api_cmd_auditor_del.c
index 5bf77bb5..df878f8e 100644
--- a/src/testing/testing_api_cmd_auditor_del.c
+++ b/src/testing/testing_api_cmd_auditor_del.c
@@ -76,16 +76,8 @@ auditor_del_cb (
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -107,10 +99,36 @@ auditor_del_run (void *cls,
   struct AuditorDelState *ds = cls;
   struct TALER_MasterSignatureP master_sig;
   struct GNUNET_TIME_Timestamp now;
+  const struct TALER_AuditorPublicKeyP *auditor_pub;
+  const struct TALER_TESTING_Command *auditor_cmd;
+  const struct TALER_TESTING_Command *exchange_cmd;
+  const char *exchange_url;
 
   (void) cmd;
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
+  auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                       "auditor");
+  if (NULL == auditor_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor_pub (auditor_cmd,
+                                                      &auditor_pub));
+  exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                        "exchange");
+  if (NULL == exchange_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                       &exchange_url));
   if (ds->bad_sig)
   {
     memset (&master_sig,
@@ -119,15 +137,20 @@ auditor_del_run (void *cls,
   }
   else
   {
-    TALER_exchange_offline_auditor_del_sign (&is->auditor_pub,
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
+    TALER_exchange_offline_auditor_del_sign (auditor_pub,
                                              now,
-                                             &is->master_priv,
+                                             master_priv,
                                              &master_sig);
   }
   ds->dh = TALER_EXCHANGE_management_disable_auditor (
-    is->ctx,
-    is->exchange_url,
-    &is->auditor_pub,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
+    auditor_pub,
     now,
     &master_sig,
     &auditor_del_cb,
@@ -156,10 +179,8 @@ auditor_del_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_disable_auditor_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c 
b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
index 293ecba2..c6a1516e 100644
--- a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
+++ b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
@@ -68,11 +68,6 @@ struct DepositConfirmationState
    */
   struct TALER_AUDITOR_DepositConfirmationHandle *dc;
 
-  /**
-   * Auditor connection.
-   */
-  struct TALER_AUDITOR_Handle *auditor;
-
   /**
    * Interpreter state.
    */
@@ -125,8 +120,7 @@ do_retry (void *cls)
   struct DepositConfirmationState *dcs = cls;
 
   dcs->retry_task = NULL;
-  dcs->is->commands[dcs->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (dcs->is);
   deposit_confirmation_run (dcs,
                             NULL,
                             dcs->is);
@@ -166,21 +160,15 @@ deposit_confirmation_cb (void *cls,
         else
           dcs->backoff = GNUNET_TIME_randomized_backoff (dcs->backoff,
                                                          MAX_BACKOFF);
-        dcs->is->commands[dcs->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (dcs->is);
         dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff,
                                                         &do_retry,
                                                         dcs);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                dcs->is->commands[dcs->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply, stderr, 0);
-    TALER_TESTING_interpreter_fail (dcs->is);
+    TALER_TESTING_unexpected_status (dcs->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (dcs->is);
@@ -220,10 +208,31 @@ deposit_confirmation_run (void *cls,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv;
   const struct TALER_EXCHANGE_Keys *keys;
   const struct TALER_EXCHANGE_SigningPublicKey *spk;
+  struct TALER_AUDITOR_Handle *auditor;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   dcs->is = is;
   GNUNET_assert (NULL != dcs->deposit_reference);
+  {
+    const struct TALER_TESTING_Command *auditor_cmd;
+
+    auditor_cmd
+      = TALER_TESTING_interpreter_get_command (is,
+                                               "auditor");
+    if (NULL == auditor_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_auditor (auditor_cmd,
+                                                    &auditor));
+  }
   deposit_cmd
     = TALER_TESTING_interpreter_lookup_command (is,
                                                 dcs->deposit_reference);
@@ -251,7 +260,7 @@ deposit_confirmation_run (void *cls,
                                                         dcs->coin_index,
                                                         &wire_deadline));
   GNUNET_assert (NULL != exchange_timestamp);
-  keys = TALER_EXCHANGE_get_keys (dcs->is->exchange);
+  keys = TALER_EXCHANGE_get_keys (exchange);
   GNUNET_assert (NULL != keys);
   spk = TALER_EXCHANGE_get_signing_key_info (keys,
                                              exchange_pub);
@@ -308,7 +317,7 @@ deposit_confirmation_run (void *cls,
     if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
       refund_deadline = timestamp;
   }
-  dcs->dc = TALER_AUDITOR_deposit_confirmation (dcs->auditor,
+  dcs->dc = TALER_AUDITOR_deposit_confirmation (auditor,
                                                 &h_wire,
                                                 &no_h_policy,
                                                 &h_contract_terms,
@@ -353,10 +362,8 @@ deposit_confirmation_cleanup (void *cls,
 
   if (NULL != dcs->dc)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                dcs->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (dcs->is,
+                                      cmd->label);
     TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc);
     dcs->dc = NULL;
   }
@@ -371,7 +378,6 @@ deposit_confirmation_cleanup (void *cls,
 
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_deposit_confirmation (const char *label,
-                                        struct TALER_AUDITOR_Handle *auditor,
                                         const char *deposit_reference,
                                         unsigned int coin_index,
                                         const char *amount_without_fee,
@@ -380,7 +386,6 @@ TALER_TESTING_cmd_deposit_confirmation (const char *label,
   struct DepositConfirmationState *dcs;
 
   dcs = GNUNET_new (struct DepositConfirmationState);
-  dcs->auditor = auditor;
   dcs->deposit_reference = deposit_reference;
   dcs->coin_index = coin_index;
   dcs->amount_without_fee = amount_without_fee;
diff --git a/src/testing/testing_api_cmd_auditor_exchanges.c 
b/src/testing/testing_api_cmd_auditor_exchanges.c
index 1e412b2d..070b6409 100644
--- a/src/testing/testing_api_cmd_auditor_exchanges.c
+++ b/src/testing/testing_api_cmd_auditor_exchanges.c
@@ -53,11 +53,6 @@ struct ExchangesState
    */
   struct TALER_AUDITOR_ListExchangesHandle *leh;
 
-  /**
-   * Auditor connection.
-   */
-  struct TALER_AUDITOR_Handle *auditor;
-
   /**
    * Interpreter state.
    */
@@ -115,8 +110,7 @@ do_retry (void *cls)
   struct ExchangesState *es = cls;
 
   es->retry_task = NULL;
-  es->is->commands[es->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (es->is);
   exchanges_run (es,
                  NULL,
                  es->is);
@@ -159,24 +153,15 @@ exchanges_cb (void *cls,
         else
           es->backoff = GNUNET_TIME_randomized_backoff (es->backoff,
                                                         MAX_BACKOFF);
-        es->is->commands[es->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (es->is);
         es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff,
                                                        &do_retry,
                                                        es);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                es->is->commands[es->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (es->is);
+    TALER_TESTING_unexpected_status (es->is,
+                                     hr->http_status);
     return;
   }
   if (NULL != es->exchange_url)
@@ -217,13 +202,26 @@ exchanges_run (void *cls,
                struct TALER_TESTING_Interpreter *is)
 {
   struct ExchangesState *es = cls;
+  const struct TALER_TESTING_Command *auditor_cmd;
+  struct TALER_AUDITOR_Handle *auditor;
 
   (void) cmd;
+  auditor_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                       "auditor");
+  if (NULL == auditor_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_auditor (auditor_cmd,
+                                                  &auditor));
+
   es->is = is;
-  es->leh = TALER_AUDITOR_list_exchanges
-              (is->auditor,
-              &exchanges_cb,
-              es);
+  es->leh = TALER_AUDITOR_list_exchanges (auditor,
+                                          &exchanges_cb,
+                                          es);
 
   if (NULL == es->leh)
   {
@@ -250,10 +248,8 @@ exchanges_cleanup (void *cls,
 
   if (NULL != es->leh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                es->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (es->is,
+                                      cmd->label);
     TALER_AUDITOR_list_exchanges_cancel (es->leh);
     es->leh = NULL;
   }
@@ -275,7 +271,7 @@ exchanges_cleanup (void *cls,
  * @param index index number of the traits to be returned.
  * @return #GNUNET_OK on success
  */
-static int
+static enum GNUNET_GenericReturnValue
 exchanges_traits (void *cls,
                   const void **ret,
                   const char *trait,
@@ -301,13 +297,11 @@ exchanges_traits (void *cls,
  */
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_exchanges (const char *label,
-                             struct TALER_AUDITOR_Handle *auditor,
                              unsigned int expected_response_code)
 {
   struct ExchangesState *es;
 
   es = GNUNET_new (struct ExchangesState);
-  es->auditor = auditor;
   es->expected_response_code = expected_response_code;
 
   {
diff --git a/src/testing/testing_api_cmd_bank_admin_add_incoming.c 
b/src/testing/testing_api_cmd_bank_admin_add_incoming.c
index a7c5dd45..fcf6079a 100644
--- a/src/testing/testing_api_cmd_bank_admin_add_incoming.c
+++ b/src/testing/testing_api_cmd_bank_admin_add_incoming.c
@@ -180,8 +180,7 @@ do_retry (void *cls)
   struct AdminAddIncomingState *fts = cls;
 
   fts->retry_task = NULL;
-  fts->is->commands[fts->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (fts->is);
   admin_add_incoming_run (fts,
                           NULL,
                           fts->is);
@@ -279,7 +278,7 @@ confirmation_cb (void *cls,
         else
           fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
                                                          MAX_BACKOFF);
-        fts->is->commands[fts->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (fts->is);
         fts->retry_task = GNUNET_SCHEDULER_add_delayed (
           fts->backoff,
           &do_retry,
@@ -314,6 +313,7 @@ admin_add_incoming_run (void *cls,
   bool have_public = false;
 
   (void) cmd;
+  fts->is = is;
   /* Use reserve public key as subject */
   if (NULL != fts->reserve_reference)
   {
@@ -364,7 +364,6 @@ admin_add_incoming_run (void *cls,
   fts->reserve_history.amount = fts->amount;
   fts->reserve_history.details.in_details.sender_url
     = (char *) fts->payto_debit_account; /* remember to NOT free this one... */
-  fts->is = is;
   fts->aih
     = TALER_BANK_admin_add_incoming (
         TALER_TESTING_interpreter_get_context (is),
@@ -398,9 +397,8 @@ admin_add_incoming_cleanup (void *cls,
 
   if (NULL != fts->aih)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %s did not complete\n",
-                cmd->label);
+    TALER_TESTING_command_incomplete (fts->is,
+                                      cmd->label);
     TALER_BANK_admin_add_incoming_cancel (fts->aih);
     fts->aih = NULL;
   }
@@ -439,12 +437,12 @@ admin_add_incoming_traits (void *cls,
   {
     struct TALER_TESTING_Trait traits[] = {
       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
-      TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
-      TALER_TESTING_make_trait_payto_uri (&fts->payto_debit_account),
+      TALER_TESTING_make_trait_debit_payto_uri (fts->payto_debit_account),
+      TALER_TESTING_make_trait_payto_uri (fts->payto_debit_account),
       /* Used as a marker, content does not matter */
-      TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
+      TALER_TESTING_make_trait_credit_payto_uri (void_uri),
       TALER_TESTING_make_trait_exchange_bank_account_url (
-        &fts->exchange_credit_url),
+        fts->exchange_credit_url),
       TALER_TESTING_make_trait_amount (&fts->amount),
       TALER_TESTING_make_trait_timestamp (0,
                                           &fts->timestamp),
@@ -464,13 +462,14 @@ admin_add_incoming_traits (void *cls,
   {
     struct TALER_TESTING_Trait traits[] = {
       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
-      TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
+      TALER_TESTING_make_trait_debit_payto_uri (fts->payto_debit_account),
       /* Used as a marker, content does not matter */
-      TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
+      TALER_TESTING_make_trait_credit_payto_uri (void_uri),
       TALER_TESTING_make_trait_exchange_bank_account_url (
-        &fts->exchange_credit_url),
+        fts->exchange_credit_url),
       TALER_TESTING_make_trait_amount (&fts->amount),
-      TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
+      TALER_TESTING_make_trait_timestamp (0,
+                                          &fts->timestamp),
       TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
       TALER_TESTING_make_trait_reserve_history (0,
                                                 &fts->reserve_history),
diff --git a/src/testing/testing_api_cmd_bank_admin_check.c 
b/src/testing/testing_api_cmd_bank_admin_check.c
index c49e49d8..6406fe2c 100644
--- a/src/testing/testing_api_cmd_bank_admin_check.c
+++ b/src/testing/testing_api_cmd_bank_admin_check.c
@@ -82,8 +82,30 @@ check_bank_admin_transfer_run (void *cls,
   const char *credit_payto;
   const struct TALER_ReservePublicKeyP *reserve_pub;
   const struct TALER_TESTING_Command *cmd_ref;
+  struct TALER_FAKEBANK_Handle *fakebank;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *fakebank_cmd;
+
+    fakebank_cmd
+      = TALER_TESTING_interpreter_get_command (is,
+                                               "fakebank");
+    if (NULL == fakebank_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+                                          &fakebank))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
   cmd_ref
     = TALER_TESTING_interpreter_lookup_command (is,
                                                 bcs->reserve_pub_ref);
@@ -109,9 +131,9 @@ check_bank_admin_transfer_run (void *cls,
                               &amount))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to parse amount `%s' at %u\n",
+                "Failed to parse amount `%s' at %s\n",
                 bcs->amount,
-                is->ip);
+                TALER_TESTING_interpreter_get_current_label (is));
     TALER_TESTING_interpreter_fail (is);
     return;
   }
@@ -122,7 +144,7 @@ check_bank_admin_transfer_run (void *cls,
               debit_payto,
               debit_account);
   if (GNUNET_OK !=
-      TALER_FAKEBANK_check_credit (is->fakebank,
+      TALER_FAKEBANK_check_credit (fakebank,
                                    &amount,
                                    debit_account,
                                    credit_account,
diff --git a/src/testing/testing_api_cmd_bank_check.c 
b/src/testing/testing_api_cmd_bank_check.c
index f2148d05..77d120e0 100644
--- a/src/testing/testing_api_cmd_bank_check.c
+++ b/src/testing/testing_api_cmd_bank_check.c
@@ -91,26 +91,48 @@ check_bank_transfer_run (void *cls,
   struct TALER_Amount amount;
   char *debit_account;
   char *credit_account;
-  const char **exchange_base_url;
-  const char **debit_payto;
-  const char **credit_payto;
+  const char *exchange_base_url;
+  const char *debit_payto;
+  const char *credit_payto;
+  struct TALER_FAKEBANK_Handle *fakebank;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *fakebank_cmd;
+
+    fakebank_cmd
+      = TALER_TESTING_interpreter_get_command (is,
+                                               "fakebank");
+    if (NULL == fakebank_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+                                          &fakebank))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
   if (NULL == bcs->deposit_reference)
   {
     TALER_LOG_INFO ("Deposit reference NOT given\n");
-    debit_payto = &bcs->debit_payto;
-    credit_payto = &bcs->credit_payto;
-    exchange_base_url = &bcs->exchange_base_url;
+    debit_payto = bcs->debit_payto;
+    credit_payto = bcs->credit_payto;
+    exchange_base_url = bcs->exchange_base_url;
 
     if (GNUNET_OK !=
         TALER_string_to_amount (bcs->amount,
                                 &amount))
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to parse amount `%s' at %u\n",
+                  "Failed to parse amount `%s' at %s\n",
                   bcs->amount,
-                  is->ip);
+                  TALER_TESTING_interpreter_get_current_label (is));
       TALER_TESTING_interpreter_fail (is);
       return;
     }
@@ -145,27 +167,22 @@ check_bank_transfer_run (void *cls,
       TALER_TESTING_FAIL (is);
     amount = *amount_ptr;
   }
-
-
-  debit_account = TALER_xtalerbank_account_from_payto (*debit_payto);
-  credit_account = TALER_xtalerbank_account_from_payto (*credit_payto);
-
+  debit_account = TALER_xtalerbank_account_from_payto (debit_payto);
+  credit_account = TALER_xtalerbank_account_from_payto (credit_payto);
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "converted debit_payto (%s) to debit_account (%s)\n",
-              *debit_payto,
+              debit_payto,
               debit_account);
-
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "converted credit_payto (%s) to credit_account (%s)\n",
-              *credit_payto,
+              credit_payto,
               credit_account);
-
   if (GNUNET_OK !=
-      TALER_FAKEBANK_check_debit (is->fakebank,
+      TALER_FAKEBANK_check_debit (fakebank,
                                   &amount,
                                   debit_account,
                                   credit_account,
-                                  *exchange_base_url,
+                                  exchange_base_url,
                                   &bcs->wtid))
   {
     GNUNET_break (0);
@@ -217,7 +234,7 @@ check_bank_transfer_traits (void *cls,
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_wtid (wtid_ptr),
     TALER_TESTING_make_trait_exchange_url (
-      &bcs->exchange_base_url),
+      bcs->exchange_base_url),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_bank_check_empty.c 
b/src/testing/testing_api_cmd_bank_check_empty.c
index 84976b0b..60f00fbb 100644
--- a/src/testing/testing_api_cmd_bank_check_empty.c
+++ b/src/testing/testing_api_cmd_bank_check_empty.c
@@ -58,9 +58,33 @@ check_bank_empty_run (void *cls,
                       const struct TALER_TESTING_Command *cmd,
                       struct TALER_TESTING_Interpreter *is)
 {
+  struct TALER_FAKEBANK_Handle *fakebank;
+
   (void) cls;
   (void) cmd;
-  if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank))
+  {
+    const struct TALER_TESTING_Command *fakebank_cmd;
+
+    fakebank_cmd
+      = TALER_TESTING_interpreter_get_command (is,
+                                               "fakebank");
+    if (NULL == fakebank_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_fakebank (fakebank_cmd,
+                                          &fakebank))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
+  if (GNUNET_OK !=
+      TALER_FAKEBANK_check_empty (fakebank))
   {
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (is);
diff --git a/src/testing/testing_api_cmd_bank_history_credit.c 
b/src/testing/testing_api_cmd_bank_history_credit.c
index 119c5d86..286d0631 100644
--- a/src/testing/testing_api_cmd_bank_history_credit.c
+++ b/src/testing/testing_api_cmd_bank_history_credit.c
@@ -82,6 +82,11 @@ struct HistoryState
    */
   struct TALER_BANK_CreditHistoryHandle *hh;
 
+  /**
+   * The interpreter.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
   /**
    * Authentication data for the operation.
    */
@@ -143,6 +148,139 @@ print_expected (struct History *h,
 }
 
 
+/**
+ * Closure for command_cb().
+ */
+struct IteratorContext
+{
+  /**
+   * Array of history items to return.
+   */
+  struct History *h;
+
+  /**
+   * Set to the row ID from where on we should actually process history items,
+   * or NULL if we should process all of them.
+   */
+  const uint64_t *row_id_start;
+
+  /**
+   * History state we are working on.
+   */
+  struct HistoryState *hs;
+
+  /**
+   * Current length of the @e h array.
+   */
+  unsigned int total;
+
+  /**
+   * Current write position in @e h array.
+   */
+  unsigned int pos;
+
+  /**
+   * Ok equals True whenever a starting row_id was provided AND was found
+   * among the CMDs, OR no starting row was given in the first place.
+   */
+  bool ok;
+
+};
+
+
+/**
+ * Helper function of build_history() that expands
+ * the history for each relevant command encountered.
+ *
+ * @param[in,out] cls our `struct IteratorContext`
+ * @param cmd a command to process
+ */
+static void
+command_cb (void *cls,
+            const struct TALER_TESTING_Command *cmd)
+{
+  struct IteratorContext *ic = cls;
+  struct HistoryState *hs = ic->hs;
+  const uint64_t *row_id;
+  const char *credit_account;
+  const char *debit_account;
+  const struct TALER_Amount *amount;
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+  const char *exchange_credit_url;
+
+  /**
+   * The following command allows us to skip over those CMDs
+   * that do not offer a "row_id" trait.  Such skipped CMDs are
+   * not interesting for building a history.
+   */
+  if ( (GNUNET_OK !=
+        TALER_TESTING_get_trait_bank_row (cmd,
+                                          &row_id)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_credit_payto_uri (cmd,
+                                                  &credit_account)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_debit_payto_uri (cmd,
+                                                 &debit_account)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_amount (cmd,
+                                        &amount)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_reserve_pub (cmd,
+                                             &reserve_pub)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_exchange_bank_account_url (
+          cmd,
+          &exchange_credit_url)) )
+    return;   // Not an interesting event
+
+  /**
+   * Is the interesting event a match with regard to
+   * the row_id value?  If yes, store this condition
+   * to the state and analyze the next CMDs.
+   */
+  if ( (NULL != ic->row_id_start) &&
+       (*(ic->row_id_start) == *row_id) &&
+       (! ic->ok) )
+  {
+    ic->ok = true;
+    return;
+  }
+  /**
+   * The interesting event didn't match the wanted
+   * row_id value, analyze the next CMDs.  Note: this
+   * branch is relevant only when row_id WAS given.
+   */
+  if (! ic->ok)
+    return;
+  if (0 != strcasecmp (hs->account_url,
+                       exchange_credit_url))
+    return;   // Account mismatch
+  if (ic->total >= GNUNET_MAX (hs->num_results,
+                               -hs->num_results) )
+  {
+    TALER_LOG_DEBUG ("Hit history limit\n");
+    return;
+  }
+  TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
+                  debit_account,
+                  credit_account,
+                  hs->account_url);
+  /* found matching record, make sure we have room */
+  if (ic->pos == ic->total)
+    GNUNET_array_grow (ic->h,
+                       ic->total,
+                       ic->pos * 2);
+  ic->h[ic->pos].url = GNUNET_strdup (debit_account);
+  ic->h[ic->pos].details.debit_account_uri = ic->h[ic->pos].url;
+  ic->h[ic->pos].details.amount = *amount;
+  ic->h[ic->pos].row_id = *row_id;
+  ic->h[ic->pos].details.reserve_pub = *reserve_pub;
+  ic->h[ic->pos].details.credit_account_uri = exchange_credit_url;
+  ic->pos++;
+}
+
+
 /**
  * This function constructs the list of history elements that
  * interest the account number of the caller.  It has two main
@@ -156,23 +294,18 @@ print_expected (struct History *h,
  * @return number of entries in @a rh.
  */
 static unsigned int
-build_history (struct TALER_TESTING_Interpreter *is,
+build_history (struct HistoryState *hs,
                struct History **rh)
 {
-  struct HistoryState *hs = is->commands[is->ip].cls;
-  unsigned int total;
-  unsigned int pos;
-  struct History *h;
-  const struct TALER_TESTING_Command *add_incoming_cmd;
-  int inc;
-  unsigned int start;
-  unsigned int end;
-
-  int ok;
-  const uint64_t *row_id_start = NULL;
+  struct TALER_TESTING_Interpreter *is = hs->is;
+  struct IteratorContext ic = {
+    .hs = hs
+  };
 
   if (NULL != hs->start_row_reference)
   {
+    const struct TALER_TESTING_Command *add_incoming_cmd;
+
     TALER_LOG_INFO ("`%s': start row given via reference `%s'\n",
                     TALER_TESTING_interpreter_get_current_label (is),
                     hs->start_row_reference);
@@ -182,131 +315,28 @@ build_history (struct TALER_TESTING_Interpreter *is,
     GNUNET_assert (NULL != add_incoming_cmd);
     GNUNET_assert (GNUNET_OK ==
                    TALER_TESTING_get_trait_row (add_incoming_cmd,
-                                                &row_id_start));
-  }
-
-  GNUNET_assert (0 != hs->num_results);
-  if (0 == is->ip)
-  {
-    TALER_LOG_DEBUG ("Checking history at FIRST transaction (EMPTY)\n");
-    *rh = NULL;
-    return 0;
+                                                &ic.row_id_start));
   }
 
-  if (hs->num_results > 0)
-  {
-    inc = 1;  /* _inc_rement */
-    start = 0;
-    end = is->ip - 1;
-  }
-  else
-  {
-    inc = -1;
-    start = is->ip - 1;
-    end = 0;
-  }
-  /**
-   * ok equals GNUNET_YES whenever a starting row_id
-   * was provided AND was found among the CMDs, OR no
-   * starting row was given in the first place.
-   */
-  ok = GNUNET_NO;
-  if (NULL == row_id_start)
-    ok = GNUNET_YES;
-  h = NULL;
-  total = 0;
-  GNUNET_array_grow (h,
-                     total,
+  ic.ok = false;
+  if (NULL == ic.row_id_start)
+    ic.ok = true;
+  GNUNET_array_grow (ic.h,
+                     ic.total,
                      4);
-  pos = 0;
-  for (unsigned int off = start; off != end + inc; off += inc)
-  {
-    const struct TALER_TESTING_Command *cmd = &is->commands[off];
-    const uint64_t *row_id;
-    const char **credit_account;
-    const char **debit_account;
-    const struct TALER_Amount *amount;
-    const struct TALER_ReservePublicKeyP *reserve_pub;
-    const char **exchange_credit_url;
-
-    /**
-     * The following command allows us to skip over those CMDs
-     * that do not offer a "row_id" trait.  Such skipped CMDs are
-     * not interesting for building a history.
-     */
-    if ( (GNUNET_OK !=
-          TALER_TESTING_get_trait_bank_row (cmd,
-                                            &row_id)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_credit_payto_uri (cmd,
-                                                    &credit_account)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_debit_payto_uri (cmd,
-                                                   &debit_account)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_amount (cmd,
-                                          &amount)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_reserve_pub (cmd,
-                                               &reserve_pub)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_exchange_bank_account_url (
-            cmd,
-            &exchange_credit_url)) )
-      continue; // Not an interesting event
-    /**
-     * Is the interesting event a match with regard to
-     * the row_id value?  If yes, store this condition
-     * to the state and analyze the next CMDs.
-     */
-    if ( (NULL != row_id_start) &&
-         (*row_id_start == *row_id) &&
-         (GNUNET_NO == ok) )
-    {
-      ok = GNUNET_YES;
-      continue;
-    }
-    /**
-     * The interesting event didn't match the wanted
-     * row_id value, analyze the next CMDs.  Note: this
-     * branch is relevant only when row_id WAS given.
-     */
-    if (GNUNET_NO == ok)
-      continue;
-    if (0 != strcasecmp (hs->account_url,
-                         *exchange_credit_url))
-      continue; // Account mismatch
-    if (total >= GNUNET_MAX (hs->num_results,
-                             -hs->num_results) )
-    {
-      TALER_LOG_DEBUG ("Hit history limit\n");
-      break;
-    }
-    TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
-                    *debit_account,
-                    *credit_account,
-                    hs->account_url);
-    /* found matching record, make sure we have room */
-    if (pos == total)
-      GNUNET_array_grow (h,
-                         total,
-                         pos * 2);
-    h[pos].url = GNUNET_strdup (*debit_account);
-    h[pos].details.debit_account_uri = h[pos].url;
-    h[pos].details.amount = *amount;
-    h[pos].row_id = *row_id;
-    h[pos].details.reserve_pub = *reserve_pub;
-    h[pos].details.credit_account_uri = *exchange_credit_url;
-    pos++;
-  }
-  GNUNET_assert (GNUNET_YES == ok);
-  GNUNET_array_grow (h,
-                     total,
-                     pos);
-  if (0 == pos)
+  GNUNET_assert (0 != hs->num_results);
+  TALER_TESTING_iterate (is,
+                         hs->num_results > 0,
+                         &command_cb,
+                         &ic);
+  GNUNET_assert (ic.ok);
+  GNUNET_array_grow (ic.h,
+                     ic.total,
+                     ic.pos);
+  if (0 == ic.pos)
     TALER_LOG_DEBUG ("Empty credit history computed\n");
-  *rh = h;
-  return total;
+  *rh = ic.h;
+  return ic.pos;
 }
 
 
@@ -376,8 +406,8 @@ static void
 history_cb (void *cls,
             const struct TALER_BANK_CreditHistoryResponse *chr)
 {
-  struct TALER_TESTING_Interpreter *is = cls;
-  struct HistoryState *hs = is->commands[is->ip].cls;
+  struct HistoryState *hs = cls;
+  struct TALER_TESTING_Interpreter *is = hs->is;
 
   hs->hh = NULL;
   switch (chr->http_status)
@@ -470,6 +500,7 @@ history_run (void *cls,
   const uint64_t *row_ptr;
 
   (void) cmd;
+  hs->is = is;
   /* Get row_id from trait. */
   if (NULL != hs->start_row_reference)
   {
@@ -490,15 +521,16 @@ history_run (void *cls,
     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
                      (unsigned long long) row_id);
   }
-  hs->total = build_history (is,
+  hs->total = build_history (hs,
                              &hs->h);
-  hs->hh = TALER_BANK_credit_history (is->ctx,
-                                      &hs->auth,
-                                      row_id,
-                                      hs->num_results,
-                                      GNUNET_TIME_UNIT_ZERO,
-                                      &history_cb,
-                                      is);
+  hs->hh = TALER_BANK_credit_history (
+    TALER_TESTING_interpreter_get_context (is),
+    &hs->auth,
+    row_id,
+    hs->num_results,
+    GNUNET_TIME_UNIT_ZERO,
+    &history_cb,
+    hs);
   GNUNET_assert (NULL != hs->hh);
 }
 
@@ -519,7 +551,8 @@ history_cleanup (void *cls,
   (void) cmd;
   if (NULL != hs->hh)
   {
-    TALER_LOG_WARNING ("/history/incoming did not complete\n");
+    TALER_TESTING_command_incomplete (hs->is,
+                                      cmd->label);
     TALER_BANK_credit_history_cancel (hs->hh);
   }
   GNUNET_free (hs->account_url);
diff --git a/src/testing/testing_api_cmd_bank_history_debit.c 
b/src/testing/testing_api_cmd_bank_history_debit.c
index fd1e8199..538538b6 100644
--- a/src/testing/testing_api_cmd_bank_history_debit.c
+++ b/src/testing/testing_api_cmd_bank_history_debit.c
@@ -91,6 +91,11 @@ struct HistoryState
    */
   struct TALER_BANK_DebitHistoryHandle *hh;
 
+  /**
+   * Our interpreter.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
   /**
    * Expected number of results (= rows).
    */
@@ -146,6 +151,131 @@ print_expected (struct History *h,
 }
 
 
+/**
+ * Closure for command_cb().
+ */
+struct IteratorContext
+{
+  /**
+   * Array of history items to return.
+   */
+  struct History *h;
+
+  /**
+   * Set to the row ID from where on we should actually process history items,
+   * or NULL if we should process all of them.
+   */
+  const uint64_t *row_id_start;
+
+  /**
+   * History state we are working on.
+   */
+  struct HistoryState *hs;
+
+  /**
+   * Current length of the @e h array.
+   */
+  unsigned int total;
+
+  /**
+   * Current write position in @e h array.
+   */
+  unsigned int pos;
+
+  /**
+   * Ok equals True whenever a starting row_id was provided AND was found
+   * among the CMDs, OR no starting row was given in the first place.
+   */
+  bool ok;
+
+};
+
+
+/**
+ * Helper function of build_history() that expands
+ * the history for each relevant command encountered.
+ *
+ * @param[in,out] cls our `struct IteratorContext`
+ * @param cmd a command to process
+ */
+static void
+command_cb (void *cls,
+            const struct TALER_TESTING_Command *cmd)
+{
+  struct IteratorContext *ic = cls;
+  struct HistoryState *hs = ic->hs;
+
+  const uint64_t *row_id;
+  const char *debit_account;
+  const char *credit_account;
+  const struct TALER_Amount *amount;
+  const struct TALER_WireTransferIdentifierRawP *wtid;
+  const char *exchange_base_url;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Checking if command %s is relevant for debit history\n",
+              cmd->label);
+  if ( (GNUNET_OK !=
+        TALER_TESTING_get_trait_bank_row (cmd,
+                                          &row_id)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_debit_payto_uri (cmd,
+                                                 &debit_account)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_credit_payto_uri (cmd,
+                                                  &credit_account)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_amount (cmd,
+                                        &amount)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_wtid (cmd,
+                                      &wtid)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_exchange_url (cmd,
+                                              &exchange_base_url)) )
+    return;   /* not an event we care about */
+  /* Seek "/history/outgoing" starting row.  */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Command %s is relevant for debit history!\n",
+              cmd->label);
+  if ( (NULL != ic->row_id_start) &&
+       (*(ic->row_id_start) == *row_id) &&
+       (! ic->ok) )
+  {
+    /* Until here, nothing counted. */
+    ic->ok = true;
+    return;
+  }
+  /* when 'start' was _not_ given, then ok == GNUNET_YES */
+  if (! ic->ok)
+    return;   /* skip until we find the marker */
+  if (ic->total >= GNUNET_MAX (hs->num_results,
+                               -hs->num_results) )
+  {
+    TALER_LOG_DEBUG ("Hit history limit\n");
+    return;
+  }
+  TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
+                  debit_account,
+                  credit_account,
+                  hs->account_url);
+  /* found matching record, make sure we have room */
+  if (ic->pos == ic->total)
+    GNUNET_array_grow (ic->h,
+                       ic->total,
+                       ic->pos * 2);
+  ic->h[ic->pos].c_url = GNUNET_strdup (credit_account);
+  ic->h[ic->pos].d_url = GNUNET_strdup (debit_account);
+  ic->h[ic->pos].details.credit_account_uri = ic->h[ic->pos].c_url;
+  ic->h[ic->pos].details.debit_account_uri = ic->h[ic->pos].d_url;
+  ic->h[ic->pos].details.amount = *amount;
+  ic->h[ic->pos].row_id = *row_id;
+  ic->h[ic->pos].details.wtid = *wtid;
+  ic->h[ic->pos].details.exchange_base_url = exchange_base_url;
+  ic->pos++;
+}
+
+
 /**
  * This function constructs the list of history elements that
  * interest the account number of the caller.  It has two main
@@ -153,32 +283,25 @@ print_expected (struct History *h,
  * to be allocated, and the second to actually populate every
  * element.
  *
- * @param is interpreter state (supposedly having the
- *        current CMD pointing at a "history" CMD).
+ * @param hs history state command context
  * @param[out] rh history array to initialize.
  * @return number of entries in @a rh.
  */
 static unsigned int
-build_history (struct TALER_TESTING_Interpreter *is,
+build_history (struct HistoryState *hs,
                struct History **rh)
 {
-  struct HistoryState *hs = is->commands[is->ip].cls;
-  unsigned int total;
-  unsigned int pos;
-  struct History *h;
-  const struct TALER_TESTING_Command *add_incoming_cmd;
-  int inc;
-  int start;
-  int end;
-  /* #GNUNET_YES whenever either no 'start' value was given for the history
-   * query, or the given value is found in the list of all the CMDs. */
-  int ok;
-  const uint64_t *row_id_start = NULL;
+  struct TALER_TESTING_Interpreter *is = hs->is;
+  struct IteratorContext ic = {
+    .hs = hs
+  };
 
   if (NULL != hs->start_row_reference)
   {
-    TALER_LOG_INFO
-      ("`%s': start row given via reference `%s'\n",
+    const struct TALER_TESTING_Command *add_incoming_cmd;
+
+    TALER_LOG_INFO (
+      "`%s': start row given via reference `%s'\n",
       TALER_TESTING_interpreter_get_current_label  (is),
       hs->start_row_reference);
     add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -187,124 +310,28 @@ build_history (struct TALER_TESTING_Interpreter *is,
     GNUNET_assert (NULL != add_incoming_cmd);
     GNUNET_assert (GNUNET_OK ==
                    TALER_TESTING_get_trait_row (add_incoming_cmd,
-                                                &row_id_start));
+                                                &ic.row_id_start));
   }
 
-  GNUNET_assert (0 != hs->num_results);
-  if (0 == is->ip)
-  {
-    TALER_LOG_DEBUG ("Checking history at first CMD..\n");
-    *rh = NULL;
-    return 0;
-  }
-
-  /* AKA 'delta' */
-  if (hs->num_results > 0)
-  {
-    inc = 1;  /* _inc_rement: go forwards */
-    start = 0;
-    end = is->ip;
-  }
-  else
-  {
-    inc = -1; /* decrement: we go backwards */
-    start = is->ip - 1;
-    end = -1; /* range is exclusive, do look at 0! */
-  }
-
-  ok = GNUNET_NO;
-  if (NULL == row_id_start)
-    ok = GNUNET_YES;
-  h = NULL;
-  total = 0;
-  GNUNET_array_grow (h,
-                     total,
+  ic.ok = false;
+  if (NULL == ic.row_id_start)
+    ic.ok = true;
+  GNUNET_array_grow (ic.h,
+                     ic.total,
                      4);
-  pos = 0;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Checking commands %u to %u for debit history\n",
-              start,
-              end);
-  for (int off = start; off != end; off += inc)
-  {
-    const struct TALER_TESTING_Command *cmd = &is->commands[off];
-    const uint64_t *row_id;
-    const char **debit_account;
-    const char **credit_account;
-    const struct TALER_Amount *amount;
-    const struct TALER_WireTransferIdentifierRawP *wtid;
-    const char **exchange_base_url;
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Checking if command %s is relevant for debit history\n",
-                cmd->label);
-    if ( (GNUNET_OK !=
-          TALER_TESTING_get_trait_bank_row (cmd,
-                                            &row_id)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_debit_payto_uri (cmd,
-                                                   &debit_account)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_credit_payto_uri (cmd,
-                                                    &credit_account)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_amount (cmd,
-                                          &amount)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_wtid (cmd,
-                                        &wtid)) ||
-         (GNUNET_OK !=
-          TALER_TESTING_get_trait_exchange_url (cmd,
-                                                &exchange_base_url)) )
-      continue; /* not an event we care about */
-    /* Seek "/history/outgoing" starting row.  */
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Command %s is relevant for debit history!\n",
-                cmd->label);
-    if ( (NULL != row_id_start) &&
-         (*row_id_start == *row_id) &&
-         (GNUNET_NO == ok) )
-    {
-      /* Until here, nothing counted. */
-      ok = GNUNET_YES;
-      continue;
-    }
-    /* when 'start' was _not_ given, then ok == GNUNET_YES */
-    if (GNUNET_NO == ok)
-      continue; /* skip until we find the marker */
-    if (total >= GNUNET_MAX (hs->num_results,
-                             -hs->num_results) )
-    {
-      TALER_LOG_DEBUG ("Hit history limit\n");
-      break;
-    }
-    TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
-                    *debit_account,
-                    *credit_account,
-                    hs->account_url);
-    /* found matching record, make sure we have room */
-    if (pos == total)
-      GNUNET_array_grow (h,
-                         total,
-                         pos * 2);
-    h[pos].c_url = GNUNET_strdup (*credit_account);
-    h[pos].d_url = GNUNET_strdup (*debit_account);
-    h[pos].details.credit_account_uri = h[pos].c_url;
-    h[pos].details.debit_account_uri = h[pos].d_url;
-    h[pos].details.amount = *amount;
-    h[pos].row_id = *row_id;
-    h[pos].details.wtid = *wtid;
-    h[pos].details.exchange_base_url = *exchange_base_url;
-    pos++;
-  }
-  GNUNET_assert (GNUNET_YES == ok);
-  GNUNET_array_grow (h,
-                     total,
-                     pos);
-  if (0 == pos)
-    TALER_LOG_DEBUG ("Empty debit history computed\n");
-  *rh = h;
-  return total;
+  GNUNET_assert (0 != hs->num_results);
+  TALER_TESTING_iterate (is,
+                         hs->num_results > 0,
+                         &command_cb,
+                         &ic);
+  GNUNET_assert (ic.ok);
+  GNUNET_array_grow (ic.h,
+                     ic.total,
+                     ic.pos);
+  if (0 == ic.pos)
+    TALER_LOG_DEBUG ("Empty credit history computed\n");
+  *rh = ic.h;
+  return ic.pos;
 }
 
 
@@ -368,8 +395,8 @@ static void
 history_cb (void *cls,
             const struct TALER_BANK_DebitHistoryResponse *dhr)
 {
-  struct TALER_TESTING_Interpreter *is = cls;
-  struct HistoryState *hs = is->commands[is->ip].cls;
+  struct HistoryState *hs = cls;
+  struct TALER_TESTING_Interpreter *is = hs->is;
 
   hs->hh = NULL;
   switch (dhr->http_status)
@@ -462,6 +489,7 @@ history_run (void *cls,
   const uint64_t *row_ptr;
 
   (void) cmd;
+  hs->is = is;
   /* Get row_id from trait. */
   if (NULL != hs->start_row_reference)
   {
@@ -482,14 +510,16 @@ history_run (void *cls,
     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
                      (unsigned long long) row_id);
   }
-  hs->total = build_history (is, &hs->h);
-  hs->hh = TALER_BANK_debit_history (is->ctx,
-                                     &hs->auth,
-                                     row_id,
-                                     hs->num_results,
-                                     GNUNET_TIME_UNIT_ZERO,
-                                     &history_cb,
-                                     is);
+  hs->total = build_history (hs,
+                             &hs->h);
+  hs->hh = TALER_BANK_debit_history (
+    TALER_TESTING_interpreter_get_context (is),
+    &hs->auth,
+    row_id,
+    hs->num_results,
+    GNUNET_TIME_UNIT_ZERO,
+    &history_cb,
+    hs);
   GNUNET_assert (NULL != hs->hh);
 }
 
@@ -510,7 +540,8 @@ history_cleanup (void *cls,
   (void) cmd;
   if (NULL != hs->hh)
   {
-    TALER_LOG_WARNING ("/history/outgoing did not complete\n");
+    TALER_TESTING_command_incomplete (hs->is,
+                                      cmd->label);
     TALER_BANK_debit_history_cancel (hs->hh);
   }
   for (unsigned int off = 0; off<hs->total; off++)
diff --git a/src/testing/testing_api_cmd_bank_transfer.c 
b/src/testing/testing_api_cmd_bank_transfer.c
index d4477645..7b9d588c 100644
--- a/src/testing/testing_api_cmd_bank_transfer.c
+++ b/src/testing/testing_api_cmd_bank_transfer.c
@@ -149,8 +149,7 @@ do_retry (void *cls)
   struct TransferState *fts = cls;
 
   fts->retry_task = NULL;
-  fts->is->commands[fts->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (fts->is);
   transfer_run (fts,
                 NULL,
                 fts->is);
@@ -191,7 +190,7 @@ confirmation_cb (void *cls,
           fts->backoff = GNUNET_TIME_UNIT_ZERO;
         else
           fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff);
-        fts->is->commands[fts->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (fts->is);
         fts->retry_task
           = GNUNET_SCHEDULER_add_delayed (fts->backoff,
                                           &do_retry,
@@ -199,12 +198,8 @@ confirmation_cb (void *cls,
         return;
       }
     }
-    GNUNET_break (0);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Bank returned HTTP status %u/%d\n",
-                tr->http_status,
-                (int) tr->ec);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     tr->http_status);
     return;
   }
 
@@ -276,9 +271,8 @@ transfer_cleanup (void *cls,
 
   if (NULL != fts->weh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %s did not complete\n",
-                cmd->label);
+    TALER_TESTING_command_incomplete (fts->is,
+                                      cmd->label);
     TALER_BANK_transfer_cancel (fts->weh);
     fts->weh = NULL;
   }
@@ -311,12 +305,12 @@ transfer_traits (void *cls,
   struct TransferState *fts = cls;
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_exchange_url (
-      (const char **) &fts->exchange_base_url),
+      fts->exchange_base_url),
     TALER_TESTING_make_trait_bank_row (&fts->serial_id),
     TALER_TESTING_make_trait_credit_payto_uri (
-      (const char **) &fts->payto_credit_account),
+      fts->payto_credit_account),
     TALER_TESTING_make_trait_debit_payto_uri (
-      (const char **) &fts->payto_debit_account),
+      fts->payto_debit_account),
     TALER_TESTING_make_trait_amount (&fts->amount),
     TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
     TALER_TESTING_make_trait_wtid (&fts->wtid),
diff --git a/src/testing/testing_api_cmd_batch.c 
b/src/testing/testing_api_cmd_batch.c
index d81a7767..bd6454a5 100644
--- a/src/testing/testing_api_cmd_batch.c
+++ b/src/testing/testing_api_cmd_batch.c
@@ -37,6 +37,11 @@ struct BatchState
    */
   struct TALER_TESTING_Command *batch;
 
+  /**
+   * My command (the batch command).
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Internal command pointer.
    */
@@ -58,6 +63,7 @@ batch_run (void *cls,
 {
   struct BatchState *bs = cls;
 
+  bs->cmd = cmd;
   if (NULL != bs->batch[bs->batch_ip].label)
     TALER_LOG_INFO ("Running batched command: %s\n",
                     bs->batch[bs->batch_ip].label);
@@ -167,19 +173,33 @@ TALER_TESTING_cmd_batch (const char *label,
 }
 
 
-void
-TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is)
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+                              void *cls)
 {
-  struct BatchState *bs = is->commands[is->ip].cls;
+  struct BatchState *bs = cls;
+  struct TALER_TESTING_Command *bcmd = &bs->batch[bs->batch_ip];
 
-  if (NULL == bs->batch[bs->batch_ip].label)
+  if (NULL == bcmd->label)
   {
-    is->commands[is->ip].finish_time = GNUNET_TIME_absolute_get ();
-    is->ip++;
-    return;
+    /* This batch is done */
+    return true;
+  }
+  if (TALER_TESTING_cmd_is_batch (bcmd))
+  {
+    if (TALER_TESTING_cmd_batch_next (is,
+                                      bcmd->cls))
+    {
+      /* sub-batch is done */
+      bcmd->finish_time = GNUNET_TIME_absolute_get ();
+      bs->batch_ip++;
+      return false;
+    }
   }
-  bs->batch[bs->batch_ip].finish_time = GNUNET_TIME_absolute_get ();
+  /* Simple command is done */
+  bcmd->finish_time = GNUNET_TIME_absolute_get ();
   bs->batch_ip++;
+  return false;
 }
 
 
diff --git a/src/testing/testing_api_cmd_batch_deposit.c 
b/src/testing/testing_api_cmd_batch_deposit.c
index d3275875..6a05071a 100644
--- a/src/testing/testing_api_cmd_batch_deposit.c
+++ b/src/testing/testing_api_cmd_batch_deposit.c
@@ -198,16 +198,8 @@ batch_deposit_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                JSON_INDENT (2));
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   if (MHD_HTTP_OK == dr->hr.http_status)
@@ -259,8 +251,12 @@ batch_deposit_run (void *cls,
                                  &wire_salt),
     GNUNET_JSON_spec_end ()
   };
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   memset (cdds,
           0,
           sizeof (cdds));
@@ -387,7 +383,7 @@ batch_deposit_run (void *cls,
       .refund_deadline = ds->refund_deadline
     };
 
-    ds->dh = TALER_EXCHANGE_batch_deposit (is->exchange,
+    ds->dh = TALER_EXCHANGE_batch_deposit (exchange,
                                            &dcd,
                                            ds->num_coins,
                                            cdds,
@@ -422,10 +418,8 @@ batch_deposit_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_batch_withdraw.c 
b/src/testing/testing_api_cmd_batch_withdraw.c
index 2aa549c0..e0b8285a 100644
--- a/src/testing/testing_api_cmd_batch_withdraw.c
+++ b/src/testing/testing_api_cmd_batch_withdraw.c
@@ -189,18 +189,8 @@ reserve_batch_withdraw_cb (void *cls,
   ws->wsh = NULL;
   if (ws->expected_response_code != wr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                wr->hr.http_status,
-                (int) wr->hr.ec,
-                TALER_TESTING_interpreter_get_current_label (is),
-                __FILE__,
-                __LINE__);
-    json_dumpf (wr->hr.reply,
-                stderr,
-                0);
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     wr->hr.http_status);
     return;
   }
   switch (wr->hr.http_status)
@@ -263,8 +253,12 @@ batch_withdraw_run (void *cls,
   const struct TALER_TESTING_Command *create_reserve;
   const struct TALER_EXCHANGE_DenomPublicKey *dpk;
   struct TALER_EXCHANGE_WithdrawCoinInput wcis[ws->num_coins];
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ws->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (
@@ -287,7 +281,7 @@ batch_withdraw_run (void *cls,
   }
   if (NULL == ws->exchange_url)
     ws->exchange_url
-      = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
+      = GNUNET_strdup (TALER_EXCHANGE_get_base_url (exchange));
   ws->reserve_priv = *rp;
   GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
                                       &ws->reserve_pub.eddsa_pub);
@@ -301,7 +295,7 @@ batch_withdraw_run (void *cls,
     struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
 
     TALER_planchet_master_setup_random (&cs->ps);
-    dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
+    dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (exchange),
                                  &cs->amount,
                                  ws->age > 0);
     if (NULL == dpk)
@@ -327,7 +321,7 @@ batch_withdraw_run (void *cls,
     wci->ps = &cs->ps;
     wci->ach = cs->h_age_commitment;
   }
-  ws->wsh = TALER_EXCHANGE_batch_withdraw (is->exchange,
+  ws->wsh = TALER_EXCHANGE_batch_withdraw (exchange,
                                            rp,
                                            wcis,
                                            ws->num_coins,
@@ -357,9 +351,8 @@ batch_withdraw_cleanup (void *cls,
 
   if (NULL != ws->wsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %s did not complete\n",
-                cmd->label);
+    TALER_TESTING_command_incomplete (ws->is,
+                                      cmd->label);
     TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
     ws->wsh = NULL;
   }
@@ -427,12 +420,9 @@ batch_withdraw_traits (void *cls,
     TALER_TESTING_make_trait_amounts (index,
                                       &cs->amount),
     TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
-    TALER_TESTING_make_trait_h_payto (
-      &ws->h_payto),
-    TALER_TESTING_make_trait_payto_uri (
-      (const char **) &ws->reserve_payto_uri),
-    TALER_TESTING_make_trait_exchange_url (
-      (const char **) &ws->exchange_url),
+    TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+    TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+    TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
     TALER_TESTING_make_trait_age_commitment_proof (index,
                                                    cs->age_commitment_proof),
     TALER_TESTING_make_trait_h_age_commitment (index,
diff --git a/src/testing/testing_api_cmd_change_auth.c 
b/src/testing/testing_api_cmd_change_auth.c
deleted file mode 100644
index c3a60a1d..00000000
--- a/src/testing/testing_api_cmd_change_auth.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2021 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 testing/testing_api_cmd_change_auth.c
- * @brief command(s) to change CURL context authorization header
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "authchange" CMD.
- */
-struct AuthchangeState
-{
-
-  /**
-   * What is the new authorization token to send?
-   */
-  const char *auth_token;
-
-  /**
-   * Old context, clean up on termination.
-   */
-  struct GNUNET_CURL_Context *old_ctx;
-};
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-authchange_run (void *cls,
-                const struct TALER_TESTING_Command *cmd,
-                struct TALER_TESTING_Interpreter *is)
-{
-  struct AuthchangeState *ss = cls;
-
-  (void) cmd;
-  ss->old_ctx = is->ctx;
-  if (NULL != is->rc)
-  {
-    GNUNET_CURL_gnunet_rc_destroy (is->rc);
-    is->rc = NULL;
-  }
-  is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
-                              &is->rc);
-  GNUNET_CURL_enable_async_scope_header (is->ctx,
-                                         "Taler-Correlation-Id");
-  GNUNET_assert (NULL != is->ctx);
-  is->rc = GNUNET_CURL_gnunet_rc_create (is->ctx);
-  if (NULL != ss->auth_token)
-  {
-    char *authorization;
-
-    GNUNET_asprintf (&authorization,
-                     "%s: %s",
-                     MHD_HTTP_HEADER_AUTHORIZATION,
-                     ss->auth_token);
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CURL_append_header (is->ctx,
-                                              authorization));
-    GNUNET_free (authorization);
-  }
-  TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Call GNUNET_CURL_fini(). Done as a separate task to
- * ensure that all of the command's cleanups have been
- * executed first.  See #7151.
- *
- * @param cls a `struct GNUNET_CURL_Context *` to clean up.
- */
-static void
-deferred_cleanup_cb (void *cls)
-{
-  struct GNUNET_CURL_Context *ctx = cls;
-
-  GNUNET_CURL_fini (ctx);
-}
-
-
-/**
- * Cleanup the state from a "authchange" CMD.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-authchange_cleanup (void *cls,
-                    const struct TALER_TESTING_Command *cmd)
-{
-  struct AuthchangeState *ss = cls;
-
-  (void) cmd;
-  if (NULL != ss->old_ctx)
-  {
-    (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
-                                     ss->old_ctx);
-    ss->old_ctx = NULL;
-  }
-  GNUNET_free (ss);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_set_authorization (const char *label,
-                                     const char *auth_token)
-{
-  struct AuthchangeState *ss;
-
-  ss = GNUNET_new (struct AuthchangeState);
-  ss->auth_token = auth_token;
-
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = ss,
-      .label = label,
-      .run = &authchange_run,
-      .cleanup = &authchange_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_change_auth.c  */
diff --git a/src/testing/testing_api_cmd_check_aml_decision.c 
b/src/testing/testing_api_cmd_check_aml_decision.c
index dd317142..1f0ada7b 100644
--- a/src/testing/testing_api_cmd_check_aml_decision.c
+++ b/src/testing/testing_api_cmd_check_aml_decision.c
@@ -79,22 +79,14 @@ check_aml_decision_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_http_status != adr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                adr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (adr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     adr->hr.http_status);
     return;
   }
   if (MHD_HTTP_OK == adr->hr.http_status)
   {
     const struct TALER_TESTING_Command *ref;
-    const char **justification;
+    const char *justification;
     enum TALER_AmlDecisionState *new_state;
     const struct TALER_Amount *amount;
     const struct TALER_EXCHANGE_AmlDecisionDetail *oldest = NULL;
@@ -138,7 +130,7 @@ check_aml_decision_cb (void *cls,
     }
     if ( (oldest->new_state != *new_state) ||
          (0 != strcmp (oldest->justification,
-                       *justification) ) )
+                       justification) ) )
     {
       GNUNET_break (0);
       TALER_TESTING_interpreter_fail (ds->is);
@@ -165,9 +157,26 @@ check_aml_decision_run (void *cls,
   const struct TALER_PaytoHashP *h_payto;
   const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
   const struct TALER_TESTING_Command *ref;
+  const char *exchange_url;
 
   (void) cmd;
   ds->is = is;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
+
   ref = TALER_TESTING_interpreter_lookup_command (is,
                                                   ds->ref_operation);
   if (NULL == ref)
@@ -191,8 +200,8 @@ check_aml_decision_run (void *cls,
                  TALER_TESTING_get_trait_officer_priv (ref,
                                                        &officer_priv));
   ds->dh = TALER_EXCHANGE_lookup_aml_decision (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     h_payto,
     officer_priv,
     true, /* history */
@@ -222,10 +231,8 @@ check_aml_decision_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_lookup_aml_decision_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_check_aml_decisions.c 
b/src/testing/testing_api_cmd_check_aml_decisions.c
index 92c21e54..4d206485 100644
--- a/src/testing/testing_api_cmd_check_aml_decisions.c
+++ b/src/testing/testing_api_cmd_check_aml_decisions.c
@@ -79,16 +79,8 @@ check_aml_decisions_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_http_status != adr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                adr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (adr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     adr->hr.http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -110,9 +102,25 @@ check_aml_decisions_run (void *cls,
   struct AmlCheckState *ds = cls;
   const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
   const struct TALER_TESTING_Command *ref;
+  const char *exchange_url;
 
   (void) cmd;
   ds->is = is;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   ref = TALER_TESTING_interpreter_lookup_command (is,
                                                   ds->ref_officer);
   if (NULL == ref)
@@ -125,8 +133,8 @@ check_aml_decisions_run (void *cls,
                  TALER_TESTING_get_trait_officer_priv (ref,
                                                        &officer_priv));
   ds->dh = TALER_EXCHANGE_lookup_aml_decisions (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     INT64_MAX,
     -1, /* return last one for testing */
     ds->filter,
@@ -157,10 +165,8 @@ check_aml_decisions_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_lookup_aml_decisions_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_check_keys.c 
b/src/testing/testing_api_cmd_check_keys.c
index 0dee8be3..f4ea126e 100644
--- a/src/testing/testing_api_cmd_check_keys.c
+++ b/src/testing/testing_api_cmd_check_keys.c
@@ -33,17 +33,10 @@
  */
 struct CheckKeysState
 {
-  /**
-   * This number will instruct the CMD interpreter to
-   * make sure that /keys was downloaded `generation` times
-   * _before_ running the very CMD logic.
-   */
-  unsigned int generation;
 
   /**
-   * If this value is true, then the "cherry
-   * picking" facility is turned off; whole /keys is
-   * downloaded.
+   * If this value is true, then the "cherry picking" facility is turned off;
+   * whole /keys is downloaded.
    */
   bool pull_all_keys;
 
@@ -52,6 +45,11 @@ struct CheckKeysState
    */
   const char *last_denom_date_ref;
 
+  /**
+   * Our interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
   /**
    * Last denomination date we received when doing this request.
    */
@@ -59,6 +57,30 @@ struct CheckKeysState
 };
 
 
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ *
+ * @param cls closure
+ * @param kr response from /keys
+ */
+static void
+keys_cb (void *cls,
+         const struct TALER_EXCHANGE_KeysResponse *kr)
+{
+  struct CheckKeysState *cks = cls;
+
+  if (MHD_HTTP_OK != kr->hr.http_status)
+  {
+    TALER_TESTING_unexpected_status (cks->is,
+                                     kr->hr.http_status);
+    return;
+  }
+  cks->my_denom_date = kr->details.ok.keys->last_denom_issue_date;
+  TALER_TESTING_interpreter_next (cks->is);
+}
+
+
 /**
  * Run the "check keys" command.
  *
@@ -72,80 +94,64 @@ check_keys_run (void *cls,
                 struct TALER_TESTING_Interpreter *is)
 {
   struct CheckKeysState *cks = cls;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
+  struct GNUNET_TIME_Timestamp rdate;
 
+  (void) cmd;
+  cks->is = is;
+  if (NULL == exchange)
+    return;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "cmd `%s' (ip: %u), key generation: %d\n",
-              cmd->label,
-              is->ip,
-              is->key_generation);
-  if (is->key_generation < cks->generation)
+              "Triggering GET /keys, cmd `%s'\n",
+              cmd->label);
+  if (NULL != cks->last_denom_date_ref)
   {
-    struct GNUNET_TIME_Timestamp rdate;
-
-    is->working = GNUNET_NO;
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Triggering GET /keys, cmd `%s'\n",
-                cmd->label);
-    if (NULL != cks->last_denom_date_ref)
+    if (0 == strcmp ("zero",
+                     cks->last_denom_date_ref))
     {
-      if (0 == strcmp ("zero",
-                       cks->last_denom_date_ref))
+      TALER_LOG_DEBUG ("Forcing last_denom_date URL argument set to zero\n");
+      TALER_EXCHANGE_set_last_denom (exchange,
+                                     GNUNET_TIME_UNIT_ZERO_TS);
+    }
+    else
+    {
+      const struct GNUNET_TIME_Timestamp *last_denom_date;
+      const struct TALER_TESTING_Command *ref;
+
+      ref = TALER_TESTING_interpreter_lookup_command (is,
+                                                      
cks->last_denom_date_ref);
+      if (NULL == ref)
       {
-        TALER_LOG_DEBUG ("Forcing last_denom_date URL argument set to zero\n");
-        TALER_EXCHANGE_set_last_denom (is->exchange,
-                                       GNUNET_TIME_UNIT_ZERO_TS);
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
       }
-      else
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_timestamp (ref,
+                                             0,
+                                             &last_denom_date))
       {
-        const struct GNUNET_TIME_Timestamp *last_denom_date;
-        const struct TALER_TESTING_Command *ref;
-
-        ref = TALER_TESTING_interpreter_lookup_command (is,
-                                                        
cks->last_denom_date_ref);
-        if (NULL == ref)
-        {
-          GNUNET_break (0);
-          TALER_TESTING_interpreter_fail (is);
-          return;
-        }
-        if (GNUNET_OK !=
-            TALER_TESTING_get_trait_timestamp (ref,
-                                               0,
-                                               &last_denom_date))
-        {
-          GNUNET_break (0);
-          TALER_TESTING_interpreter_fail (is);
-          return;
-        }
-
-        TALER_LOG_DEBUG ("Forcing last_denom_date URL argument\n");
-        TALER_EXCHANGE_set_last_denom (is->exchange,
-                                       *last_denom_date);
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
       }
-    }
 
-    rdate = TALER_EXCHANGE_check_keys_current (
-      is->exchange,
-      cks->pull_all_keys
-      ? TALER_EXCHANGE_CKF_FORCE_ALL_NOW
-      : TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
-    /* Redownload /keys.  */
-    GNUNET_break (GNUNET_TIME_absolute_is_zero (rdate.abs_time));
-    return;
-  }
-  {
-    const struct TALER_EXCHANGE_Keys *keys;
-
-    keys = TALER_EXCHANGE_get_keys (is->exchange);
-    if (NULL == keys)
-    {
-      GNUNET_break (0);
-      TALER_TESTING_interpreter_fail (is);
-      return;
+      TALER_LOG_DEBUG ("Forcing last_denom_date URL argument\n");
+      TALER_EXCHANGE_set_last_denom (exchange,
+                                     *last_denom_date);
     }
-    cks->my_denom_date = keys->last_denom_issue_date;
   }
-  TALER_TESTING_interpreter_next (is);
+
+  rdate = TALER_EXCHANGE_check_keys_current (
+    exchange,
+    cks->pull_all_keys
+      ? TALER_EXCHANGE_CKF_FORCE_ALL_NOW
+    : TALER_EXCHANGE_CKF_FORCE_DOWNLOAD,
+    &keys_cb,
+    cks);
+  /* Redownload /keys.  */
+  GNUNET_break (GNUNET_TIME_absolute_is_zero (rdate.abs_time));
 }
 
 
@@ -176,7 +182,7 @@ check_keys_cleanup (void *cls,
  * @param index index number of the object to offer.
  * @return #GNUNET_OK on success
  */
-static int
+static enum GNUNET_GenericReturnValue
 check_keys_traits (void *cls,
                    const void **ret,
                    const char *trait,
@@ -184,7 +190,6 @@ check_keys_traits (void *cls,
 {
   struct CheckKeysState *cks = cls;
   struct TALER_TESTING_Trait traits[] = {
-    /* history entry MUST be first due to response code logic below! */
     TALER_TESTING_make_trait_timestamp (0,
                                         &cks->my_denom_date),
     TALER_TESTING_trait_end ()
@@ -198,13 +203,11 @@ check_keys_traits (void *cls,
 
 
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
-                              unsigned int generation)
+TALER_TESTING_cmd_check_keys (const char *label)
 {
   struct CheckKeysState *cks;
 
   cks = GNUNET_new (struct CheckKeysState);
-  cks->generation = generation;
   {
     struct TALER_TESTING_Command cmd = {
       .cls = cks,
@@ -220,12 +223,10 @@ TALER_TESTING_cmd_check_keys (const char *label,
 
 
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
-                                            unsigned int generation)
+TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label)
 {
   struct TALER_TESTING_Command cmd
-    = TALER_TESTING_cmd_check_keys (label,
-                                    generation);
+    = TALER_TESTING_cmd_check_keys (label);
   struct CheckKeysState *cks = cmd.cls;
 
   cks->pull_all_keys = true;
@@ -236,13 +237,12 @@ TALER_TESTING_cmd_check_keys_pull_all_keys (const char 
*label,
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_check_keys_with_last_denom (
   const char *label,
-  unsigned int generation,
   const char *last_denom_date_ref)
 {
   struct TALER_TESTING_Command cmd
-    = TALER_TESTING_cmd_check_keys (label,
-                                    generation);
+    = TALER_TESTING_cmd_check_keys (label);
   struct CheckKeysState *cks = cmd.cls;
+
   cks->last_denom_date_ref = last_denom_date_ref;
   return cmd;
 }
diff --git a/src/testing/testing_api_cmd_connect_with_state.c 
b/src/testing/testing_api_cmd_connect_with_state.c
new file mode 100644
index 00000000..95e860f1
--- /dev/null
+++ b/src/testing/testing_api_cmd_connect_with_state.c
@@ -0,0 +1,191 @@
+/*
+  This file is part of TALER
+  (C) 2018-2023 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 testing/testing_api_cmd_connect_with_state.c
+ * @brief Lets tests use the keys deserialization API.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * Internal state for a connect-with-state CMD.
+ */
+struct ConnectWithStateState
+{
+
+  /**
+   * Reference to a CMD that offers a serialized key-state
+   * that will be used in the reconnection.
+   */
+  const char *state_reference;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * New exchange handle.
+   */
+  struct TALER_EXCHANGE_Handle *exchange;
+};
+
+
+static void
+cert_cb (void *cls,
+         const struct TALER_EXCHANGE_KeysResponse *kr)
+{
+  struct ConnectWithStateState *cwss = cls;
+  struct TALER_TESTING_Interpreter *is = cwss->is;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr;
+
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    /* dealt with below */
+    break;
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Got failure response %u/%d for /keys!\n",
+                hr->http_status,
+                (int) hr->ec);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got %d DK from /keys\n",
+              kr->details.ok.keys->num_denom_keys);
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+connect_with_state_run (void *cls,
+                        const struct TALER_TESTING_Command *cmd,
+                        struct TALER_TESTING_Interpreter *is)
+{
+  struct ConnectWithStateState *cwss = cls;
+  const struct TALER_TESTING_Command *state_cmd;
+  const json_t *serialized_keys;
+  const char *exchange_url;
+
+  cwss->is = is;
+  state_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                        cwss->state_reference);
+  if (NULL == state_cmd)
+  {
+    /* Command providing serialized keys not found.  */
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_keys (state_cmd,
+                                                        &serialized_keys));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_url (state_cmd,
+                                                       &exchange_url));
+  cwss->exchange
+    = TALER_EXCHANGE_connect (
+        TALER_TESTING_interpreter_get_context (is),
+        exchange_url,
+        &cert_cb,
+        cwss,
+        TALER_EXCHANGE_OPTION_DATA,
+        serialized_keys,
+        TALER_EXCHANGE_OPTION_END);
+}
+
+
+/**
+ * Offer exchange connection as trait.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+connect_with_state_traits (void *cls,
+                           const void **ret,
+                           const char *trait,
+                           unsigned int index)
+{
+  struct ConnectWithStateState *cwss = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_exchange (cwss->exchange),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Cleanup the state of a "connect with state" CMD.  Just
+ * a placeholder to avoid jumping on an invalid address.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+connect_with_state_cleanup (void *cls,
+                            const struct TALER_TESTING_Command *cmd)
+{
+  struct ConnectWithStateState *cwss = cls;
+
+  GNUNET_free (cwss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_connect_with_state (const char *label,
+                                      const char *state_reference)
+{
+  struct ConnectWithStateState *cwss;
+
+  cwss = GNUNET_new (struct ConnectWithStateState);
+  cwss->state_reference = state_reference;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = cwss,
+      .label = label,
+      .run = connect_with_state_run,
+      .cleanup = connect_with_state_cleanup,
+      .traits = connect_with_state_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_contract_get.c 
b/src/testing/testing_api_cmd_contract_get.c
index d599cb59..adde3ed2 100644
--- a/src/testing/testing_api_cmd_contract_get.c
+++ b/src/testing/testing_api_cmd_contract_get.c
@@ -101,16 +101,8 @@ get_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
@@ -198,8 +190,12 @@ get_run (void *cls,
   struct ContractGetState *ds = cls;
   const struct TALER_ContractDiffiePrivateP *contract_priv;
   const struct TALER_TESTING_Command *ref;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ds->is = is;
   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
                                                   ds->contract_ref);
@@ -214,7 +210,7 @@ get_run (void *cls,
   }
   ds->contract_priv = *contract_priv;
   ds->dh = TALER_EXCHANGE_contract_get (
-    is->exchange,
+    exchange,
     contract_priv,
     &get_cb,
     ds);
@@ -244,10 +240,8 @@ get_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_contract_get_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
index 1b097a34..3d5c00ab 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -199,8 +199,7 @@ do_retry (void *cls)
   struct DepositState *ds = cls;
 
   ds->retry_task = NULL;
-  ds->is->commands[ds->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (ds->is);
   deposit_run (ds,
                NULL,
                ds->is);
@@ -240,7 +239,7 @@ deposit_cb (void *cls,
         else
           ds->backoff = GNUNET_TIME_randomized_backoff (ds->backoff,
                                                         MAX_BACKOFF);
-        ds->is->commands[ds->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (ds->is);
         GNUNET_assert (NULL == ds->retry_task);
         ds->retry_task
           = GNUNET_SCHEDULER_add_delayed (ds->backoff,
@@ -249,16 +248,8 @@ deposit_cb (void *cls,
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                JSON_INDENT (2));
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   if (MHD_HTTP_OK == dr->hr.http_status)
@@ -305,8 +296,12 @@ deposit_run (void *cls,
                                  &wire_salt),
     GNUNET_JSON_spec_end ()
   };
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ds->is = is;
   if (NULL != ds->deposit_reference)
   {
@@ -474,7 +469,7 @@ deposit_run (void *cls,
       .refund_deadline = ds->refund_deadline
     };
 
-    ds->dh = TALER_EXCHANGE_deposit (is->exchange,
+    ds->dh = TALER_EXCHANGE_deposit (exchange,
                                      &dcd,
                                      &cdd,
                                      &deposit_cb,
@@ -508,10 +503,8 @@ deposit_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_deposit_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_deposits_get.c 
b/src/testing/testing_api_cmd_deposits_get.c
index c39d7f6c..972a85b7 100644
--- a/src/testing/testing_api_cmd_deposits_get.c
+++ b/src/testing/testing_api_cmd_deposits_get.c
@@ -39,6 +39,11 @@ struct TrackTransactionState
    */
   const char *bank_transfer_reference;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * The WTID associated by the transaction being tracked.
    */
@@ -107,22 +112,12 @@ deposit_wtid_cb (void *cls,
 {
   struct TrackTransactionState *tts = cls;
   struct TALER_TESTING_Interpreter *is = tts->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
   tts->tth = NULL;
   if (tts->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                dr->hr.http_status,
-                (int) dr->hr.ec,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     dr->hr.http_status);
     return;
   }
   switch (dr->hr.http_status)
@@ -203,8 +198,12 @@ track_transaction_run (void *cls,
   struct TALER_MerchantWireHashP h_wire_details;
   struct TALER_PrivateContractHashP h_contract_terms;
   const struct TALER_MerchantPrivateKeyP *merchant_priv;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  (void) cmd;
+  tts->cmd = cmd;
+  if (NULL == exchange)
+    return;
   tts->is = is;
   transaction_cmd
     = TALER_TESTING_interpreter_lookup_command (tts->is,
@@ -276,7 +275,7 @@ track_transaction_run (void *cls,
     return;
   }
 
-  tts->tth = TALER_EXCHANGE_deposits_get (is->exchange,
+  tts->tth = TALER_EXCHANGE_deposits_get (exchange,
                                           merchant_priv,
                                           &h_wire_details,
                                           &h_contract_terms,
@@ -303,10 +302,8 @@ track_transaction_cleanup (void *cls,
 
   if (NULL != tts->tth)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                tts->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (tts->is,
+                                      cmd->label);
     TALER_EXCHANGE_deposits_get_cancel (tts->tth);
     tts->tth = NULL;
   }
@@ -335,10 +332,8 @@ track_transaction_traits (void *cls,
     TALER_TESTING_make_trait_wtid (&tts->wtid),
     TALER_TESTING_make_trait_legi_requirement_row (
       &tts->requirement_row),
-    TALER_TESTING_make_trait_h_payto (
-      &tts->h_payto),
-    TALER_TESTING_make_trait_payto_uri (
-      (const char **) &tts->merchant_payto_uri),
+    TALER_TESTING_make_trait_h_payto (&tts->h_payto),
+    TALER_TESTING_make_trait_payto_uri (tts->merchant_payto_uri),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_get_auditor.c 
b/src/testing/testing_api_cmd_get_auditor.c
new file mode 100644
index 00000000..42c887da
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_auditor.c
@@ -0,0 +1,280 @@
+/*
+  This file is part of TALER
+  (C) 2023 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 testing/testing_api_cmd_get_auditor.c
+ * @brief Command to get an auditor handle
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get auditor" CMD.
+ */
+struct GetAuditorState
+{
+
+  /**
+   * Private key of the auditor.
+   */
+  struct TALER_AuditorPrivateKeyP auditor_priv;
+
+  /**
+   * Public key of the auditor.
+   */
+  struct TALER_AuditorPublicKeyP auditor_pub;
+
+  /**
+   * Our interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Auditor handle we produced.
+   */
+  struct TALER_AUDITOR_Handle *auditor;
+
+  /**
+   * URL of the auditor.
+   */
+  char *auditor_url;
+
+  /**
+   * Filename of the master private key of the auditor.
+   */
+  char *priv_file;
+
+};
+
+
+/**
+ * Function called with information about the auditor.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ * @param vi basic information about the auditor
+ * @param compat protocol compatibility information
+ */
+static void
+version_cb (
+  void *cls,
+  const struct TALER_AUDITOR_HttpResponse *hr,
+  const struct TALER_AUDITOR_VersionInformation *vi,
+  enum TALER_AUDITOR_VersionCompatibility compat)
+{
+  struct GetAuditorState *gas = cls;
+
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    TALER_TESTING_unexpected_status (gas->is,
+                                     hr->http_status);
+    return;
+  }
+  TALER_TESTING_interpreter_next (gas->is);
+}
+
+
+/**
+ * Run the "get_auditor" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_auditor_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
+{
+  struct GetAuditorState *gas = cls;
+
+  (void) cmd;
+  if (NULL == gas->auditor_url)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL != gas->priv_file)
+  {
+    if (GNUNET_SYSERR ==
+        GNUNET_CRYPTO_eddsa_key_from_file (gas->priv_file,
+                                           GNUNET_YES,
+                                           &gas->auditor_priv.eddsa_priv))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_CRYPTO_eddsa_key_get_public (&gas->auditor_priv.eddsa_priv,
+                                        &gas->auditor_pub.eddsa_pub);
+  }
+  gas->is = is;
+  gas->auditor
+    = TALER_AUDITOR_connect (TALER_TESTING_interpreter_get_context (is),
+                             gas->auditor_url,
+                             &version_cb,
+                             gas);
+  if (NULL == gas->auditor)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_auditor_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
+{
+  struct GetAuditorState *gas = cls;
+
+  if (NULL != gas->auditor)
+  {
+    TALER_AUDITOR_disconnect (gas->auditor);
+    gas->auditor = NULL;
+  }
+  GNUNET_free (gas->priv_file);
+  GNUNET_free (gas->auditor_url);
+  GNUNET_free (gas);
+}
+
+
+/**
+ * Offer internal data to a "get_auditor" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_auditor_traits (void *cls,
+                    const void **ret,
+                    const char *trait,
+                    unsigned int index)
+{
+  struct GetAuditorState *gas = cls;
+  unsigned int off = (NULL == gas->priv_file) ? 2 : 0;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_auditor_priv (&gas->auditor_priv),
+    TALER_TESTING_make_trait_auditor_pub (&gas->auditor_pub),
+    TALER_TESTING_make_trait_auditor (gas->auditor),
+    TALER_TESTING_make_trait_auditor_url (gas->auditor_url),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (&traits[off],
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Get the base URL of the auditor from @a cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the auditor according to @a cfg
+ */
+static char *
+get_auditor_base_url (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *auditor_url;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             "auditor",
+                                             "BASE_URL",
+                                             &auditor_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "auditor",
+                               "BASE_URL");
+    return NULL;
+  }
+  return auditor_url;
+}
+
+
+/**
+ * Get the file name of the master private key file of the auditor from @a
+ * cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the auditor according to @a cfg
+ */
+static char *
+get_auditor_priv_file (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *fn;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "auditor",
+                                               "AUDITOR_PRIV_FILE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "auditor",
+                               "AUDITOR_PRIV_FILE");
+    return NULL;
+  }
+  return fn;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_auditor (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  bool load_auditor_keys)
+{
+  struct GetAuditorState *gas;
+
+  gas = GNUNET_new (struct GetAuditorState);
+  gas->auditor_url = get_auditor_base_url (cfg);
+  if (load_auditor_keys)
+    gas->priv_file = get_auditor_priv_file (cfg);
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gas,
+      .label = label,
+      .run = &get_auditor_run,
+      .cleanup = &get_auditor_cleanup,
+      .traits = &get_auditor_traits,
+      .name = "auditor"
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_get_exchange.c 
b/src/testing/testing_api_cmd_get_exchange.c
new file mode 100644
index 00000000..2fc8ba77
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_exchange.c
@@ -0,0 +1,308 @@
+/*
+  This file is part of TALER
+  (C) 2023 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 testing/testing_api_cmd_get_exchange.c
+ * @brief Command to get an exchange handle
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get exchange" CMD.
+ */
+struct GetExchangeState
+{
+
+  /**
+   * Master private key of the exchange.
+   */
+  struct TALER_MasterPrivateKeyP master_priv;
+
+  /**
+   * Our interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Exchange handle we produced.
+   */
+  struct TALER_EXCHANGE_Handle *exchange;
+
+  /**
+   * URL of the exchange.
+   */
+  char *exchange_url;
+
+  /**
+   * Filename of the master private key of the exchange.
+   */
+  char *master_priv_file;
+
+  /**
+   * Are we waiting for /keys before continuing?
+   */
+  bool wait_for_keys;
+};
+
+
+static void
+cert_cb (void *cls,
+         const struct TALER_EXCHANGE_KeysResponse *kr)
+{
+  struct GetExchangeState *ges = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr;
+  struct TALER_TESTING_Interpreter *is = ges->is;
+
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (ges->wait_for_keys)
+    {
+      ges->wait_for_keys = false;
+      TALER_TESTING_interpreter_next (is);
+      return;
+    }
+    return;
+  default:
+    GNUNET_break (0);
+    TALER_EXCHANGE_disconnect (ges->exchange);
+    ges->exchange = NULL;
+    if (ges->wait_for_keys)
+    {
+      ges->wait_for_keys = false;
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    return;
+  }
+}
+
+
+/**
+ * Run the "get_exchange" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_exchange_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct GetExchangeState *ges = cls;
+
+  (void) cmd;
+  if (NULL == ges->exchange_url)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL != ges->master_priv_file)
+  {
+    if (GNUNET_SYSERR ==
+        GNUNET_CRYPTO_eddsa_key_from_file (ges->master_priv_file,
+                                           GNUNET_YES,
+                                           &ges->master_priv.eddsa_priv))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
+  ges->is = is;
+  ges->exchange
+    = TALER_EXCHANGE_connect (TALER_TESTING_interpreter_get_context (is),
+                              ges->exchange_url,
+                              &cert_cb,
+                              ges,
+                              TALER_EXCHANGE_OPTION_END);
+  if (NULL == ges->exchange)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (! ges->wait_for_keys)
+    TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_exchange_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct GetExchangeState *ges = cls;
+
+  if (NULL != ges->exchange)
+  {
+    TALER_EXCHANGE_disconnect (ges->exchange);
+    ges->exchange = NULL;
+  }
+  GNUNET_free (ges->master_priv_file);
+  GNUNET_free (ges->exchange_url);
+  GNUNET_free (ges);
+}
+
+
+/**
+ * Offer internal data to a "get_exchange" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_exchange_traits (void *cls,
+                     const void **ret,
+                     const char *trait,
+                     unsigned int index)
+{
+  struct GetExchangeState *ges = cls;
+  unsigned int off = (NULL == ges->master_priv_file) ? 1 : 0;
+  const struct TALER_EXCHANGE_Keys *keys
+    = TALER_EXCHANGE_get_keys (ges->exchange);
+
+  if (NULL != keys)
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+      TALER_TESTING_make_trait_master_pub (&keys->master_pub),
+      TALER_TESTING_make_trait_exchange (ges->exchange),
+      TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait (&traits[off],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+  else
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+      TALER_TESTING_make_trait_exchange (ges->exchange),
+      TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait (&traits[off],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+}
+
+
+/**
+ * Get the base URL of the exchange from @a cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_base_url (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *exchange_url;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &exchange_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    return NULL;
+  }
+  return exchange_url;
+}
+
+
+/**
+ * Get the file name of the master private key file of the exchange from @a
+ * cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_master_priv_file (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *fn;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "exchange-offline",
+                                               "MASTER_PRIV_FILE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "MASTER_PRIV_FILE");
+    return NULL;
+  }
+  return fn;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  bool wait_for_keys,
+  bool load_private_key)
+{
+  struct GetExchangeState *ges;
+
+  ges = GNUNET_new (struct GetExchangeState);
+  ges->exchange_url = get_exchange_base_url (cfg);
+  if (load_private_key)
+    ges->master_priv_file = get_exchange_master_priv_file (cfg);
+  ges->wait_for_keys = wait_for_keys;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ges,
+      .label = label,
+      .run = &get_exchange_run,
+      .cleanup = &get_exchange_cleanup,
+      .traits = &get_exchange_traits,
+      .name = "exchange"
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_insert_deposit.c 
b/src/testing/testing_api_cmd_insert_deposit.c
index 032ff72d..7fa8fa79 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -29,6 +29,7 @@
 #include "taler_signatures.h"
 #include "taler_testing_lib.h"
 #include "taler_exchangedb_plugin.h"
+#include "taler_exchangedb_lib.h"
 
 
 /**
@@ -37,9 +38,9 @@
 struct InsertDepositState
 {
   /**
-   * Configuration file used by the command.
+   * Database connection we use.
    */
-  const struct TALER_TESTING_DatabaseConnection *dbc;
+  struct TALER_EXCHANGEDB_Plugin *plugin;
 
   /**
    * Human-readable name of the shop.
@@ -71,6 +72,11 @@ struct InsertDepositState
    * Deposit fee.
    */
   const char *deposit_fee;
+
+  /**
+   * Do we used a cached @e plugin?
+   */
+  bool cached;
 };
 
 /**
@@ -133,6 +139,19 @@ insert_deposit_run (void *cls,
   struct TALER_DenominationPrivateKey denom_priv;
 
   (void) cmd;
+  if (NULL == ids->plugin)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      ids->plugin->preflight (ids->plugin->cls))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
   // prepare and store issue first.
   fake_issue (&issue);
   GNUNET_assert (GNUNET_OK ==
@@ -144,14 +163,14 @@ insert_deposit_run (void *cls,
                         &issue.denom_hash);
 
   if ( (GNUNET_OK !=
-        ids->dbc->plugin->start (ids->dbc->plugin->cls,
-                                 "talertestinglib: denomination insertion")) ||
+        ids->plugin->start (ids->plugin->cls,
+                            "talertestinglib: denomination insertion")) ||
        (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-        ids->dbc->plugin->insert_denomination_info (ids->dbc->plugin->cls,
-                                                    &dpk,
-                                                    &issue)) ||
+        ids->plugin->insert_denomination_info (ids->plugin->cls,
+                                               &dpk,
+                                               &issue)) ||
        (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-        ids->dbc->plugin->commit (ids->dbc->plugin->cls)) )
+        ids->plugin->commit (ids->plugin->cls)) )
   {
     TALER_TESTING_interpreter_fail (is);
     TALER_denom_pub_free (&dpk);
@@ -248,23 +267,23 @@ insert_deposit_run (void *cls,
     struct TALER_AgeCommitmentHash agh;
 
     if ( (GNUNET_OK !=
-          ids->dbc->plugin->start (ids->dbc->plugin->cls,
-                                   "libtalertesting: insert deposit")) ||
+          ids->plugin->start (ids->plugin->cls,
+                              "libtalertesting: insert deposit")) ||
          (0 >
-          ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls,
-                                               &deposit.coin,
-                                               &known_coin_id,
-                                               &dph,
-                                               &agh)) ||
+          ids->plugin->ensure_coin_known (ids->plugin->cls,
+                                          &deposit.coin,
+                                          &known_coin_id,
+                                          &dph,
+                                          &agh)) ||
          (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls,
-                                            ids->exchange_timestamp,
-                                            &deposit)) ||
+          ids->plugin->insert_deposit (ids->plugin->cls,
+                                       ids->exchange_timestamp,
+                                       &deposit)) ||
          (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-          ids->dbc->plugin->commit (ids->dbc->plugin->cls)) )
+          ids->plugin->commit (ids->plugin->cls)) )
     {
       GNUNET_break (0);
-      ids->dbc->plugin->rollback (ids->dbc->plugin->cls);
+      ids->plugin->rollback (ids->plugin->cls);
       GNUNET_free (deposit.receiver_wire_account);
       TALER_denom_pub_free (&dpk);
       TALER_denom_priv_free (&denom_priv);
@@ -295,6 +314,14 @@ insert_deposit_cleanup (void *cls,
   struct InsertDepositState *ids = cls;
 
   (void) cmd;
+  if ( (NULL != ids->plugin) &&
+       (! ids->cached) )
+  {
+    // FIXME: historically, we also did:
+    // ids->plugin->drop_tables (ids->plugin->cls);
+    TALER_EXCHANGEDB_plugin_unload (ids->plugin);
+    ids->plugin = NULL;
+  }
   GNUNET_free (ids);
 }
 
@@ -302,7 +329,7 @@ insert_deposit_cleanup (void *cls,
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_insert_deposit (
   const char *label,
-  const struct TALER_TESTING_DatabaseConnection *dbc,
+  const struct GNUNET_CONFIGURATION_Handle *db_cfg,
   const char *merchant_name,
   const char *merchant_account,
   struct GNUNET_TIME_Timestamp exchange_timestamp,
@@ -310,10 +337,22 @@ TALER_TESTING_cmd_insert_deposit (
   const char *amount_with_fee,
   const char *deposit_fee)
 {
+  static struct TALER_EXCHANGEDB_Plugin *pluginc;
+  static const struct GNUNET_CONFIGURATION_Handle *db_cfgc;
   struct InsertDepositState *ids;
 
   ids = GNUNET_new (struct InsertDepositState);
-  ids->dbc = dbc;
+  if (db_cfgc == db_cfg)
+  {
+    ids->plugin = pluginc;
+    ids->cached = true;
+  }
+  else
+  {
+    ids->plugin = TALER_EXCHANGEDB_plugin_load (db_cfg);
+    pluginc = ids->plugin;
+    db_cfgc = db_cfg;
+  }
   ids->merchant_name = merchant_name;
   ids->merchant_account = merchant_account;
   ids->exchange_timestamp = exchange_timestamp;
diff --git a/src/testing/testing_api_cmd_kyc_check_get.c 
b/src/testing/testing_api_cmd_kyc_check_get.c
index 3241aae3..7c8bcf72 100644
--- a/src/testing/testing_api_cmd_kyc_check_get.c
+++ b/src/testing/testing_api_cmd_kyc_check_get.c
@@ -73,19 +73,12 @@ check_kyc_cb (void *cls,
 {
   struct KycCheckGetState *kcg = cls;
   struct TALER_TESTING_Interpreter *is = kcg->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
   kcg->kwh = NULL;
   if (kcg->expected_response_code != ks->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                ks->http_status,
-                (int) ks->ec,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     ks->http_status);
     return;
   }
   switch (ks->http_status)
@@ -121,8 +114,12 @@ check_kyc_run (void *cls,
   const struct TALER_TESTING_Command *res_cmd;
   const uint64_t *requirement_row;
   const struct TALER_PaytoHashP *h_payto;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   kcg->is = is;
   res_cmd = TALER_TESTING_interpreter_lookup_command (kcg->is,
                                                       kcg->
@@ -155,7 +152,7 @@ check_kyc_run (void *cls,
     TALER_TESTING_interpreter_fail (kcg->is);
     return;
   }
-  kcg->kwh = TALER_EXCHANGE_kyc_check (is->exchange,
+  kcg->kwh = TALER_EXCHANGE_kyc_check (exchange,
                                        *requirement_row,
                                        h_payto,
                                        TALER_KYCLOGIC_KYC_UT_INDIVIDUAL,
@@ -181,10 +178,8 @@ check_kyc_cleanup (void *cls,
 
   if (NULL != kcg->kwh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                kcg->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (kcg->is,
+                                      cmd->label);
     TALER_EXCHANGE_kyc_check_cancel (kcg->kwh);
     kcg->kwh = NULL;
   }
@@ -210,8 +205,7 @@ check_kyc_traits (void *cls,
 {
   struct KycCheckGetState *kcg = cls;
   struct TALER_TESTING_Trait traits[] = {
-    TALER_TESTING_make_trait_kyc_url (
-      (const char **) &kcg->kyc_url),
+    TALER_TESTING_make_trait_kyc_url (kcg->kyc_url),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_kyc_proof.c 
b/src/testing/testing_api_cmd_kyc_proof.c
index ff76e415..291378d9 100644
--- a/src/testing/testing_api_cmd_kyc_proof.c
+++ b/src/testing/testing_api_cmd_kyc_proof.c
@@ -83,18 +83,12 @@ proof_kyc_cb (void *cls,
 {
   struct KycProofGetState *kcg = cls;
   struct TALER_TESTING_Interpreter *is = kcg->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
   kcg->kph = NULL;
   if (kcg->expected_response_code != kpr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                kpr->http_status,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     kpr->http_status);
     return;
   }
   switch (kpr->http_status)
@@ -133,8 +127,12 @@ proof_kyc_run (void *cls,
   const struct TALER_TESTING_Command *res_cmd;
   const struct TALER_PaytoHashP *h_payto;
   char *uargs;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   kps->is = is;
   res_cmd = TALER_TESTING_interpreter_lookup_command (
     kps->is,
@@ -159,7 +157,7 @@ proof_kyc_run (void *cls,
     GNUNET_asprintf (&uargs,
                      "&code=%s",
                      kps->code);
-  kps->kph = TALER_EXCHANGE_kyc_proof (is->exchange,
+  kps->kph = TALER_EXCHANGE_kyc_proof (exchange,
                                        h_payto,
                                        kps->logic,
                                        uargs,
@@ -185,10 +183,8 @@ proof_kyc_cleanup (void *cls,
 
   if (NULL != kps->kph)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                kps->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (kps->is,
+                                      cmd->label);
     TALER_EXCHANGE_kyc_proof_cancel (kps->kph);
     kps->kph = NULL;
   }
@@ -214,8 +210,7 @@ proof_kyc_traits (void *cls,
 {
   struct KycProofGetState *kps = cls;
   struct TALER_TESTING_Trait traits[] = {
-    TALER_TESTING_make_trait_web_url (
-      (const char **) &kps->redirect_url),
+    TALER_TESTING_make_trait_web_url (kps->redirect_url),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_kyc_wallet_get.c 
b/src/testing/testing_api_cmd_kyc_wallet_get.c
index 23df3b9d..5f6eaeb4 100644
--- a/src/testing/testing_api_cmd_kyc_wallet_get.c
+++ b/src/testing/testing_api_cmd_kyc_wallet_get.c
@@ -48,6 +48,11 @@ struct KycWalletGetState
    */
   char *reserve_payto_uri;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Command to get a reserve private key from.
    */
@@ -99,20 +104,12 @@ wallet_kyc_cb (void *cls,
 {
   struct KycWalletGetState *kwg = cls;
   struct TALER_TESTING_Interpreter *is = kwg->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
   kwg->kwh = NULL;
   if (kwg->expected_response_code != wkr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d (wanted %u) to command %s in 
%s:%u\n",
-                wkr->http_status,
-                (int) wkr->ec,
-                kwg->expected_response_code,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     wkr->http_status);
     return;
   }
   switch (wkr->http_status)
@@ -150,8 +147,12 @@ wallet_kyc_run (void *cls,
                 struct TALER_TESTING_Interpreter *is)
 {
   struct KycWalletGetState *kwg = cls;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  (void) cmd;
+  kwg->cmd = cmd;
+  if (NULL == exchange)
+    return;
   kwg->is = is;
   if (NULL != kwg->reserve_reference)
   {
@@ -184,9 +185,9 @@ wallet_kyc_run (void *cls,
   GNUNET_CRYPTO_eddsa_key_get_public (&kwg->reserve_priv.eddsa_priv,
                                       &kwg->reserve_pub.eddsa_pub);
   kwg->reserve_payto_uri
-    = TALER_reserve_make_payto (TALER_EXCHANGE_get_base_url (is->exchange),
+    = TALER_reserve_make_payto (TALER_EXCHANGE_get_base_url (exchange),
                                 &kwg->reserve_pub);
-  kwg->kwh = TALER_EXCHANGE_kyc_wallet (is->exchange,
+  kwg->kwh = TALER_EXCHANGE_kyc_wallet (exchange,
                                         &kwg->reserve_priv,
                                         &kwg->balance,
                                         &wallet_kyc_cb,
@@ -210,10 +211,8 @@ wallet_kyc_cleanup (void *cls,
 
   if (NULL != kwg->kwh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                kwg->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (kwg->is,
+                                      cmd->label);
     TALER_EXCHANGE_kyc_wallet_cancel (kwg->kwh);
     kwg->kwh = NULL;
   }
@@ -243,8 +242,7 @@ wallet_kyc_traits (void *cls,
     TALER_TESTING_make_trait_reserve_pub (&kwg->reserve_pub),
     TALER_TESTING_make_trait_legi_requirement_row (&kwg->requirement_row),
     TALER_TESTING_make_trait_h_payto (&kwg->h_payto),
-    TALER_TESTING_make_trait_payto_uri (
-      (const char **) &kwg->reserve_payto_uri),
+    TALER_TESTING_make_trait_payto_uri (kwg->reserve_payto_uri),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_purse_create_deposit.c 
b/src/testing/testing_api_cmd_purse_create_deposit.c
index 6fa7d91f..2f13849d 100644
--- a/src/testing/testing_api_cmd_purse_create_deposit.c
+++ b/src/testing/testing_api_cmd_purse_create_deposit.c
@@ -162,16 +162,8 @@ deposit_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   if (MHD_HTTP_OK == dr->hr.http_status)
@@ -197,8 +189,12 @@ deposit_run (void *cls,
 {
   struct PurseCreateDepositState *ds = cls;
   struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ds->is = is;
   for (unsigned int i = 0; i<ds->num_coin_references; i++)
   {
@@ -263,7 +259,7 @@ deposit_run (void *cls,
                    "pay_deadline",
                    GNUNET_JSON_from_timestamp (ds->purse_expiration)));
   ds->dh = TALER_EXCHANGE_purse_create_with_deposit (
-    is->exchange,
+    exchange,
     &ds->purse_priv,
     &ds->merge_priv,
     &ds->contract_priv,
@@ -299,10 +295,8 @@ deposit_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_purse_create_with_deposit_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_purse_delete.c 
b/src/testing/testing_api_cmd_purse_delete.c
index aa085327..758524ae 100644
--- a/src/testing/testing_api_cmd_purse_delete.c
+++ b/src/testing/testing_api_cmd_purse_delete.c
@@ -74,16 +74,8 @@ purse_delete_cb (void *cls,
   pds->pdh = NULL;
   if (pds->expected_response_code != pdr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                pdr->hr.http_status,
-                pds->is->commands[pds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (pdr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (pds->is);
+    TALER_TESTING_unexpected_status (pds->is,
+                                     pdr->hr.http_status);
     return;
   }
   TALER_TESTING_interpreter_next (pds->is);
@@ -105,8 +97,12 @@ purse_delete_run (void *cls,
   struct PurseDeleteState *pds = cls;
   const struct TALER_PurseContractPrivateKeyP *purse_priv;
   const struct TALER_TESTING_Command *ref;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ref = TALER_TESTING_interpreter_lookup_command (is,
                                                   pds->purse_cmd);
   if (NULL == ref)
@@ -125,7 +121,7 @@ purse_delete_run (void *cls,
   }
   pds->is = is;
   pds->pdh = TALER_EXCHANGE_purse_delete (
-    is->exchange,
+    exchange,
     purse_priv,
     &purse_delete_cb,
     pds);
@@ -153,10 +149,8 @@ purse_delete_cleanup (void *cls,
 
   if (NULL != pds->pdh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                pds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (pds->is,
+                                      cmd->label);
     TALER_EXCHANGE_purse_delete_cancel (pds->pdh);
     pds->pdh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_purse_deposit.c 
b/src/testing/testing_api_cmd_purse_deposit.c
index aaf6ff6b..fb1d5155 100644
--- a/src/testing/testing_api_cmd_purse_deposit.c
+++ b/src/testing/testing_api_cmd_purse_deposit.c
@@ -133,20 +133,15 @@ deposit_cb (void *cls,
             const struct TALER_EXCHANGE_PurseDepositResponse *dr)
 {
   struct PurseDepositState *ds = cls;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (ds->is);
 
   ds->dh = NULL;
+  GNUNET_assert (NULL != exchange);
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                JSON_INDENT (2));
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   if (MHD_HTTP_OK == dr->hr.http_status)
@@ -205,7 +200,7 @@ deposit_cb (void *cls,
         const struct TALER_EXCHANGE_Keys *keys;
         const struct TALER_EXCHANGE_GlobalFee *gf;
 
-        keys = TALER_EXCHANGE_get_keys (ds->is->exchange);
+        keys = TALER_EXCHANGE_get_keys (exchange);
         GNUNET_assert (NULL != keys);
         gf = TALER_EXCHANGE_get_global_fee (keys,
                                             *merge_timestamp);
@@ -264,10 +259,13 @@ deposit_run (void *cls,
   struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
   const struct TALER_PurseContractPublicKeyP *purse_pub;
   const struct TALER_TESTING_Command *purse_cmd;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   (void) cmd;
   ds->is = is;
-
   purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                         ds->purse_ref);
   GNUNET_assert (NULL != purse_cmd);
@@ -323,7 +321,7 @@ deposit_run (void *cls,
   }
 
   ds->dh = TALER_EXCHANGE_purse_deposit (
-    is->exchange,
+    exchange,
     NULL, /* FIXME #7271: WADs support: purse exchange URL */
     &ds->purse_pub,
     ds->min_age,
@@ -357,10 +355,8 @@ deposit_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_purse_deposit_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_purse_get.c 
b/src/testing/testing_api_cmd_purse_get.c
index 60638752..235ae63c 100644
--- a/src/testing/testing_api_cmd_purse_get.c
+++ b/src/testing/testing_api_cmd_purse_get.c
@@ -183,7 +183,11 @@ status_run (void *cls,
 {
   struct StatusState *ss = cls;
   const struct TALER_TESTING_Command *create_purse;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_purse
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -198,7 +202,7 @@ status_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-  ss->pgh = TALER_EXCHANGE_purse_get (is->exchange,
+  ss->pgh = TALER_EXCHANGE_purse_get (exchange,
                                       ss->purse_pub,
                                       ss->timeout,
                                       ss->wait_for_merge,
@@ -227,10 +231,8 @@ status_cleanup (void *cls,
 
   if (NULL != ss->pgh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_purse_get_cancel (ss->pgh);
     ss->pgh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_purse_merge.c 
b/src/testing/testing_api_cmd_purse_merge.c
index 2ab3a235..8f4f6b3c 100644
--- a/src/testing/testing_api_cmd_purse_merge.c
+++ b/src/testing/testing_api_cmd_purse_merge.c
@@ -177,16 +177,8 @@ merge_cb (void *cls,
 
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -209,8 +201,12 @@ merge_run (void *cls,
   const struct TALER_PurseMergePrivateKeyP *merge_priv;
   const json_t *ct;
   const struct TALER_TESTING_Command *ref;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ds->is = is;
   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
                                                   ds->merge_ref);
@@ -302,8 +298,21 @@ merge_run (void *cls,
                                       &ds->reserve_pub.eddsa_pub);
   {
     char *payto_uri;
+    const char *exchange_url;
+    const struct TALER_TESTING_Command *exchange_cmd;
 
-    payto_uri = TALER_reserve_make_payto (is->exchange_url,
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+    payto_uri = TALER_reserve_make_payto (exchange_url,
                                           &ds->reserve_pub);
     TALER_payto_hash (payto_uri,
                       &ds->h_payto);
@@ -313,7 +322,7 @@ merge_run (void *cls,
                                       &ds->merge_pub.eddsa_pub);
   ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
   ds->dh = TALER_EXCHANGE_account_merge (
-    is->exchange,
+    exchange,
     NULL, /* no wad */
     &ds->reserve_priv,
     &ds->purse_pub,
@@ -351,10 +360,8 @@ merge_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_account_merge_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_recoup.c 
b/src/testing/testing_api_cmd_recoup.c
index e11475f2..9483b158 100644
--- a/src/testing/testing_api_cmd_recoup.c
+++ b/src/testing/testing_api_cmd_recoup.c
@@ -82,7 +82,6 @@ recoup_cb (void *cls,
   struct RecoupState *ps = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
   struct TALER_TESTING_Interpreter *is = ps->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
   const struct TALER_TESTING_Command *reserve_cmd;
   char *cref;
   unsigned int idx;
@@ -90,18 +89,8 @@ recoup_cb (void *cls,
   ps->ph = NULL;
   if (ps->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    fprintf (stderr, "\n");
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     hr->http_status);
     return;
   }
 
@@ -193,7 +182,11 @@ recoup_run (void *cls,
   char *cref;
   unsigned int idx;
   const struct TALER_ExchangeWithdrawValues *ewv;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ps->is = is;
   if (GNUNET_OK !=
       TALER_TESTING_parse_coin_reference (
@@ -266,7 +259,7 @@ recoup_run (void *cls,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Trying to recoup denomination '%s'\n",
               TALER_B2S (&denom_pub->h_key));
-  ps->ph = TALER_EXCHANGE_recoup (is->exchange,
+  ps->ph = TALER_EXCHANGE_recoup (exchange,
                                   denom_pub,
                                   coin_sig,
                                   ewv,
diff --git a/src/testing/testing_api_cmd_recoup_refresh.c 
b/src/testing/testing_api_cmd_recoup_refresh.c
index ff7dab00..1c7456c7 100644
--- a/src/testing/testing_api_cmd_recoup_refresh.c
+++ b/src/testing/testing_api_cmd_recoup_refresh.c
@@ -82,25 +82,14 @@ recoup_refresh_cb (void *cls,
   struct RecoupRefreshState *rrs = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
   struct TALER_TESTING_Interpreter *is = rrs->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
   char *cref;
   unsigned int idx;
 
   rrs->ph = NULL;
   if (rrs->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    fprintf (stderr, "\n");
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     hr->http_status);
     return;
   }
 
@@ -195,7 +184,11 @@ recoup_refresh_run (void *cls,
   const struct TALER_ExchangeWithdrawValues *ewv;
   char *cref;
   unsigned int idx;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   rrs->is = is;
   if (GNUNET_OK !=
       TALER_TESTING_parse_coin_reference (
@@ -280,7 +273,7 @@ recoup_refresh_run (void *cls,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Trying to recoup_refresh denomination '%s'\n",
               TALER_B2S (&denom_pub->h_key));
-  rrs->ph = TALER_EXCHANGE_recoup_refresh (is->exchange,
+  rrs->ph = TALER_EXCHANGE_recoup_refresh (exchange,
                                            denom_pub,
                                            coin_sig,
                                            ewv,
diff --git a/src/testing/testing_api_cmd_refresh.c 
b/src/testing/testing_api_cmd_refresh.c
index 9c2bd8d5..a15e8dbc 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -102,6 +102,11 @@ struct RefreshMeltState
    */
   struct TALER_EXCHANGE_RefreshData refresh_data;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Reference to a previous melt command.
    */
@@ -209,6 +214,11 @@ struct RefreshRevealState
    */
   struct TALER_EXCHANGE_RefreshesRevealHandle *rrh;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Convenience struct to keep in one place all the
    * data related to one fresh coin, set by the reveal callback
@@ -272,6 +282,11 @@ struct RefreshLinkState
    */
   const char *reveal_reference;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Handle to the ongoing operation.
    */
@@ -334,8 +349,7 @@ do_reveal_retry (void *cls)
   struct RefreshRevealState *rrs = cls;
 
   rrs->retry_task = NULL;
-  rrs->is->commands[rrs->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (rrs->is);
   refresh_reveal_run (rrs,
                       NULL,
                       rrs->is);
@@ -357,8 +371,12 @@ reveal_cb (void *cls,
   struct RefreshRevealState *rrs = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
   const struct TALER_TESTING_Command *melt_cmd;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (rrs->is);
 
   rrs->rrh = NULL;
+  if (NULL == exchange)
+    return;
   if (rrs->expected_response_code != hr->http_status)
   {
     if (0 != rrs->do_retry)
@@ -380,24 +398,15 @@ reveal_cb (void *cls,
                                                          MAX_BACKOFF);
         rrs->total_backoff = GNUNET_TIME_relative_add (rrs->total_backoff,
                                                        rrs->backoff);
-        rrs->is->commands[rrs->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (rrs->is);
         rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff,
                                                         &do_reveal_retry,
                                                         rrs);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                rrs->is->commands[rrs->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rrs->is);
+    TALER_TESTING_unexpected_status (rrs->is,
+                                     hr->http_status);
     return;
   }
   melt_cmd = TALER_TESTING_interpreter_lookup_command (rrs->is,
@@ -444,9 +453,9 @@ reveal_cb (void *cls,
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Total reveal backoff for %s was %s\n",
-                  rrs->is->commands[rrs->is->ip].label,
+                  rrs->cmd->label,
                   GNUNET_STRINGS_relative_time_to_string (rrs->total_backoff,
-                                                          GNUNET_YES));
+                                                          true));
     }
     break;
   default:
@@ -487,7 +496,12 @@ refresh_reveal_run (void *cls,
   struct RefreshRevealState *rrs = cls;
   struct RefreshMeltState *rms;
   const struct TALER_TESTING_Command *melt_cmd;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  rrs->cmd = cmd;
+  if (NULL == exchange)
+    return;
   rrs->is = is;
   melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                        rrs->melt_reference);
@@ -504,7 +518,7 @@ refresh_reveal_run (void *cls,
 
     for (unsigned int i = 0; i<rms->num_fresh_coins; i++)
       alg_values[i] = rms->mbds[i].alg_value;
-    rrs->rrh = TALER_EXCHANGE_refreshes_reveal (is->exchange,
+    rrs->rrh = TALER_EXCHANGE_refreshes_reveal (exchange,
                                                 &rms->rms,
                                                 &rms->refresh_data,
                                                 rms->num_fresh_coins,
@@ -538,10 +552,8 @@ refresh_reveal_cleanup (void *cls,
   (void) cmd;
   if (NULL != rrs->rrh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                rrs->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (rrs->is,
+                                      cmd->label);
     TALER_EXCHANGE_refreshes_reveal_cancel (rrs->rrh);
     rrs->rrh = NULL;
   }
@@ -585,8 +597,7 @@ do_link_retry (void *cls)
   struct RefreshLinkState *rls = cls;
 
   rls->retry_task = NULL;
-  rls->is->commands[rls->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (rls->is);
   refresh_link_run (rls,
                     NULL,
                     rls->is);
@@ -608,7 +619,6 @@ link_cb (void *cls,
   struct RefreshLinkState *rls = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &lr->hr;
   const struct TALER_TESTING_Command *reveal_cmd;
-  struct TALER_TESTING_Command *link_cmd = &rls->is->commands[rls->is->ip];
   unsigned int found;
   const unsigned int *num_fresh_coins;
 
@@ -634,24 +644,15 @@ link_cb (void *cls,
                                                          MAX_BACKOFF);
         rls->total_backoff = GNUNET_TIME_relative_add (rls->total_backoff,
                                                        rls->backoff);
-        rls->is->commands[rls->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (rls->is);
         rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff,
                                                         &do_link_retry,
                                                         rls);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                link_cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rls->is);
+    TALER_TESTING_unexpected_status (rls->is,
+                                     hr->http_status);
     return;
   }
   reveal_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
@@ -749,9 +750,9 @@ link_cb (void *cls,
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Total link backoff for %s was %s\n",
-                  rls->is->commands[rls->is->ip].label,
+                  rls->cmd->label,
                   GNUNET_STRINGS_relative_time_to_string (rls->total_backoff,
-                                                          GNUNET_YES));
+                                                          true));
     }
     break;
   default:
@@ -782,8 +783,12 @@ refresh_link_run (void *cls,
   const struct TALER_TESTING_Command *reveal_cmd;
   const struct TALER_TESTING_Command *melt_cmd;
   const struct TALER_TESTING_Command *coin_cmd;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  (void) cmd;
+  rls->cmd = cmd;
+  if (NULL == exchange)
+    return;
   rls->is = is;
   reveal_cmd = TALER_TESTING_interpreter_lookup_command (rls->is,
                                                          
rls->reveal_reference);
@@ -827,7 +832,7 @@ refresh_link_run (void *cls,
   }
 
   /* finally, use private key from withdraw sign command */
-  rls->rlh = TALER_EXCHANGE_link (is->exchange,
+  rls->rlh = TALER_EXCHANGE_link (exchange,
                                   coin_priv,
                                   rms->refresh_data.melt_age_commitment_proof,
                                   &link_cb,
@@ -857,11 +862,8 @@ refresh_link_cleanup (void *cls,
 
   if (NULL != rls->rlh)
   {
-
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                rls->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (rls->is,
+                                      cmd->label);
     TALER_EXCHANGE_link_cancel (rls->rlh);
     rls->rlh = NULL;
   }
@@ -885,8 +887,7 @@ do_melt_retry (void *cls)
   struct RefreshMeltState *rms = cls;
 
   rms->retry_task = NULL;
-  rms->is->commands[rms->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (rms->is);
   melt_run (rms,
             NULL,
             rms->is);
@@ -907,8 +908,12 @@ melt_cb (void *cls,
 {
   struct RefreshMeltState *rms = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (rms->is);
 
   rms->rmh = NULL;
+  if (NULL == exchange)
+    return;
   if (rms->expected_response_code != hr->http_status)
   {
     if (0 != rms->do_retry)
@@ -930,24 +935,15 @@ melt_cb (void *cls,
                                                          MAX_BACKOFF);
         rms->total_backoff = GNUNET_TIME_relative_add (rms->total_backoff,
                                                        rms->backoff);
-        rms->is->commands[rms->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (rms->is);
         rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff,
                                                         &do_melt_retry,
                                                         rms);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                rms->is->commands[rms->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rms->is);
+    TALER_TESTING_unexpected_status (rms->is,
+                                     hr->http_status);
     return;
   }
   if (MHD_HTTP_OK == hr->http_status)
@@ -969,15 +965,15 @@ melt_cb (void *cls,
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Total melt backoff for %s was %s\n",
-                rms->is->commands[rms->is->ip].label,
+                rms->cmd->label,
                 GNUNET_STRINGS_relative_time_to_string (rms->total_backoff,
-                                                        GNUNET_YES));
+                                                        true));
   }
   if (rms->double_melt)
   {
     TALER_LOG_DEBUG ("Doubling the melt (%s)\n",
-                     rms->is->commands[rms->is->ip].label);
-    rms->rmh = TALER_EXCHANGE_melt (rms->is->exchange,
+                     rms->cmd->label);
+    rms->rmh = TALER_EXCHANGE_melt (exchange,
                                     &rms->rms,
                                     &rms->refresh_data,
                                     &melt_cb,
@@ -1008,8 +1004,12 @@ melt_run (void *cls,
     NULL
   };
   const char **melt_fresh_amounts;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  (void) cmd;
+  rms->cmd = cmd;
+  if (NULL == exchange)
+    return;
   if (NULL == (melt_fresh_amounts = rms->melt_fresh_amounts))
     melt_fresh_amounts = default_melt_fresh_amounts;
   rms->is = is;
@@ -1113,7 +1113,7 @@ melt_run (void *cls,
         TALER_TESTING_interpreter_fail (rms->is);
         return;
       }
-      fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
+      fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (exchange),
                                         &fresh_amount,
                                         age_restricted);
       if (NULL == fresh_pk)
@@ -1149,7 +1149,7 @@ melt_run (void *cls,
     GNUNET_assert (age_restricted ==
                    (NULL != age_commitment_proof));
 
-    rms->rmh = TALER_EXCHANGE_melt (is->exchange,
+    rms->rmh = TALER_EXCHANGE_melt (exchange,
                                     &rms->rms,
                                     &rms->refresh_data,
                                     &melt_cb,
@@ -1181,10 +1181,8 @@ melt_cleanup (void *cls,
   (void) cmd;
   if (NULL != rms->rmh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                rms->is->ip,
-                rms->is->commands[rms->is->ip].label);
+    TALER_TESTING_command_incomplete (rms->is,
+                                      cmd->label);
     TALER_EXCHANGE_melt_cancel (rms->rmh);
     rms->rmh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_refund.c 
b/src/testing/testing_api_cmd_refund.c
index d41700d1..a3ebd2e8 100644
--- a/src/testing/testing_api_cmd_refund.c
+++ b/src/testing/testing_api_cmd_refund.c
@@ -83,23 +83,12 @@ refund_cb (void *cls,
 {
   struct RefundState *rs = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
-  struct TALER_TESTING_Command *refund_cmd;
 
-  refund_cmd = &rs->is->commands[rs->is->ip];
   rs->rh = NULL;
   if (rs->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                hr->ec,
-                refund_cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rs->is);
+    TALER_TESTING_unexpected_status (rs->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (rs->is);
@@ -127,17 +116,17 @@ refund_run (void *cls,
   const struct TALER_MerchantPrivateKeyP *merchant_priv;
   const struct TALER_TESTING_Command *coin_cmd;
 
-  rs->exchange = is->exchange;
+  rs->exchange = TALER_TESTING_get_exchange (is);
+  if (NULL == rs->exchange)
+    return;
   rs->is = is;
-
   if (GNUNET_OK !=
       TALER_string_to_amount (rs->refund_amount,
                               &refund_amount))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to parse amount `%s' at %u/%s\n",
+                "Failed to parse amount `%s' at %s\n",
                 rs->refund_amount,
-                is->ip,
                 cmd->label);
     TALER_TESTING_interpreter_fail (is);
     return;
@@ -210,10 +199,8 @@ refund_cleanup (void *cls,
 
   if (NULL != rs->rh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                rs->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (rs->is,
+                                      cmd->label);
     TALER_EXCHANGE_refund_cancel (rs->rh);
     rs->rh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_attest.c 
b/src/testing/testing_api_cmd_reserve_attest.c
index eec3eae5..77166d2b 100644
--- a/src/testing/testing_api_cmd_reserve_attest.c
+++ b/src/testing/testing_api_cmd_reserve_attest.c
@@ -152,7 +152,11 @@ attest_run (void *cls,
 {
   struct AttestState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -175,7 +179,7 @@ attest_run (void *cls,
   }
   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
                                       &ss->reserve_pub.eddsa_pub);
-  ss->rsh = TALER_EXCHANGE_reserves_attest (is->exchange,
+  ss->rsh = TALER_EXCHANGE_reserves_attest (exchange,
                                             ss->reserve_priv,
                                             ss->attrs_len,
                                             ss->attrs,
@@ -199,10 +203,8 @@ attest_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_attest_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_close.c 
b/src/testing/testing_api_cmd_reserve_close.c
index 63d51160..b2929f16 100644
--- a/src/testing/testing_api_cmd_reserve_close.c
+++ b/src/testing/testing_api_cmd_reserve_close.c
@@ -142,7 +142,11 @@ close_run (void *cls,
 {
   struct CloseState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -165,7 +169,7 @@ close_run (void *cls,
   }
   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
                                       &ss->reserve_pub.eddsa_pub);
-  ss->rsh = TALER_EXCHANGE_reserves_close (is->exchange,
+  ss->rsh = TALER_EXCHANGE_reserves_close (exchange,
                                            ss->reserve_priv,
                                            ss->target_account,
                                            &reserve_close_cb,
@@ -188,10 +192,8 @@ close_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_close_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_get.c 
b/src/testing/testing_api_cmd_reserve_get.c
index 22c29a3b..a4345940 100644
--- a/src/testing/testing_api_cmd_reserve_get.c
+++ b/src/testing/testing_api_cmd_reserve_get.c
@@ -178,7 +178,11 @@ status_run (void *cls,
 {
   struct StatusState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -193,7 +197,7 @@ status_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-  ss->rsh = TALER_EXCHANGE_reserves_get (is->exchange,
+  ss->rsh = TALER_EXCHANGE_reserves_get (exchange,
                                          ss->reserve_pubp,
                                          ss->timeout,
                                          &reserve_status_cb,
@@ -221,10 +225,8 @@ status_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_get_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_get_attestable.c 
b/src/testing/testing_api_cmd_reserve_get_attestable.c
index 3b400a36..75783e04 100644
--- a/src/testing/testing_api_cmd_reserve_get_attestable.c
+++ b/src/testing/testing_api_cmd_reserve_get_attestable.c
@@ -125,7 +125,11 @@ get_attestable_run (void *cls,
   const struct TALER_TESTING_Command *ref_reserve;
   const struct TALER_ReservePrivateKeyP *reserve_priv;
   const struct TALER_ReservePublicKeyP *reserve_pub;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   ref_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -158,7 +162,7 @@ get_attestable_run (void *cls,
     }
     ss->reserve_pub = *reserve_pub;
   }
-  ss->rgah = TALER_EXCHANGE_reserves_get_attestable (is->exchange,
+  ss->rgah = TALER_EXCHANGE_reserves_get_attestable (exchange,
                                                      &ss->reserve_pub,
                                                      
&reserve_get_attestable_cb,
                                                      ss);
@@ -180,10 +184,8 @@ get_attestable_cleanup (void *cls,
 
   if (NULL != ss->rgah)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_get_attestable_cancel (ss->rgah);
     ss->rgah = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_history.c 
b/src/testing/testing_api_cmd_reserve_history.c
index beba23f1..e9cc11a6 100644
--- a/src/testing/testing_api_cmd_reserve_history.c
+++ b/src/testing/testing_api_cmd_reserve_history.c
@@ -78,25 +78,56 @@ struct HistoryState
 };
 
 
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+  /**
+   * Reserve public key we are looking at.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Length of the @e history array.
+   */
+  unsigned int history_length;
+
+  /**
+   * Array of history items to match.
+   */
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+  /**
+   * Array of @e history_length of matched entries.
+   */
+  bool *found;
+
+  /**
+   * Set to true if an entry could not be found.
+   */
+  bool failure;
+};
+
+
 /**
  * Check if @a cmd changed the reserve, if so, find the
- * entry in @a history and set the respective index in @a found
- * to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
  *
- * @param reserve_pub public key of the reserve for which we have the @a 
history
+ * @param cls our `struct AnalysisContext *`
  * @param cmd command to analyze for impact on history
- * @param history_length number of entries in @a history and @a found
- * @param history history to check
- * @param[in,out] found array to update
- * @return #GNUNET_OK if @a cmd action on reserve was found in @a history
  */
-static enum GNUNET_GenericReturnValue
-analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
-                 const struct TALER_TESTING_Command *cmd,
-                 unsigned int history_length,
-                 const struct TALER_EXCHANGE_ReserveHistoryEntry *history,
-                 bool *found)
+static void
+analyze_command (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
 {
+  struct AnalysisContext *ac = cls;
+  const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
+  unsigned int history_length = ac->history_length;
+  bool *found = ac->found;
+
   if (TALER_TESTING_cmd_is_batch (cmd))
   {
     struct TALER_TESTING_Command *cur;
@@ -108,28 +139,26 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
                                             &bcmd))
     {
       GNUNET_break (0);
-      return GNUNET_SYSERR;
+      ac->failure = true;
+      return;
     }
     for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
     {
       struct TALER_TESTING_Command *step = &(*bcmd)[i];
 
-      if (GNUNET_OK !=
-          analyze_command (reserve_pub,
-                           step,
-                           history_length,
-                           history,
-                           found))
+      analyze_command (ac,
+                       step);
+      if (ac->failure)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Entry for batch step `%s' missing in history\n",
                     step->label);
-        return GNUNET_SYSERR;
+        return;
       }
       if (step == cur)
         break; /* if *we* are in a batch, make sure not to analyze commands 
past 'now' */
     }
-    return GNUNET_OK;
+    return;
   }
 
   {
@@ -138,11 +167,11 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
     if (GNUNET_OK !=
         TALER_TESTING_get_trait_reserve_pub (cmd,
                                              &rp))
-      return GNUNET_OK; /* command does nothing for reserves */
+      return; /* command does nothing for reserves */
     if (0 !=
         GNUNET_memcmp (rp,
                        reserve_pub))
-      return GNUNET_OK; /* command affects some _other_ reserve */
+      return; /* command affects some _other_ reserve */
     for (unsigned int j = 0; true; j++)
     {
       const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
@@ -156,9 +185,9 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
         /* NOTE: only for debugging... */
         if (0 == j)
           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                      "Command `%s' has the reserve_pub trait, but does not 
reserve history trait\n",
+                      "Command `%s' has the reserve_pub, but lacks reserve 
history trait\n",
                       cmd->label);
-        return GNUNET_OK; /* command does nothing for reserves */
+        return; /* command does nothing for reserves */
       }
       for (unsigned int i = 0; i<history_length; i++)
       {
@@ -179,7 +208,8 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
                     "Command `%s' reserve history entry #%u not found\n",
                     cmd->label,
                     j);
-        return GNUNET_SYSERR;
+        ac->failure = true;
+        return;
       }
     }
   }
@@ -200,15 +230,19 @@ reserve_history_cb (void *cls,
   struct HistoryState *ss = cls;
   struct TALER_TESTING_Interpreter *is = ss->is;
   struct TALER_Amount eb;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   ss->rsh = NULL;
+  if (NULL == exchange)
+    return;
   if (MHD_HTTP_OK == rs->hr.http_status)
   {
     const struct TALER_EXCHANGE_Keys *keys;
     const struct TALER_EXCHANGE_GlobalFee *gf;
 
     ss->reserve_history.type = TALER_EXCHANGE_RTT_HISTORY;
-    keys = TALER_EXCHANGE_get_keys (ss->is->exchange);
+    keys = TALER_EXCHANGE_get_keys (exchange);
     GNUNET_assert (NULL != keys);
     gf = TALER_EXCHANGE_get_global_fee (keys,
                                         rs->ts);
@@ -254,44 +288,42 @@ reserve_history_cb (void *cls,
   }
   {
     bool found[rs->details.ok.history_len];
+    struct AnalysisContext ac = {
+      .reserve_pub = &ss->reserve_pub,
+      .history = rs->details.ok.history,
+      .history_length = rs->details.ok.history_len,
+      .found = found
+    };
 
     memset (found,
             0,
             sizeof (found));
-    for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
+    TALER_TESTING_iterate (is,
+                           true,
+                           &analyze_command,
+                           &ac);
+    if (ac.failure)
     {
-      struct TALER_TESTING_Command *cmd = &is->commands[i];
-
-      if (GNUNET_OK !=
-          analyze_command (&ss->reserve_pub,
-                           cmd,
-                           rs->details.ok.history_len,
-                           rs->details.ok.history,
-                           found))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Entry for command `%s' missing in history\n",
-                    cmd->label);
-        json_dumpf (rs->hr.reply,
-                    stderr,
-                    JSON_INDENT (2));
-        TALER_TESTING_interpreter_fail (ss->is);
-        return;
-      }
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
     }
     for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
-      if (! found[i])
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "History entry at index %u of type %d not justified by 
command history\n",
-                    i,
-                    rs->details.ok.history[i].type);
-        json_dumpf (rs->hr.reply,
-                    stderr,
-                    JSON_INDENT (2));
-        TALER_TESTING_interpreter_fail (ss->is);
-        return;
-      }
+    {
+      if (found[i])
+        continue;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "History entry at index %u of type %d not justified by 
command history\n",
+                  i,
+                  rs->details.ok.history[i].type);
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
   }
   TALER_TESTING_interpreter_next (is);
 }
@@ -311,7 +343,11 @@ history_run (void *cls,
 {
   struct HistoryState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -333,7 +369,7 @@ history_run (void *cls,
   }
   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
                                       &ss->reserve_pub.eddsa_pub);
-  ss->rsh = TALER_EXCHANGE_reserves_history (is->exchange,
+  ss->rsh = TALER_EXCHANGE_reserves_history (exchange,
                                              ss->reserve_priv,
                                              &reserve_history_cb,
                                              ss);
@@ -388,10 +424,8 @@ history_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_open.c 
b/src/testing/testing_api_cmd_reserve_open.c
index cc0e649d..6a6247b8 100644
--- a/src/testing/testing_api_cmd_reserve_open.c
+++ b/src/testing/testing_api_cmd_reserve_open.c
@@ -165,7 +165,11 @@ open_run (void *cls,
   struct OpenState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
   struct TALER_EXCHANGE_PurseDeposit cp[GNUNET_NZL (ss->cpl)];
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -252,7 +256,7 @@ open_run (void *cls,
     cpi->h_denom_pub = denom_pub->h_key;
   }
   ss->rsh = TALER_EXCHANGE_reserves_open (
-    is->exchange,
+    exchange,
     ss->reserve_priv,
     &ss->reserve_pay,
     ss->cpl,
@@ -279,10 +283,8 @@ open_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_open_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_purse.c 
b/src/testing/testing_api_cmd_reserve_purse.c
index f01741b0..79530803 100644
--- a/src/testing/testing_api_cmd_reserve_purse.c
+++ b/src/testing/testing_api_cmd_reserve_purse.c
@@ -153,16 +153,8 @@ purse_cb (void *cls,
   ds->reserve_sig = *dr->reserve_sig;
   if (ds->expected_response_code != dr->hr.http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                dr->hr.http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (dr->hr.reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status);
     return;
   }
   switch (dr->hr.http_status)
@@ -192,8 +184,12 @@ purse_run (void *cls,
   struct ReservePurseState *ds = cls;
   const struct TALER_ReservePrivateKeyP *reserve_priv;
   const struct TALER_TESTING_Command *ref;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
   (void) cmd;
+  if (NULL == exchange)
+    return;
   ds->is = is;
   ref = TALER_TESTING_interpreter_lookup_command (ds->is,
                                                   ds->reserve_ref);
@@ -221,8 +217,21 @@ purse_run (void *cls,
 
   {
     char *payto_uri;
-
-    payto_uri = TALER_reserve_make_payto (is->exchange_url,
+    const char *exchange_url;
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+    payto_uri = TALER_reserve_make_payto (exchange_url,
                                           &ds->reserve_pub);
     TALER_payto_hash (payto_uri,
                       &ds->h_payto);
@@ -236,7 +245,7 @@ purse_run (void *cls,
                    GNUNET_JSON_from_timestamp (ds->purse_expiration)));
   ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
   ds->dh = TALER_EXCHANGE_purse_create_with_merge (
-    is->exchange,
+    exchange,
     &ds->reserve_priv,
     &ds->purse_priv,
     &ds->merge_priv,
@@ -273,10 +282,8 @@ purse_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_purse_create_with_merge_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_reserve_status.c 
b/src/testing/testing_api_cmd_reserve_status.c
index a1b7aaef..7e7b45cd 100644
--- a/src/testing/testing_api_cmd_reserve_status.c
+++ b/src/testing/testing_api_cmd_reserve_status.c
@@ -69,26 +69,56 @@ struct StatusState
   struct TALER_TESTING_Interpreter *is;
 };
 
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+  /**
+   * Reserve public key we are looking at.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Length of the @e history array.
+   */
+  unsigned int history_length;
+
+  /**
+   * Array of history items to match.
+   */
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+  /**
+   * Array of @e history_length of matched entries.
+   */
+  bool *found;
+
+  /**
+   * Set to true if an entry could not be found.
+   */
+  bool failure;
+};
+
 
 /**
  * Check if @a cmd changed the reserve, if so, find the
- * entry in @a history and set the respective index in @a found
- * to #GNUNET_YES. If the entry is not found, return #GNUNET_SYSERR.
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
  *
- * @param reserve_pub public key of the reserve for which we have the @a 
history
+ * @param cls our `struct AnalysisContext *`
  * @param cmd command to analyze for impact on history
- * @param history_length number of entries in @a history and @a found
- * @param history history to check
- * @param[in,out] found array to update
- * @return #GNUNET_OK if @a cmd action on reserve was found in @a history
  */
-static enum GNUNET_GenericReturnValue
-analyze_command (const struct TALER_ReservePublicKeyP *reserve_pub,
-                 const struct TALER_TESTING_Command *cmd,
-                 unsigned int history_length,
-                 const struct TALER_EXCHANGE_ReserveHistoryEntry *history,
-                 bool *found)
+static void
+analyze_command (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
 {
+  struct AnalysisContext *ac = cls;
+  const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
+  unsigned int history_length = ac->history_length;
+  bool *found = ac->found;
+
   if (TALER_TESTING_cmd_is_batch (cmd))
   {
     struct TALER_TESTING_Command *cur;
@@ -100,7 +130,8 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
                                             &bcmd))
     {
       GNUNET_break (0);
-      return GNUNET_SYSERR;
+      ac->failure = true;
+      return;
     }
     for (unsigned int i = 0; NULL != (*bcmd)[i].label; i++)
     {
@@ -108,33 +139,30 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
 
       if (step == cur)
         break; /* if *we* are in a batch, make sure not to analyze commands 
past 'now' */
-      if (GNUNET_OK !=
-          analyze_command (reserve_pub,
-                           step,
-                           history_length,
-                           history,
-                           found))
+      analyze_command (ac,
+                       step);
+      if (ac->failure)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Entry for batch step `%s' missing in history\n",
                     step->label);
-        return GNUNET_SYSERR;
+        return;
       }
     }
-    return GNUNET_OK;
+    return;
   }
-  else
+
   {
     const struct TALER_ReservePublicKeyP *rp;
 
     if (GNUNET_OK !=
         TALER_TESTING_get_trait_reserve_pub (cmd,
                                              &rp))
-      return GNUNET_OK; /* command does nothing for reserves */
+      return; /* command does nothing for reserves */
     if (0 !=
         GNUNET_memcmp (rp,
                        reserve_pub))
-      return GNUNET_OK; /* command affects some _other_ reserve */
+      return; /* command affects some _other_ reserve */
     for (unsigned int j = 0; true; j++)
     {
       const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
@@ -149,7 +177,7 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                     "Command `%s' has the reserve_pub trait, but does not 
reserve history trait\n",
                     cmd->label);
-        return GNUNET_OK; /* command does nothing for reserves */
+        return; /* command does nothing for reserves */
       }
       for (unsigned int i = 0; i<history_length; i++)
       {
@@ -170,7 +198,8 @@ analyze_command (const struct TALER_ReservePublicKeyP 
*reserve_pub,
                     "Command `%s' reserve history entry #%u not found\n",
                     cmd->label,
                     j);
-        return GNUNET_SYSERR;
+        ac->failure = true;
+        return;
       }
     }
   }
@@ -230,44 +259,42 @@ reserve_status_cb (void *cls,
   }
   {
     bool found[rs->details.ok.history_len];
+    struct AnalysisContext ac = {
+      .reserve_pub = &ss->reserve_pub,
+      .history = rs->details.ok.history,
+      .history_length = rs->details.ok.history_len,
+      .found = found
+    };
 
     memset (found,
             0,
             sizeof (found));
-    for (unsigned int i = 0; i<= (unsigned int) is->ip; i++)
+    TALER_TESTING_iterate (is,
+                           true,
+                           &analyze_command,
+                           &ac);
+    if (ac.failure)
     {
-      struct TALER_TESTING_Command *cmd = &is->commands[i];
-
-      if (GNUNET_OK !=
-          analyze_command (&ss->reserve_pub,
-                           cmd,
-                           rs->details.ok.history_len,
-                           rs->details.ok.history,
-                           found))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Entry for command `%s' missing in history\n",
-                    cmd->label);
-        json_dumpf (rs->hr.reply,
-                    stderr,
-                    JSON_INDENT (2));
-        TALER_TESTING_interpreter_fail (ss->is);
-        return;
-      }
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
     }
     for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
-      if (! found[i])
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "History entry at index %u of type %d not justified by 
command status\n",
-                    i,
-                    rs->details.ok.history[i].type);
-        json_dumpf (rs->hr.reply,
-                    stderr,
-                    JSON_INDENT (2));
-        TALER_TESTING_interpreter_fail (ss->is);
-        return;
-      }
+    {
+      if (found[i])
+        continue;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "History entry at index %u of type %d not justified by 
command status\n",
+                  i,
+                  rs->details.ok.history[i].type);
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
   }
   TALER_TESTING_interpreter_next (is);
 }
@@ -287,7 +314,11 @@ status_run (void *cls,
 {
   struct StatusState *ss = cls;
   const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  if (NULL == exchange)
+    return;
   ss->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (is,
@@ -310,7 +341,7 @@ status_run (void *cls,
   }
   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
                                       &ss->reserve_pub.eddsa_pub);
-  ss->rsh = TALER_EXCHANGE_reserves_status (is->exchange,
+  ss->rsh = TALER_EXCHANGE_reserves_status (exchange,
                                             ss->reserve_priv,
                                             &reserve_status_cb,
                                             ss);
@@ -332,10 +363,8 @@ status_cleanup (void *cls,
 
   if (NULL != ss->rsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ss->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
     TALER_EXCHANGE_reserves_status_cancel (ss->rsh);
     ss->rsh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_revoke_denom_key.c 
b/src/testing/testing_api_cmd_revoke_denom_key.c
index 8afd4f20..8bbfda63 100644
--- a/src/testing/testing_api_cmd_revoke_denom_key.c
+++ b/src/testing/testing_api_cmd_revoke_denom_key.c
@@ -78,16 +78,8 @@ success_cb (
   rs->kh = NULL;
   if (rs->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                rs->is->commands[rs->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rs->is);
+    TALER_TESTING_unexpected_status (rs->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (rs->is);
@@ -159,7 +151,23 @@ revoke_run (void *cls,
   const struct TALER_TESTING_Command *coin_cmd;
   const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
   struct TALER_MasterSignatureP master_sig;
+  const char *exchange_url;
 
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   rs->is = is;
   /* Get denom pub from trait */
   coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
@@ -186,13 +194,27 @@ revoke_run (void *cls,
   }
   else
   {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
     TALER_exchange_offline_denomination_revoke_sign (&denom_pub->h_key,
-                                                     &is->master_priv,
+                                                     master_priv,
                                                      &master_sig);
   }
   rs->kh = TALER_EXCHANGE_management_revoke_denomination_key (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     &denom_pub->h_key,
     &master_sig,
     &success_cb,
diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c 
b/src/testing/testing_api_cmd_revoke_sign_key.c
index 3b869312..477ffbe8 100644
--- a/src/testing/testing_api_cmd_revoke_sign_key.c
+++ b/src/testing/testing_api_cmd_revoke_sign_key.c
@@ -78,16 +78,8 @@ success_cb (
   rs->kh = NULL;
   if (rs->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                rs->is->commands[rs->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (rs->is);
+    TALER_TESTING_unexpected_status (rs->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (rs->is);
@@ -159,7 +151,23 @@ revoke_run (void *cls,
   const struct TALER_TESTING_Command *coin_cmd;
   const struct TALER_ExchangePublicKeyP *exchange_pub;
   struct TALER_MasterSignatureP master_sig;
+  const char *exchange_url;
 
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   rs->is = is;
   /* Get sign pub from trait */
   coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
@@ -186,13 +194,27 @@ revoke_run (void *cls,
   }
   else
   {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
     TALER_exchange_offline_signkey_revoke_sign (exchange_pub,
-                                                &is->master_priv,
+                                                master_priv,
                                                 &master_sig);
   }
   rs->kh = TALER_EXCHANGE_management_revoke_signing_key (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     exchange_pub,
     &master_sig,
     &success_cb,
diff --git a/src/testing/testing_api_cmd_rewind.c 
b/src/testing/testing_api_cmd_rewind.c
deleted file mode 100644
index 93b38d3c..00000000
--- a/src/testing/testing_api_cmd_rewind.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-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 testing/testing_api_cmd_rewind.c
- * @brief command to rewind the instruction pointer.
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchange_service.h"
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "rewind" CMD.
- */
-struct RewindIpState
-{
-  /**
-   * Instruction pointer to set into the interpreter.
-   */
-  const char *target_label;
-
-  /**
-   * How many times this set should take place.  However, this value lives at
-   * the calling process, and this CMD is only in charge of checking and
-   * decremeting it.
-   */
-  unsigned int counter;
-};
-
-
-/**
- * Seek for the @a target command in @a batch (and rewind to it
- * if successful).
- *
- * @param is the interpreter state (for failures)
- * @param cmd batch to search for @a target
- * @param target command to search for
- * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
- *         #GNUNET_SYSERR if target is in the future and we failed
- */
-static enum GNUNET_GenericReturnValue
-seek_batch (struct TALER_TESTING_Interpreter *is,
-            const struct TALER_TESTING_Command *cmd,
-            const struct TALER_TESTING_Command *target)
-{
-  unsigned int new_ip;
-  struct TALER_TESTING_Command **batch;
-  struct TALER_TESTING_Command *current;
-  struct TALER_TESTING_Command *icmd;
-  struct TALER_TESTING_Command *match;
-
-  current = TALER_TESTING_cmd_batch_get_current (cmd);
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_TESTING_get_trait_batch_cmds (cmd,
-                                                     &batch));
-  match = NULL;
-  for (new_ip = 0;
-       NULL != (icmd = &(*batch)[new_ip]);
-       new_ip++)
-  {
-    if (current == target)
-      current = NULL;
-    if (icmd == target)
-    {
-      match = icmd;
-      break;
-    }
-    if (TALER_TESTING_cmd_is_batch (icmd))
-    {
-      int ret = seek_batch (is,
-                            icmd,
-                            target);
-      if (GNUNET_SYSERR == ret)
-        return GNUNET_SYSERR; /* failure! */
-      if (GNUNET_OK == ret)
-      {
-        match = icmd;
-        break;
-      }
-    }
-  }
-  if (NULL == current)
-  {
-    /* refuse to jump forward */
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
-    return GNUNET_SYSERR;
-  }
-  if (NULL == match)
-    return GNUNET_NO; /* not found */
-  TALER_TESTING_cmd_batch_set_current (cmd,
-                                       new_ip);
-  return GNUNET_OK;
-}
-
-
-/**
- * Run the "rewind" CMD.
- *
- * @param cls closure.
- * @param cmd command being executed now.
- * @param is the interpreter state.
- */
-static void
-rewind_ip_run (void *cls,
-               const struct TALER_TESTING_Command *cmd,
-               struct TALER_TESTING_Interpreter *is)
-{
-  struct RewindIpState *ris = cls;
-  const struct TALER_TESTING_Command *target;
-  unsigned int new_ip;
-
-  (void) cmd;
-  if (0 == ris->counter)
-  {
-    TALER_TESTING_interpreter_next (is);
-    return;
-  }
-  target
-    = TALER_TESTING_interpreter_lookup_command (is,
-                                                ris->target_label);
-  if (NULL == target)
-  {
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
-    return;
-  }
-  ris->counter--;
-  for (new_ip = 0;
-       NULL != is->commands[new_ip].label;
-       new_ip++)
-  {
-    const struct TALER_TESTING_Command *cmd = &is->commands[new_ip];
-
-    if (cmd == target)
-      break;
-    if (TALER_TESTING_cmd_is_batch (cmd))
-    {
-      int ret = seek_batch (is,
-                            cmd,
-                            target);
-      if (GNUNET_SYSERR == ret)
-        return;   /* failure! */
-      if (GNUNET_OK == ret)
-        break;
-    }
-  }
-  if (new_ip > (unsigned int) is->ip)
-  {
-    /* refuse to jump forward */
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
-    return;
-  }
-  is->ip = new_ip - 1; /* -1 because the next function will advance by one */
-  TALER_TESTING_interpreter_next (is);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_rewind_ip (const char *label,
-                             const char *target_label,
-                             unsigned int counter)
-{
-  struct RewindIpState *ris;
-
-  ris = GNUNET_new (struct RewindIpState);
-  ris->target_label = target_label;
-  ris->counter = counter;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = ris,
-      .label = label,
-      .run = &rewind_ip_run
-    };
-
-    return cmd;
-  }
-}
diff --git a/src/testing/testing_api_cmd_run_fakebank.c 
b/src/testing/testing_api_cmd_run_fakebank.c
new file mode 100644
index 00000000..f9a6b9b6
--- /dev/null
+++ b/src/testing/testing_api_cmd_run_fakebank.c
@@ -0,0 +1,211 @@
+/*
+  This file is part of TALER
+  (C) 2023 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 testing/testing_api_cmd_run_fakebank.c
+ * @brief Command to run fakebank in-process
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "run fakebank" CMD.
+ */
+struct RunFakebankState
+{
+
+  /**
+   * Our interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Handle to the fakebank we are running.
+   */
+  struct TALER_FAKEBANK_Handle *fakebank;
+
+  /**
+   * URL of the bank.
+   */
+  char *bank_url;
+
+  /**
+   * Currency to use.
+   */
+  char *currency;
+
+  /**
+   * Data for access control.
+   */
+  struct TALER_BANK_AuthenticationData ba;
+
+  /**
+   * Port to use.
+   */
+  uint16_t port;
+};
+
+
+/**
+ * Run the "get_exchange" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+run_fakebank_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct RunFakebankState *rfs = cls;
+
+  (void) cmd;
+  rfs->fakebank = TALER_FAKEBANK_start (rfs->port,
+                                        rfs->currency);
+  if (NULL == rfs->fakebank)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+run_fakebank_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct RunFakebankState *rfs = cls;
+
+  if (NULL != rfs->fakebank)
+  {
+    TALER_FAKEBANK_stop (rfs->fakebank);
+    rfs->fakebank = NULL;
+  }
+  GNUNET_free (rfs->ba.wire_gateway_url);
+  GNUNET_free (rfs->bank_url);
+  GNUNET_free (rfs);
+}
+
+
+/**
+ * Offer internal data to a "run_fakebank" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+run_fakebank_traits (void *cls,
+                     const void **ret,
+                     const char *trait,
+                     unsigned int index)
+{
+  struct RunFakebankState *rfs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_bank_auth_data (&rfs->ba),
+    TALER_TESTING_make_trait_fakebank (rfs->fakebank),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  const char *exchange_account_section)
+{
+  struct RunFakebankState *rfs;
+  unsigned long long fakebank_port;
+  char *exchange_payto_uri;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_number (cfg,
+                                             "BANK",
+                                             "HTTP_PORT",
+                                             &fakebank_port))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "BANK",
+                               "HTTP_PORT");
+    GNUNET_assert (0);
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             exchange_account_section,
+                                             "PAYTO_URI",
+                                             &exchange_payto_uri))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               exchange_account_section,
+                               "PAYTO_URI");
+    GNUNET_assert (0);
+  }
+  rfs = GNUNET_new (struct RunFakebankState);
+  rfs->port = (uint16_t) fakebank_port;
+  GNUNET_asprintf (&rfs->bank_url,
+                   "http://localhost:%u/";,
+                   (unsigned int) rfs->port);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_config_get_currency (cfg,
+                                            &rfs->currency));
+  {
+    char *exchange_xtalerbank_account;
+
+    exchange_xtalerbank_account
+      = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
+    GNUNET_assert (NULL != exchange_xtalerbank_account);
+    GNUNET_asprintf (&rfs->ba.wire_gateway_url,
+                     "http://localhost:%u/%s/";,
+                     (unsigned int) fakebank_port,
+                     exchange_xtalerbank_account);
+    GNUNET_free (exchange_xtalerbank_account);
+  }
+  rfs->ba.method = TALER_BANK_AUTH_NONE;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = rfs,
+      .label = label,
+      .run = &run_fakebank_run,
+      .cleanup = &run_fakebank_cleanup,
+      .traits = &run_fakebank_traits,
+      .name = "fakebank"
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_serialize_keys.c 
b/src/testing/testing_api_cmd_serialize_keys.c
index ef912bf5..9e5a25c4 100644
--- a/src/testing/testing_api_cmd_serialize_keys.c
+++ b/src/testing/testing_api_cmd_serialize_keys.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2018 Taler Systems SA
+  (C) 2018-2023 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
@@ -45,35 +45,6 @@ struct SerializeKeysState
 };
 
 
-/**
- * Internal state for a connect-with-state CMD.
- */
-struct ConnectWithStateState
-{
-
-  /**
-   * Reference to a CMD that offers a serialized key-state
-   * that will be used in the reconnection.
-   */
-  const char *state_reference;
-
-  /**
-   * If set to GNUNET_YES, then the /keys callback has already
-   * been passed the control to the next CMD.  This is necessary
-   * because it is not uncommon that the /keys callback gets
-   * invoked multiple times, and without this flag, we would keep
-   * going "next" CMD upon every invocation (causing impredictable
-   * behaviour as for the instruction pointer.)
-   */
-  unsigned int consumed;
-
-  /**
-   * Interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-};
-
-
 /**
  * Run the command.
  *
@@ -87,16 +58,20 @@ serialize_keys_run (void *cls,
                     struct TALER_TESTING_Interpreter *is)
 {
   struct SerializeKeysState *sks = cls;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  sks->keys = TALER_EXCHANGE_serialize_data (is->exchange);
+  if (NULL == exchange)
+    return;
+  sks->keys = TALER_EXCHANGE_serialize_data (exchange);
   if (NULL == sks->keys)
+  {
+    GNUNET_break (0);
     TALER_TESTING_interpreter_fail (is);
-
-  sks->exchange_url = GNUNET_strdup
-                        (TALER_EXCHANGE_get_base_url (is->exchange));
-  TALER_EXCHANGE_disconnect (is->exchange);
-  is->exchange = NULL;
-  is->working = GNUNET_NO;
+  }
+  sks->exchange_url
+    = GNUNET_strdup (
+        TALER_EXCHANGE_get_base_url (exchange));
   TALER_TESTING_interpreter_next (is);
 }
 
@@ -114,9 +89,7 @@ serialize_keys_cleanup (void *cls,
   struct SerializeKeysState *sks = cls;
 
   if (NULL != sks->keys)
-  {
     json_decref (sks->keys);
-  }
   GNUNET_free (sks->exchange_url);
   GNUNET_free (sks);
 }
@@ -140,8 +113,7 @@ serialize_keys_traits (void *cls,
   struct SerializeKeysState *sks = cls;
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_exchange_keys (sks->keys),
-    TALER_TESTING_make_trait_exchange_url (
-      (const char **) &sks->exchange_url),
+    TALER_TESTING_make_trait_exchange_url (sks->exchange_url),
     TALER_TESTING_trait_end ()
   };
 
@@ -152,87 +124,6 @@ serialize_keys_traits (void *cls,
 }
 
 
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-connect_with_state_run (void *cls,
-                        const struct TALER_TESTING_Command *cmd,
-                        struct TALER_TESTING_Interpreter *is)
-{
-  struct ConnectWithStateState *cwss = cls;
-  const struct TALER_TESTING_Command *state_cmd;
-  const json_t *serialized_keys;
-  const char **exchange_url;
-
-  /* This command usually gets rescheduled after serialized
-   * reconnection.  */
-  if (GNUNET_YES == cwss->consumed)
-  {
-    TALER_TESTING_interpreter_next (is);
-    return;
-  }
-
-  cwss->is = is;
-  state_cmd = TALER_TESTING_interpreter_lookup_command (is,
-                                                        cwss->state_reference);
-
-  /* Command providing serialized keys not found.  */
-  if (NULL == state_cmd)
-  {
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
-    return;
-  }
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_TESTING_get_trait_exchange_keys (state_cmd,
-                                                        &serialized_keys));
-  {
-    char *dump;
-
-    dump = json_dumps (serialized_keys,
-                       JSON_INDENT (1));
-    TALER_LOG_DEBUG ("Serialized key-state: %s\n",
-                     dump);
-    free (dump);
-  }
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_TESTING_get_trait_exchange_url (state_cmd,
-                                                       &exchange_url));
-  is->exchange = TALER_EXCHANGE_connect (is->ctx,
-                                         *exchange_url,
-                                         &TALER_TESTING_cert_cb,
-                                         cwss,
-                                         TALER_EXCHANGE_OPTION_DATA,
-                                         serialized_keys,
-                                         TALER_EXCHANGE_OPTION_END);
-  cwss->consumed = GNUNET_YES;
-}
-
-
-/**
- * Cleanup the state of a "connect with state" CMD.  Just
- * a placeholder to avoid jumping on an invalid address.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-connect_with_state_cleanup (void *cls,
-                            const struct TALER_TESTING_Command *cmd)
-{
-  struct ConnectWithStateState *cwss = cls;
-
-  GNUNET_free (cwss);
-}
-
-
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_serialize_keys (const char *label)
 {
@@ -251,25 +142,3 @@ TALER_TESTING_cmd_serialize_keys (const char *label)
     return cmd;
   }
 }
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_connect_with_state (const char *label,
-                                      const char *state_reference)
-{
-  struct ConnectWithStateState *cwss;
-
-  cwss = GNUNET_new (struct ConnectWithStateState);
-  cwss->state_reference = state_reference;
-  cwss->consumed = GNUNET_NO;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cwss,
-      .label = label,
-      .run = connect_with_state_run,
-      .cleanup = connect_with_state_cleanup
-    };
-
-    return cmd;
-  }
-}
diff --git a/src/testing/testing_api_cmd_set_officer.c 
b/src/testing/testing_api_cmd_set_officer.c
index 1c0495f3..f2464c8a 100644
--- a/src/testing/testing_api_cmd_set_officer.c
+++ b/src/testing/testing_api_cmd_set_officer.c
@@ -97,16 +97,8 @@ set_officer_cb (void *cls,
   ds->dh = NULL;
   if (MHD_HTTP_NO_CONTENT != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -128,8 +120,24 @@ set_officer_run (void *cls,
   struct SetOfficerState *ds = cls;
   struct GNUNET_TIME_Timestamp now;
   struct TALER_MasterSignatureP master_sig;
+  const char *exchange_url;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
   if (NULL == ds->ref_cmd)
@@ -161,16 +169,33 @@ set_officer_run (void *cls,
     ds->officer_pub = *officer_pub;
     ds->officer_priv = *officer_priv;
   }
-  TALER_exchange_offline_aml_officer_status_sign (&ds->officer_pub,
-                                                  ds->name,
-                                                  now,
-                                                  ds->is_active,
-                                                  ds->read_only,
-                                                  &is->master_priv,
-                                                  &master_sig);
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
+
+    TALER_exchange_offline_aml_officer_status_sign (&ds->officer_pub,
+                                                    ds->name,
+                                                    now,
+                                                    ds->is_active,
+                                                    ds->read_only,
+                                                    master_priv,
+                                                    &master_sig);
+  }
   ds->dh = TALER_EXCHANGE_management_update_aml_officer (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     &ds->officer_pub,
     ds->name,
     now,
@@ -203,10 +228,8 @@ set_officer_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_update_aml_officer_cancel (ds->dh);
     ds->dh = NULL;
   }
@@ -234,7 +257,7 @@ set_officer_traits (void *cls,
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_officer_pub (&ws->officer_pub),
     TALER_TESTING_make_trait_officer_priv (&ws->officer_priv),
-    TALER_TESTING_make_trait_officer_name (&ws->name),
+    TALER_TESTING_make_trait_officer_name (ws->name),
     TALER_TESTING_trait_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_set_wire_fee.c 
b/src/testing/testing_api_cmd_set_wire_fee.c
index f0f76a87..b2002e0f 100644
--- a/src/testing/testing_api_cmd_set_wire_fee.c
+++ b/src/testing/testing_api_cmd_set_wire_fee.c
@@ -89,16 +89,8 @@ wire_add_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -123,8 +115,24 @@ wire_add_run (void *cls,
   struct GNUNET_TIME_Timestamp start_time;
   struct GNUNET_TIME_Timestamp end_time;
   struct TALER_WireFeeSet fees;
+  const char *exchange_url;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   ds->is = is;
   now = GNUNET_TIME_absolute_get ();
   start_time = GNUNET_TIME_absolute_to_timestamp (
@@ -153,16 +161,30 @@ wire_add_run (void *cls,
   }
   else
   {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
     TALER_exchange_offline_wire_fee_sign (ds->wire_method,
                                           start_time,
                                           end_time,
                                           &fees,
-                                          &is->master_priv,
+                                          master_priv,
                                           &master_sig);
   }
   ds->dh = TALER_EXCHANGE_management_set_wire_fees (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     ds->wire_method,
     start_time,
     end_time,
@@ -194,10 +216,8 @@ wire_add_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_set_wire_fees_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_stat.c 
b/src/testing/testing_api_cmd_stat.c
index 5d41c05c..f85072a1 100644
--- a/src/testing/testing_api_cmd_stat.c
+++ b/src/testing/testing_api_cmd_stat.c
@@ -74,13 +74,15 @@ stat_cmd (struct TALER_TESTING_Timer *timings,
 /**
  * Obtain statistics for @a timings of @a cmd
  *
- * @param timings what timings to get
+ * @param[in,out] cls what timings to get
  * @param cmd command to process
  */
 static void
-do_stat (struct TALER_TESTING_Timer *timings,
+do_stat (void *cls,
          const struct TALER_TESTING_Command *cmd)
 {
+  struct TALER_TESTING_Timer *timings = cls;
+
   if (TALER_TESTING_cmd_is_batch (cmd))
   {
     struct TALER_TESTING_Command **bcmd;
@@ -121,13 +123,10 @@ stat_run (void *cls,
 {
   struct TALER_TESTING_Timer *timings = cls;
 
-  for (unsigned int i = 0; NULL != is->commands[i].label; i++)
-  {
-    if (cmd == &is->commands[i])
-      break; /* skip at our current command */
-    do_stat (timings,
-             &is->commands[i]);
-  }
+  TALER_TESTING_iterate (is,
+                         true,
+                         &do_stat,
+                         timings);
   TALER_TESTING_interpreter_next (is);
 }
 
diff --git a/src/testing/testing_api_cmd_system_start.c 
b/src/testing/testing_api_cmd_system_start.c
index 491f03c5..2f5a9a8e 100644
--- a/src/testing/testing_api_cmd_system_start.c
+++ b/src/testing/testing_api_cmd_system_start.c
@@ -163,14 +163,19 @@ read_stdout (void *cls)
                 "Child closed stdout\n");
     return;
   }
+  /* forward log, except single '.' outputs */
+  if ( (1 != ret) ||
+       ('.' != buf[off]) )
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "TUS: %.*s\n",
+                (int) ret,
+                &buf[off]);
   start_reader (as);
   off += ret;
   if (as->ready)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Taler system UP\n");
-    TALER_TESTING_interpreter_next (as->is);
-    return; /* done */
+    /* already done */
+    return;
   }
   if (NULL !=
       memmem (buf,
@@ -178,7 +183,10 @@ read_stdout (void *cls)
               "\n<<READY>>\n",
               strlen ("\n<<READY>>\n")))
   {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Taler system UP\n");
     as->ready = true;
+    TALER_TESTING_interpreter_next (as->is);
     return;
   }
 
@@ -226,7 +234,7 @@ system_run (void *cls,
 
   (void) cmd;
   as->is = is;
-  as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
+  as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
   GNUNET_assert (NULL != as->pipe_in);
   as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
   GNUNET_assert (NULL != as->pipe_out);
@@ -234,7 +242,7 @@ system_run (void *cls,
     = GNUNET_OS_start_process_vap (
         GNUNET_OS_INHERIT_STD_ERR,
         as->pipe_in, as->pipe_out, NULL,
-        "taler-benchmark-setup.sh",
+        "taler-unified-setup.sh",
         as->args);
   if (NULL == as->system_proc)
   {
@@ -357,7 +365,7 @@ TALER_TESTING_cmd_system_start (
   va_end (ap);
   as->args = GNUNET_new_array (cnt,
                                char *);
-  as->args[0] = GNUNET_strdup ("taler-benchmark-setup");
+  as->args[0] = GNUNET_strdup ("taler-unified-setup");
   as->args[1] = GNUNET_strdup ("-c");
   as->args[2] = GNUNET_strdup (config_file);
   cnt = 3;
diff --git a/src/testing/testing_api_cmd_take_aml_decision.c 
b/src/testing/testing_api_cmd_take_aml_decision.c
index c355c686..eb758397 100644
--- a/src/testing/testing_api_cmd_take_aml_decision.c
+++ b/src/testing/testing_api_cmd_take_aml_decision.c
@@ -107,16 +107,8 @@ take_aml_decision_cb (
   ds->dh = NULL;
   if (ds->expected_response != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -141,8 +133,24 @@ take_aml_decision_run (void *cls,
   const struct TALER_AmlOfficerPrivateKeyP *officer_priv;
   const struct TALER_TESTING_Command *ref;
   json_t *kyc_requirements = NULL;
+  const char *exchange_url;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
   ref = TALER_TESTING_interpreter_lookup_command (is,
@@ -188,8 +196,8 @@ take_aml_decision_run (void *cls,
   }
 
   ds->dh = TALER_EXCHANGE_add_aml_decision (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     ds->justification,
     now,
     &ds->new_threshold,
@@ -224,10 +232,8 @@ take_aml_decision_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_add_aml_decision_cancel (ds->dh);
     ds->dh = NULL;
   }
@@ -254,7 +260,7 @@ take_aml_decision_traits (void *cls,
   struct AmlDecisionState *ws = cls;
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_h_payto (&ws->h_payto),
-    TALER_TESTING_make_trait_aml_justification (&ws->justification),
+    TALER_TESTING_make_trait_aml_justification (ws->justification),
     TALER_TESTING_make_trait_aml_decision (&ws->new_state),
     TALER_TESTING_make_trait_amount (&ws->new_threshold),
     TALER_TESTING_trait_end ()
diff --git a/src/testing/testing_api_cmd_transfer_get.c 
b/src/testing/testing_api_cmd_transfer_get.c
index cb6bb7df..10fe48c2 100644
--- a/src/testing/testing_api_cmd_transfer_get.c
+++ b/src/testing/testing_api_cmd_transfer_get.c
@@ -43,6 +43,11 @@ struct TrackTransferState
    */
   const char *expected_wire_fee;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Reference to any operation that can provide a WTID.
    * Will be the WTID to track.
@@ -98,10 +103,8 @@ track_transfer_cleanup (void *cls,
 
   if (NULL != tts->tth)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                tts->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (tts->is,
+                                      cmd->label);
     TALER_EXCHANGE_transfers_get_cancel (tts->tth);
     tts->tth = NULL;
   }
@@ -124,23 +127,13 @@ track_transfer_cb (void *cls,
   struct TrackTransferState *tts = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr;
   struct TALER_TESTING_Interpreter *is = tts->is;
-  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
   struct TALER_Amount expected_amount;
 
   tts->tth = NULL;
   if (tts->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                hr->http_status,
-                (int) hr->ec,
-                cmd->label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     hr->http_status);
     return;
   }
 
@@ -178,7 +171,7 @@ track_transfer_cb (void *cls,
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Total amount mismatch to command %s - "
                     "%s vs %s\n",
-                    cmd->label,
+                    tts->cmd->label,
                     TALER_amount_to_string (&ta->total_amount),
                     TALER_amount_to_string (&expected_amount));
         json_dumpf (hr->reply,
@@ -203,7 +196,7 @@ track_transfer_cb (void *cls,
       {
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Wire fee mismatch to command %s\n",
-                    cmd->label);
+                    tts->cmd->label);
         json_dumpf (hr->reply,
                     stderr,
                     0);
@@ -221,7 +214,7 @@ track_transfer_cb (void *cls,
       if (NULL != tts->wire_details_reference)
       {
         const struct TALER_TESTING_Command *wire_details_cmd;
-        const char **payto_uri;
+        const char *payto_uri;
         struct TALER_PaytoHashP h_payto;
 
         wire_details_cmd
@@ -242,14 +235,14 @@ track_transfer_cb (void *cls,
           TALER_TESTING_interpreter_fail (is);
           return;
         }
-        TALER_payto_hash (*payto_uri,
+        TALER_payto_hash (payto_uri,
                           &h_payto);
         if (0 != GNUNET_memcmp (&h_payto,
                                 &ta->h_payto))
         {
           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                       "Wire hash missmath to command %s\n",
-                      cmd->label);
+                      tts->cmd->label);
           json_dumpf (hr->reply,
                       stderr,
                       0);
@@ -284,8 +277,8 @@ track_transfer_cb (void *cls,
                                    total_amount_from_reference))
         {
           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Amount missmath to command %s\n",
-                      cmd->label);
+                      "Amount mismatch in command %s\n",
+                      tts->cmd->label);
           json_dumpf (hr->reply,
                       stderr,
                       0);
@@ -316,14 +309,18 @@ track_transfer_run (void *cls,
   struct TrackTransferState *tts = cls;
   struct TALER_WireTransferIdentifierRawP wtid;
   const struct TALER_WireTransferIdentifierRawP *wtid_ptr;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
+  tts->cmd = cmd;
+  if (NULL == exchange)
+    return;
   /* If no reference is given, we'll use a all-zeros
    * WTID */
   memset (&wtid,
           0,
           sizeof (wtid));
   wtid_ptr = &wtid;
-
   tts->is = is;
   if (NULL != tts->wtid_reference)
   {
@@ -348,7 +345,7 @@ track_transfer_run (void *cls,
     }
     GNUNET_assert (NULL != wtid_ptr);
   }
-  tts->tth = TALER_EXCHANGE_transfers_get (is->exchange,
+  tts->tth = TALER_EXCHANGE_transfers_get (exchange,
                                            wtid_ptr,
                                            &track_transfer_cb,
                                            tts);
diff --git a/src/testing/testing_api_cmd_twister_exec_client.c 
b/src/testing/testing_api_cmd_twister_exec_client.c
index d1d781f5..bf83c1f8 100644
--- a/src/testing/testing_api_cmd_twister_exec_client.c
+++ b/src/testing/testing_api_cmd_twister_exec_client.c
@@ -572,7 +572,7 @@ flip_object_cleanup
  * @param index index number of the object to offer.
  * @return #GNUNET_OK on success
  */
-static int
+static enum GNUNET_GenericReturnValue
 flip_object_traits (void *cls,
                     const void **ret,
                     const char *trait,
diff --git a/src/testing/testing_api_cmd_wire.c 
b/src/testing/testing_api_cmd_wire.c
index 5fbd41b1..a1324231 100644
--- a/src/testing/testing_api_cmd_wire.c
+++ b/src/testing/testing_api_cmd_wire.c
@@ -38,6 +38,11 @@ struct WireState
    */
   struct TALER_EXCHANGE_WireHandle *wh;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * Which wire-method we expect is offered by the exchange.
    */
@@ -80,7 +85,6 @@ wire_cb (void *cls,
 {
   struct WireState *ws = cls;
   const struct TALER_EXCHANGE_HttpResponse *hr = &wr->hr;
-  struct TALER_TESTING_Command *cmd = &ws->is->commands[ws->is->ip];
   struct TALER_Amount expected_fee;
 
   TALER_LOG_DEBUG ("Checking parsed /wire response\n");
@@ -145,7 +149,7 @@ wire_cb (void *cls,
           {
             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                         "Wire fee mismatch to command %s\n",
-                        cmd->label);
+                        ws->cmd->label);
             TALER_TESTING_interpreter_fail (ws->is);
             return;
           }
@@ -187,10 +191,14 @@ wire_run (void *cls,
           struct TALER_TESTING_Interpreter *is)
 {
   struct WireState *ws = cls;
+  struct TALER_EXCHANGE_Handle *exchange
+    = TALER_TESTING_get_exchange (is);
 
-  (void) cmd;
+  ws->cmd = cmd;
+  if (NULL == exchange)
+    return;
   ws->is = is;
-  ws->wh = TALER_EXCHANGE_wire (is->exchange,
+  ws->wh = TALER_EXCHANGE_wire (exchange,
                                 &wire_cb,
                                 ws);
 }
@@ -211,10 +219,8 @@ wire_cleanup (void *cls,
 
   if (NULL != ws->wh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ws->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ws->is,
+                                      cmd->label);
     TALER_EXCHANGE_wire_cancel (ws->wh);
     ws->wh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_wire_add.c 
b/src/testing/testing_api_cmd_wire_add.c
index c36f03b1..b19cca1e 100644
--- a/src/testing/testing_api_cmd_wire_add.c
+++ b/src/testing/testing_api_cmd_wire_add.c
@@ -79,16 +79,8 @@ wire_add_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -113,8 +105,24 @@ wire_add_run (void *cls,
   struct GNUNET_TIME_Timestamp now;
   json_t *credit_rest;
   json_t *debit_rest;
+  const char *exchange_url;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
   debit_rest = json_array ();
@@ -130,23 +138,37 @@ wire_add_run (void *cls,
   }
   else
   {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
     TALER_exchange_offline_wire_add_sign (ds->payto_uri,
                                           NULL,
                                           debit_rest,
                                           credit_rest,
                                           now,
-                                          &is->master_priv,
+                                          master_priv,
                                           &master_sig1);
     TALER_exchange_wire_signature_make (ds->payto_uri,
                                         NULL,
                                         debit_rest,
                                         credit_rest,
-                                        &is->master_priv,
+                                        master_priv,
                                         &master_sig2);
   }
   ds->dh = TALER_EXCHANGE_management_enable_wire (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     ds->payto_uri,
     NULL,
     debit_rest,
@@ -182,10 +204,8 @@ wire_add_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_enable_wire_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_wire_del.c 
b/src/testing/testing_api_cmd_wire_del.c
index 89fb8395..61405e6e 100644
--- a/src/testing/testing_api_cmd_wire_del.c
+++ b/src/testing/testing_api_cmd_wire_del.c
@@ -79,16 +79,8 @@ wire_del_cb (void *cls,
   ds->dh = NULL;
   if (ds->expected_response_code != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u to command %s in %s:%u\n",
-                hr->http_status,
-                ds->is->commands[ds->is->ip].label,
-                __FILE__,
-                __LINE__);
-    json_dumpf (hr->reply,
-                stderr,
-                0);
-    TALER_TESTING_interpreter_fail (ds->is);
+    TALER_TESTING_unexpected_status (ds->is,
+                                     hr->http_status);
     return;
   }
   TALER_TESTING_interpreter_next (ds->is);
@@ -110,8 +102,24 @@ wire_del_run (void *cls,
   struct WireDelState *ds = cls;
   struct TALER_MasterSignatureP master_sig;
   struct GNUNET_TIME_Timestamp now;
+  const char *exchange_url;
 
   (void) cmd;
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
   now = GNUNET_TIME_timestamp_get ();
   ds->is = is;
   if (ds->bad_sig)
@@ -122,14 +130,28 @@ wire_del_run (void *cls,
   }
   else
   {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
     TALER_exchange_offline_wire_del_sign (ds->payto_uri,
                                           now,
-                                          &is->master_priv,
+                                          master_priv,
                                           &master_sig);
   }
   ds->dh = TALER_EXCHANGE_management_disable_wire (
-    is->ctx,
-    is->exchange_url,
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
     ds->payto_uri,
     now,
     &master_sig,
@@ -159,10 +181,8 @@ wire_del_cleanup (void *cls,
 
   if (NULL != ds->dh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %u (%s) did not complete\n",
-                ds->is->ip,
-                cmd->label);
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
     TALER_EXCHANGE_management_disable_wire_cancel (ds->dh);
     ds->dh = NULL;
   }
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index 8d53f4d0..8188eeae 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -66,6 +66,11 @@ struct WithdrawState
    */
   const char *reuse_coin_key_ref;
 
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
   /**
    * String describing the denomination value we should withdraw.
    * A corresponding denomination key must exist in the exchange's
@@ -220,8 +225,7 @@ do_retry (void *cls)
   struct WithdrawState *ws = cls;
 
   ws->retry_task = NULL;
-  ws->is->commands[ws->is->ip].last_req_time
-    = GNUNET_TIME_absolute_get ();
+  TALER_TESTING_touch_cmd (ws->is);
   withdraw_run (ws,
                 NULL,
                 ws->is);
@@ -272,25 +276,15 @@ reserve_withdraw_cb (void *cls,
                                                 UNKNOWN_MAX_BACKOFF);
         ws->total_backoff = GNUNET_TIME_relative_add (ws->total_backoff,
                                                       ws->backoff);
-        ws->is->commands[ws->is->ip].num_tries++;
+        TALER_TESTING_inc_tries (ws->is);
         ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff,
                                                        &do_retry,
                                                        ws);
         return;
       }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d to command %s in %s:%u\n",
-                wr->hr.http_status,
-                (int) wr->hr.ec,
-                TALER_TESTING_interpreter_get_current_label (is),
-                __FILE__,
-                __LINE__);
-    json_dumpf (wr->hr.reply,
-                stderr,
-                0);
-    GNUNET_break (0);
-    TALER_TESTING_interpreter_fail (is);
+    TALER_TESTING_unexpected_status (is,
+                                     wr->hr.http_status);
     return;
   }
   switch (wr->hr.http_status)
@@ -305,9 +299,9 @@ reserve_withdraw_cb (void *cls,
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Total withdraw backoff for %s was %s\n",
-                  is->commands[is->ip].label,
+                  ws->cmd->label,
                   GNUNET_STRINGS_relative_time_to_string (ws->total_backoff,
-                                                          GNUNET_YES));
+                                                          true));
     }
     break;
   case MHD_HTTP_FORBIDDEN:
@@ -353,8 +347,9 @@ withdraw_run (void *cls,
   const struct TALER_ReservePrivateKeyP *rp;
   const struct TALER_TESTING_Command *create_reserve;
   const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+  struct TALER_EXCHANGE_Handle *exchange;
 
-  (void) cmd;
+  ws->cmd = cmd;
   ws->is = is;
   create_reserve
     = TALER_TESTING_interpreter_lookup_command (
@@ -374,9 +369,12 @@ withdraw_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
+  exchange = TALER_TESTING_get_exchange (is);
+  if (NULL == exchange)
+    return;
   if (NULL == ws->exchange_url)
     ws->exchange_url
-      = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
+      = GNUNET_strdup (TALER_EXCHANGE_get_base_url (exchange));
   ws->reserve_priv = *rp;
   GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
                                       &ws->reserve_pub.eddsa_pub);
@@ -412,7 +410,7 @@ withdraw_run (void *cls,
 
   if (NULL == ws->pk)
   {
-    dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
+    dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (exchange),
                                  &ws->amount,
                                  ws->age > 0);
     if (NULL == dpk)
@@ -445,7 +443,7 @@ withdraw_run (void *cls,
       .ps = &ws->ps,
       .ach = ws->h_age_commitment
     };
-    ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
+    ws->wsh = TALER_EXCHANGE_withdraw (exchange,
                                        rp,
                                        &wci,
                                        &reserve_withdraw_cb,
@@ -475,9 +473,8 @@ withdraw_cleanup (void *cls,
 
   if (NULL != ws->wsh)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command %s did not complete\n",
-                cmd->label);
+    TALER_TESTING_command_incomplete (ws->is,
+                                      cmd->label);
     TALER_EXCHANGE_withdraw_cancel (ws->wsh);
     ws->wsh = NULL;
   }
@@ -544,12 +541,9 @@ withdraw_traits (void *cls,
     TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
     TALER_TESTING_make_trait_amount (&ws->amount),
     TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
-    TALER_TESTING_make_trait_h_payto (
-      &ws->h_payto),
-    TALER_TESTING_make_trait_payto_uri (
-      (const char **) &ws->reserve_payto_uri),
-    TALER_TESTING_make_trait_exchange_url (
-      (const char **) &ws->exchange_url),
+    TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+    TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+    TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
     TALER_TESTING_make_trait_age_commitment_proof (0,
                                                    ws->age_commitment_proof),
     TALER_TESTING_make_trait_h_age_commitment (0,
@@ -570,13 +564,12 @@ struct TALER_TESTING_Command
 TALER_TESTING_cmd_withdraw_amount (const char *label,
                                    const char *reserve_reference,
                                    const char *amount,
-                                   const uint8_t age,
+                                   uint8_t age,
                                    unsigned int expected_response_code)
 {
   struct WithdrawState *ws;
 
   ws = GNUNET_new (struct WithdrawState);
-
   ws->age = age;
   if (0 < age)
   {
@@ -605,8 +598,8 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
                   label);
       GNUNET_assert (0);
     }
-
-    TALER_age_commitment_hash (&acp->commitment,hac);
+    TALER_age_commitment_hash (&acp->commitment,
+                               hac);
     ws->age_commitment_proof = acp;
     ws->h_age_commitment = hac;
   }
diff --git a/src/testing/testing_api_helpers_bank.c 
b/src/testing/testing_api_helpers_bank.c
index f2f92956..cfee6f24 100644
--- a/src/testing/testing_api_helpers_bank.c
+++ b/src/testing/testing_api_helpers_bank.c
@@ -64,30 +64,6 @@ TALER_TESTING_run_fakebank (const char *bank_url,
 }
 
 
-int
-TALER_TESTING_has_in_name (const char *prog,
-                           const char *marker)
-{
-  size_t name_pos;
-  size_t pos;
-
-  if (! prog || ! marker)
-    return GNUNET_NO;
-
-  pos = 0;
-  name_pos = 0;
-  while (prog[pos])
-  {
-    if ('/' == prog[pos])
-      name_pos = pos + 1;
-    pos++;
-  }
-  if (name_pos == pos)
-    return GNUNET_YES;
-  return (NULL != strstr (prog + name_pos, marker));
-}
-
-
 struct TALER_TESTING_LibeufinServices
 TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc)
 {
@@ -708,21 +684,4 @@ TALER_TESTING_prepare_fakebank (const char 
*config_filename,
 }
 
 
-json_t *
-TALER_TESTING_make_wire_details (const char *payto)
-{
-  struct TALER_WireSaltP salt;
-
-  /* salt must be constant for aggregation tests! */
-  memset (&salt,
-          47,
-          sizeof (salt));
-  return GNUNET_JSON_PACK (
-    GNUNET_JSON_pack_string ("payto_uri",
-                             payto),
-    GNUNET_JSON_pack_data_auto ("salt",
-                                &salt));
-}
-
-
 /* end of testing_api_helpers_bank.c */
diff --git a/src/testing/testing_api_helpers_exchange.c 
b/src/testing/testing_api_helpers_exchange.c
index 7fe0038a..1444f154 100644
--- a/src/testing/testing_api_helpers_exchange.c
+++ b/src/testing/testing_api_helpers_exchange.c
@@ -48,81 +48,6 @@ TALER_TESTING_cleanup_files (const char *config_name)
 }
 
 
-/**
- * Remove @a option directory from @a section in @a cfg.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-remove_dir (const struct GNUNET_CONFIGURATION_Handle *cfg,
-            const char *section,
-            const char *option)
-{
-  char *dir;
-
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (cfg,
-                                               section,
-                                               option,
-                                               &dir))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               section,
-                               option);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_YES ==
-      GNUNET_DISK_directory_test (dir,
-                                  GNUNET_NO))
-    GNUNET_break (GNUNET_OK ==
-                  GNUNET_DISK_directory_remove (dir));
-  GNUNET_free (dir);
-  return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_TESTING_cleanup_files_cfg (void *cls,
-                                 const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-  char *dir;
-
-  (void) cls;
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (cfg,
-                                               "exchange-offline",
-                                               "SECM_TOFU_FILE",
-                                               &dir))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "exchange-offline",
-                               "SECM_TOFU_FILE");
-    return GNUNET_SYSERR;
-  }
-  if ( (0 != unlink (dir)) &&
-       (ENOENT != errno) )
-  {
-    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                              "unlink",
-                              dir);
-    GNUNET_free (dir);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_free (dir);
-  if (GNUNET_OK !=
-      remove_dir (cfg,
-                  "taler-exchange-secmod-eddsa",
-                  "KEY_DIR"))
-    return GNUNET_SYSERR;
-  if (GNUNET_OK !=
-      remove_dir (cfg,
-                  "taler-exchange-secmod-rsa",
-                  "KEY_DIR"))
-    return GNUNET_SYSERR;
-  return GNUNET_OK;
-}
-
-
 enum GNUNET_GenericReturnValue
 TALER_TESTING_run_auditor_exchange (const char *config_filename,
                                     const char *exchange_master_pub,
@@ -410,66 +335,6 @@ TALER_TESTING_prepare_exchange (const char 
*config_filename,
 }
 
 
-const struct TALER_EXCHANGE_DenomPublicKey *
-TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
-                       const struct TALER_Amount *amount,
-                       bool age_restricted)
-{
-  struct GNUNET_TIME_Timestamp now;
-  struct TALER_EXCHANGE_DenomPublicKey *pk;
-  char *str;
-
-  now = GNUNET_TIME_timestamp_get ();
-  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
-  {
-    pk = &keys->denom_keys[i];
-    if ( (0 == TALER_amount_cmp (amount,
-                                 &pk->value)) &&
-         (GNUNET_TIME_timestamp_cmp (now,
-                                     >=,
-                                     pk->valid_from)) &&
-         (GNUNET_TIME_timestamp_cmp (now,
-                                     <,
-                                     pk->withdraw_valid_until)) &&
-         (age_restricted == (0 != pk->key.age_mask.bits)) )
-      return pk;
-  }
-  /* do 2nd pass to check if expiration times are to blame for
-   * failure */
-  str = TALER_amount_to_string (amount);
-  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
-  {
-    pk = &keys->denom_keys[i];
-    if ( (0 == TALER_amount_cmp (amount,
-                                 &pk->value)) &&
-         (GNUNET_TIME_timestamp_cmp (now,
-                                     <,
-                                     pk->valid_from) ||
-          GNUNET_TIME_timestamp_cmp (now,
-                                     >,
-                                     pk->withdraw_valid_until) ) &&
-         (age_restricted == (0 != pk->key.age_mask.bits)) )
-    {
-      GNUNET_log
-        (GNUNET_ERROR_TYPE_WARNING,
-        "Have denomination key for `%s', but with wrong"
-        " expiration range %llu vs [%llu,%llu)\n",
-        str,
-        (unsigned long long) now.abs_time.abs_value_us,
-        (unsigned long long) pk->valid_from.abs_time.abs_value_us,
-        (unsigned long long) pk->withdraw_valid_until.abs_time.abs_value_us);
-      GNUNET_free (str);
-      return NULL;
-    }
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-              "No denomination key for amount %s found\n",
-              str);
-  GNUNET_free (str);
-  return NULL;
-}
-
-
 int
 TALER_TESTING_wait_exchange_ready (const char *base_url)
 {
@@ -502,38 +367,6 @@ TALER_TESTING_wait_exchange_ready (const char *base_url)
 }
 
 
-int
-TALER_TESTING_wait_httpd_ready (const char *base_url)
-{
-  char *wget_cmd;
-  unsigned int iter;
-
-  GNUNET_asprintf (&wget_cmd,
-                   "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null",
-                   base_url); // make sure ends with '/'
-  /* give child time to start and bind against the socket */
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Waiting for HTTP service to be ready (check with: %s)\n",
-              wget_cmd);
-  iter = 0;
-  do
-  {
-    if (10 == iter)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Failed to launch HTTP service (or `wget')\n");
-      GNUNET_free (wget_cmd);
-      return 77;
-    }
-    sleep (1);
-    iter++;
-  }
-  while (0 != system (wget_cmd));
-  GNUNET_free (wget_cmd);
-  return 0;
-}
-
-
 /**
  * Wait for the auditor to have started. Waits for at
  * most 10s, after that returns 77 to indicate an error.
@@ -960,29 +793,4 @@ TALER_TESTING_setup_with_auditor_and_exchange 
(TALER_TESTING_Main main_cb,
 }
 
 
-enum GNUNET_GenericReturnValue
-TALER_TESTING_url_port_free (const char *url)
-{
-  const char *port;
-  long pnum;
-
-  port = strrchr (url,
-                  (unsigned char) ':');
-  if (NULL == port)
-    pnum = 80;
-  else
-    pnum = strtol (port + 1, NULL, 10);
-  if (GNUNET_OK !=
-      GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
-                                     pnum))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Port %u not available.\n",
-                (unsigned int) pnum);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 /* end of testing_api_helpers_exchange.c */
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c
index 271b6e76..ea6b805b 100644
--- a/src/testing/testing_api_loop.c
+++ b/src/testing/testing_api_loop.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2018 Taler Systems SA
+  Copyright (C) 2018-2023 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
@@ -26,15 +26,62 @@
 #include "platform.h"
 #include "taler_json_lib.h"
 #include <gnunet/gnunet_curl_lib.h>
+#include "taler_extensions.h"
 #include "taler_signatures.h"
 #include "taler_testing_lib.h"
-#include "taler_fakebank_lib.h"
 
-/**
- * Pipe used to communicate child death via signal.
- * Must be global, as used in signal handler!
- */
-static struct GNUNET_DISK_PipeHandle *sigpipe;
+
+struct TALER_TESTING_Interpreter
+{
+
+  /**
+   * Commands the interpreter will run.
+   */
+  struct TALER_TESTING_Command *commands;
+
+  /**
+   * Interpreter task (if one is scheduled).
+   */
+  struct GNUNET_SCHEDULER_Task *task;
+
+  /**
+   * Handle for the child management.
+   */
+  struct GNUNET_ChildWaitHandle *cwh;
+
+  /**
+   * Main execution context for the main loop.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Context for running the CURL event loop.
+   */
+  struct GNUNET_CURL_RescheduleContext *rc;
+
+  /**
+   * Hash map mapping variable names to commands.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *vars;
+
+  /**
+   * Task run on timeout.
+   */
+  struct GNUNET_SCHEDULER_Task *timeout_task;
+
+  /**
+   * Instruction pointer.  Tells #interpreter_run() which instruction to run
+   * next.  Need (signed) int because it gets -1 when rewinding the
+   * interpreter to the first CMD.
+   */
+  int ip;
+
+  /**
+   * Result of the testcases, #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue result;
+
+};
 
 
 const struct TALER_TESTING_Command *
@@ -90,13 +137,29 @@ TALER_TESTING_interpreter_lookup_command (struct 
TALER_TESTING_Interpreter *is,
               "Command not found: %s\n",
               label);
   return NULL;
+}
 
+
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
+                                       const char *name)
+{
+  const struct TALER_TESTING_Command *cmd;
+  struct GNUNET_HashCode h_name;
+
+  GNUNET_CRYPTO_hash (name,
+                      strlen (name),
+                      &h_name);
+  cmd = GNUNET_CONTAINER_multihashmap_get (is->vars,
+                                           &h_name);
+  if (NULL == cmd)
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Command not found by name: %s\n",
+                name);
+  return cmd;
 }
 
 
-/**
- * Obtain main execution context for the main loop.
- */
 struct GNUNET_CURL_Context *
 TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is)
 {
@@ -104,38 +167,18 @@ TALER_TESTING_interpreter_get_context (struct 
TALER_TESTING_Interpreter *is)
 }
 
 
-struct TALER_FAKEBANK_Handle *
-TALER_TESTING_interpreter_get_fakebank (struct TALER_TESTING_Interpreter *is)
+void
+TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is)
 {
-  return is->fakebank;
+  is->commands[is->ip].last_req_time
+    = GNUNET_TIME_absolute_get ();
 }
 
 
 void
-TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is,
-                                 struct TALER_TESTING_Command *commands,
-                                 const char *bank_url)
+TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is)
 {
-  char *currency;
-
-  if (GNUNET_OK !=
-      TALER_config_get_currency (is->cfg,
-                                 &currency))
-  {
-    is->result = GNUNET_SYSERR;
-    return;
-  }
-  is->fakebank = TALER_TESTING_run_fakebank (bank_url,
-                                             currency);
-  GNUNET_free (currency);
-  if (NULL == is->fakebank)
-  {
-    GNUNET_break (0);
-    is->result = GNUNET_SYSERR;
-    return;
-  }
-  TALER_TESTING_run (is,
-                     commands);
+  is->commands[is->ip].num_tries++;
 }
 
 
@@ -159,7 +202,12 @@ TALER_TESTING_interpreter_next (struct 
TALER_TESTING_Interpreter *is)
     return; /* ignore, we already failed! */
   if (TALER_TESTING_cmd_is_batch (cmd))
   {
-    TALER_TESTING_cmd_batch_next (is);
+    if (TALER_TESTING_cmd_batch_next (is,
+                                      cmd->cls))
+    {
+      cmd->finish_time = GNUNET_TIME_absolute_get ();
+      is->ip++; /* batch is done */
+    }
   }
   else
   {
@@ -202,19 +250,9 @@ TALER_TESTING_interpreter_fail (struct 
TALER_TESTING_Interpreter *is)
 }
 
 
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_end (void)
-{
-  static struct TALER_TESTING_Command cmd;
-  cmd.label = NULL;
-
-  return cmd;
-}
-
-
 const char *
-TALER_TESTING_interpreter_get_current_label (struct
-                                             TALER_TESTING_Interpreter *is)
+TALER_TESTING_interpreter_get_current_label (
+  struct TALER_TESTING_Interpreter *is)
 {
   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
@@ -246,6 +284,19 @@ interpreter_run (void *cls)
     = cmd->last_req_time
       = GNUNET_TIME_absolute_get ();
   cmd->num_tries = 1;
+  if (NULL != cmd->name)
+  {
+    struct GNUNET_HashCode h_name;
+
+    GNUNET_CRYPTO_hash (cmd->name,
+                        strlen (cmd->name),
+                        &h_name);
+    (void) GNUNET_CONTAINER_multihashmap_put (
+      is->vars,
+      &h_name,
+      cmd,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
+  }
   cmd->run (cmd->cls,
             cmd,
             is);
@@ -277,22 +328,11 @@ do_shutdown (void *cls)
     if (NULL != cmd->cleanup)
       cmd->cleanup (cmd->cls,
                     cmd);
-  if (NULL != is->exchange)
-  {
-    TALER_LOG_DEBUG ("Disconnecting the exchange\n");
-    TALER_EXCHANGE_disconnect (is->exchange);
-    is->exchange = NULL;
-  }
   if (NULL != is->task)
   {
     GNUNET_SCHEDULER_cancel (is->task);
     is->task = NULL;
   }
-  if (NULL != is->fakebank)
-  {
-    TALER_FAKEBANK_stop (is->fakebank);
-    is->fakebank = NULL;
-  }
   if (NULL != is->ctx)
   {
     GNUNET_CURL_fini (is->ctx);
@@ -303,15 +343,20 @@ do_shutdown (void *cls)
     GNUNET_CURL_gnunet_rc_destroy (is->rc);
     is->rc = NULL;
   }
+  if (NULL != is->vars)
+  {
+    GNUNET_CONTAINER_multihashmap_destroy (is->vars);
+    is->vars = NULL;
+  }
   if (NULL != is->timeout_task)
   {
     GNUNET_SCHEDULER_cancel (is->timeout_task);
     is->timeout_task = NULL;
   }
-  if (NULL != is->child_death_task)
+  if (NULL != is->cwh)
   {
-    GNUNET_SCHEDULER_cancel (is->child_death_task);
-    is->child_death_task = NULL;
+    GNUNET_wait_child_cancel (is->cwh);
+    is->cwh = NULL;
   }
   GNUNET_free (is->commands);
 }
@@ -339,30 +384,24 @@ do_timeout (void *cls)
  * process died).
  *
  * @param cls the `struct TALER_TESTING_Interpreter *`
+ * @param type type of the process
+ * @param exit_code status code of the process
  */
 static void
-maint_child_death (void *cls)
+maint_child_death (void *cls,
+                   enum GNUNET_OS_ProcessStatusType type,
+                   long unsigned int code)
 {
   struct TALER_TESTING_Interpreter *is = cls;
   struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
-  const struct GNUNET_DISK_FileHandle *pr;
   struct GNUNET_OS_Process **processp;
-  char c[16];
-  enum GNUNET_OS_ProcessStatusType type;
-  unsigned long code;
 
+  is->cwh = NULL;
   while (TALER_TESTING_cmd_is_batch (cmd))
     cmd = TALER_TESTING_cmd_batch_get_current (cmd);
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Got SIGCHLD for `%s'.\n",
               cmd->label);
-  is->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)));
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_process (cmd,
                                        &processp))
@@ -371,13 +410,8 @@ maint_child_death (void *cls)
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Got the dead child process handle, waiting for termination 
...\n");
-  GNUNET_assert (GNUNET_OK ==
-                 GNUNET_OS_process_wait_status (*processp,
-                                                &type,
-                                                &code));
   GNUNET_OS_process_destroy (*processp);
   *processp = NULL;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -411,7 +445,6 @@ maint_child_death (void *cls)
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Dead child, go on with next command.\n");
   TALER_TESTING_interpreter_next (is);
@@ -421,16 +454,24 @@ maint_child_death (void *cls)
 void
 TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is)
 {
-  const struct GNUNET_DISK_FileHandle *pr;
+  struct GNUNET_OS_Process **processp;
+  struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
 
-  GNUNET_assert (NULL == is->child_death_task);
-  pr = GNUNET_DISK_pipe_handle (sigpipe,
-                                GNUNET_DISK_PIPE_END_READ);
-  is->child_death_task
-    = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
-                                      pr,
-                                      &maint_child_death,
-                                      is);
+  while (TALER_TESTING_cmd_is_batch (cmd))
+    cmd = TALER_TESTING_cmd_batch_get_current (cmd);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_process (cmd,
+                                       &processp))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (NULL == is->cwh);
+  is->cwh
+    = GNUNET_wait_child (*processp,
+                         &maint_child_death,
+                         is);
 }
 
 
@@ -507,349 +548,446 @@ struct MainContext
 
 
 /**
- * Signal handler called for SIGCHLD.  Triggers the
- * respective handler by writing to the trigger pipe.
+ * Initialize scheduler loop and curl context for the testcase,
+ * and responsible to run the "run" method.
+ *
+ * @param cls closure, typically the "run" method, the
+ *        interpreter state and a closure for "run".
  */
 static void
-sighandler_child_death (void)
+main_wrapper (void *cls)
 {
-  static char c;
-  int old_errno = errno;  /* back-up errno */
+  struct MainContext *main_ctx = cls;
 
-  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 */
+  main_ctx->main_cb (main_ctx->main_cb_cls,
+                     main_ctx->is);
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_TESTING_loop (TALER_TESTING_Main main_cb,
+                    void *main_cb_cls)
+{
+  struct TALER_TESTING_Interpreter is;
+  struct MainContext main_ctx = {
+    .main_cb = main_cb,
+    .main_cb_cls = main_cb_cls,
+    /* needed to init the curl ctx */
+    .is = &is,
+  };
+
+  memset (&is,
+          0,
+          sizeof (is));
+  is.ctx = GNUNET_CURL_init (
+    &GNUNET_CURL_gnunet_scheduler_reschedule,
+    &is.rc);
+  GNUNET_CURL_enable_async_scope_header (is.ctx,
+                                         "Taler-Correlation-Id");
+  GNUNET_assert (NULL != is.ctx);
+  is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
+  is.vars = GNUNET_CONTAINER_multihashmap_create (1024,
+                                                  false);
+  /* Blocking */
+  GNUNET_SCHEDULER_run (&main_wrapper,
+                        &main_ctx);
+  return is.result;
+}
+
+
+int
+TALER_TESTING_main (char *const *argv,
+                    const char *loglevel,
+                    const char *cfg_file,
+                    const char *exchange_account_section,
+                    enum TALER_TESTING_BankSystem bs,
+                    struct TALER_TESTING_Credentials *cred,
+                    TALER_TESTING_Main main_cb,
+                    void *main_cb_cls)
+{
+  enum GNUNET_GenericReturnValue ret;
+
+  unsetenv ("XDG_DATA_HOME");
+  unsetenv ("XDG_CONFIG_HOME");
+  GNUNET_log_setup (argv[0],
+                    loglevel,
+                    NULL);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_credentials (cfg_file,
+                                     exchange_account_section,
+                                     bs,
+                                     cred))
+  {
+    GNUNET_break (0);
+    return 77;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_cleanup_files_cfg (NULL,
+                                       cred->cfg))
+  {
+    GNUNET_break (0);
+    return 77;
+  }
+  if (GNUNET_OK !=
+      TALER_extensions_init (cred->cfg))
+  {
+    GNUNET_break (0);
+    return 77;
+  }
+  ret = TALER_TESTING_loop (main_cb,
+                            main_cb_cls);
+  /* TODO: should we free 'cred' resources here? */
+  return (GNUNET_OK == ret) ? 0 : 1;
+}
+
+
+/* ************** iterate over commands ********* */
+
+
 void
-TALER_TESTING_cert_cb (void *cls,
-                       const struct TALER_EXCHANGE_KeysResponse *kr)
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+                       bool asc,
+                       TALER_TESTING_CommandIterator cb,
+                       void *cb_cls)
 {
-  const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr;
-  struct MainContext *main_ctx = cls;
-  struct TALER_TESTING_Interpreter *is = main_ctx->is;
+  unsigned int start;
+  unsigned int end;
+  int inc;
 
-  switch (hr->http_status)
+  if (asc)
   {
-  case MHD_HTTP_OK:
-    /* dealt with below */
-    break;
-  default:
-    if (GNUNET_YES == is->working)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Got NULL response for /keys during execution (%u/%d)!\n",
-                  hr->http_status,
-                  (int) hr->ec);
-      return;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Got failure response for /keys during startup (%u/%d), 
retrying!\n",
-                hr->http_status,
-                (int) hr->ec);
-    TALER_EXCHANGE_disconnect (is->exchange);
-    GNUNET_assert (
-      NULL != (is->exchange
-                 = TALER_EXCHANGE_connect (is->ctx,
-                                           main_ctx->exchange_url,
-                                           &TALER_TESTING_cert_cb,
-                                           main_ctx,
-                                           TALER_EXCHANGE_OPTION_END)));
-    return;
+    inc = 1;
+    start = 0;
+    end = is->ip;
+  }
+  else
+  {
+    inc = -1;
+    start = is->ip;
+    end = 0;
+  }
+  for (unsigned int off = start; off != end + inc; off += inc)
+  {
+    const struct TALER_TESTING_Command *cmd = &is->commands[off];
+
+    cb (cb_cls,
+        cmd);
   }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Got %d DK from /keys in generation %u\n",
-              kr->details.ok.keys->num_denom_keys,
-              is->key_generation + 1);
-  is->key_generation++;
-  is->keys = kr->details.ok.keys;
-
-  /* /keys has been called for some reason and
-   * the interpreter is already running. */
-  if (GNUNET_YES == is->working)
-    return;
-  is->working = GNUNET_YES;
-  /* Trigger the next command. */
-  TALER_LOG_DEBUG ("Cert_cb, scheduling CMD (ip: %d)\n",
-                   is->ip);
-  GNUNET_SCHEDULER_add_now (&interpreter_run,
-                            is);
+}
+
+
+/* ************** special commands ********* */
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void)
+{
+  static struct TALER_TESTING_Command cmd;
+  cmd.label = NULL;
+
+  return cmd;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+                           struct TALER_TESTING_Command cmd)
+{
+  cmd.name = name;
+  return cmd;
 }
 
 
 /**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
+ * State for a "rewind" CMD.
+ */
+struct RewindIpState
+{
+  /**
+   * Instruction pointer to set into the interpreter.
+   */
+  const char *target_label;
+
+  /**
+   * How many times this set should take place.  However, this value lives at
+   * the calling process, and this CMD is only in charge of checking and
+   * decremeting it.
+   */
+  unsigned int counter;
+};
+
+
+/**
+ * Seek for the @a target command in @a batch (and rewind to it
+ * if successful).
  *
- * @param cls closure, typically the "run" method, the
- *        interpreter state and a closure for "run".
+ * @param is the interpreter state (for failures)
+ * @param cmd batch to search for @a target
+ * @param target command to search for
+ * @return #GNUNET_OK on success, #GNUNET_NO if target was not found,
+ *         #GNUNET_SYSERR if target is in the future and we failed
  */
-static void
-main_wrapper_exchange_agnostic (void *cls)
+static enum GNUNET_GenericReturnValue
+seek_batch (struct TALER_TESTING_Interpreter *is,
+            const struct TALER_TESTING_Command *cmd,
+            const struct TALER_TESTING_Command *target)
 {
-  struct MainContext *main_ctx = cls;
+  unsigned int new_ip;
+  struct TALER_TESTING_Command **batch;
+  struct TALER_TESTING_Command *current;
+  struct TALER_TESTING_Command *icmd;
+  struct TALER_TESTING_Command *match;
 
-  main_ctx->main_cb (main_ctx->main_cb_cls,
-                     main_ctx->is);
+  current = TALER_TESTING_cmd_batch_get_current (cmd);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_batch_cmds (cmd,
+                                                     &batch));
+  match = NULL;
+  for (new_ip = 0;
+       NULL != (icmd = &(*batch)[new_ip]);
+       new_ip++)
+  {
+    if (current == target)
+      current = NULL;
+    if (icmd == target)
+    {
+      match = icmd;
+      break;
+    }
+    if (TALER_TESTING_cmd_is_batch (icmd))
+    {
+      int ret = seek_batch (is,
+                            icmd,
+                            target);
+      if (GNUNET_SYSERR == ret)
+        return GNUNET_SYSERR; /* failure! */
+      if (GNUNET_OK == ret)
+      {
+        match = icmd;
+        break;
+      }
+    }
+  }
+  if (NULL == current)
+  {
+    /* refuse to jump forward */
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == match)
+    return GNUNET_NO; /* not found */
+  TALER_TESTING_cmd_batch_set_current (cmd,
+                                       new_ip);
+  return GNUNET_OK;
 }
 
 
 /**
- * Function run when the test is aborted before we launch the actual
- * interpreter.  Cleans up our state.
+ * Run the "rewind" CMD.
  *
- * @param cls the main context
+ * @param cls closure.
+ * @param cmd command being executed now.
+ * @param is the interpreter state.
  */
 static void
-do_abort (void *cls)
+rewind_ip_run (void *cls,
+               const struct TALER_TESTING_Command *cmd,
+               struct TALER_TESTING_Interpreter *is)
 {
-  struct MainContext *main_ctx = cls;
-  struct TALER_TESTING_Interpreter *is = main_ctx->is;
+  struct RewindIpState *ris = cls;
+  const struct TALER_TESTING_Command *target;
+  unsigned int new_ip;
 
-  is->timeout_task = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Executing abort prior to interpreter launch\n");
-  if (NULL != is->exchange)
+  (void) cmd;
+  if (0 == ris->counter)
   {
-    TALER_EXCHANGE_disconnect (is->exchange);
-    is->exchange = NULL;
+    TALER_TESTING_interpreter_next (is);
+    return;
   }
-  if (NULL != is->fakebank)
+  target
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ris->target_label);
+  if (NULL == target)
   {
-    TALER_FAKEBANK_stop (is->fakebank);
-    is->fakebank = NULL;
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
   }
-  if (NULL != is->ctx)
+  ris->counter--;
+  for (new_ip = 0;
+       NULL != is->commands[new_ip].label;
+       new_ip++)
   {
-    GNUNET_CURL_fini (is->ctx);
-    is->ctx = NULL;
+    const struct TALER_TESTING_Command *cmd = &is->commands[new_ip];
+
+    if (cmd == target)
+      break;
+    if (TALER_TESTING_cmd_is_batch (cmd))
+    {
+      int ret = seek_batch (is,
+                            cmd,
+                            target);
+      if (GNUNET_SYSERR == ret)
+        return;   /* failure! */
+      if (GNUNET_OK == ret)
+        break;
+    }
   }
-  if (NULL != is->rc)
+  if (new_ip > (unsigned int) is->ip)
   {
-    GNUNET_CURL_gnunet_rc_destroy (is->rc);
-    is->rc = NULL;
+    /* refuse to jump forward */
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
   }
+  is->ip = new_ip - 1; /* -1 because the next function will advance by one */
+  TALER_TESTING_interpreter_next (is);
 }
 
 
-/**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
- *
- * @param cls a `struct MainContext *`
- */
-static void
-main_wrapper_exchange_connect (void *cls)
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_rewind_ip (const char *label,
+                             const char *target_label,
+                             unsigned int counter)
 {
-  struct MainContext *main_ctx = cls;
-  struct TALER_TESTING_Interpreter *is = main_ctx->is;
-  char *exchange_url;
+  struct RewindIpState *ris;
 
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (is->cfg,
-                                             "exchange",
-                                             "BASE_URL",
-                                             &exchange_url))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "exchange",
-                               "BASE_URL");
-    return;
+  ris = GNUNET_new (struct RewindIpState);
+  ris->target_label = target_label;
+  ris->counter = counter;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ris,
+      .label = label,
+      .run = &rewind_ip_run
+    };
+
+    return cmd;
   }
-  main_ctx->exchange_url = exchange_url;
-  is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort,
-                                                    main_ctx);
-  is->working = GNUNET_YES;
-  GNUNET_assert (NULL == is->exchange);
-  GNUNET_break (
-    NULL != (is->exchange =
-               TALER_EXCHANGE_connect (is->ctx,
-                                       exchange_url,
-                                       &TALER_TESTING_cert_cb,
-                                       main_ctx,
-                                       TALER_EXCHANGE_OPTION_END)));
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Starting main test loop\n");
-  main_ctx->main_cb (main_ctx->main_cb_cls,
-                     is);
 }
 
 
 /**
- * Load the exchange and auditor key material into @a is.
+ * State for a "authchange" CMD.
+ */
+struct AuthchangeState
+{
+
+  /**
+   * What is the new authorization token to send?
+   */
+  const char *auth_token;
+
+  /**
+   * Old context, clean up on termination.
+   */
+  struct GNUNET_CURL_Context *old_ctx;
+};
+
+
+/**
+ * Run the command.
  *
- * @param[in,out] is state to initialize
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
  */
-static enum GNUNET_GenericReturnValue
-load_keys (struct TALER_TESTING_Interpreter *is)
+static void
+authchange_run (void *cls,
+                const struct TALER_TESTING_Command *cmd,
+                struct TALER_TESTING_Interpreter *is)
 {
-  char *fn;
+  struct AuthchangeState *ss = cls;
 
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (is->cfg,
-                                               "exchange-offline",
-                                               "MASTER_PRIV_FILE",
-                                               &fn))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "exchange-offline",
-                               "MASTER_PRIV_FILE");
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_SYSERR ==
-      GNUNET_DISK_directory_create_for_file (fn))
+  (void) cmd;
+  ss->old_ctx = is->ctx;
+  if (NULL != is->rc)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not setup directory for master private key file `%s'\n",
-                fn);
-    GNUNET_free (fn);
-    return GNUNET_SYSERR;
+    GNUNET_CURL_gnunet_rc_destroy (is->rc);
+    is->rc = NULL;
   }
-  if (GNUNET_SYSERR ==
-      GNUNET_CRYPTO_eddsa_key_from_file (fn,
-                                         GNUNET_YES,
-                                         &is->master_priv.eddsa_priv))
+  is->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+                              &is->rc);
+  GNUNET_CURL_enable_async_scope_header (is->ctx,
+                                         "Taler-Correlation-Id");
+  GNUNET_assert (NULL != is->ctx);
+  is->rc = GNUNET_CURL_gnunet_rc_create (is->ctx);
+  if (NULL != ss->auth_token)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not load master private key from `%s'\n",
-                fn);
-    GNUNET_free (fn);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_free (fn);
-  GNUNET_CRYPTO_eddsa_key_get_public (&is->master_priv.eddsa_priv,
-                                      &is->master_pub.eddsa_pub);
+    char *authorization;
 
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (is->cfg,
-                                               "auditor",
-                                               "AUDITOR_PRIV_FILE",
-                                               &fn))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "auditor",
-                               "AUDITOR_PRIV_FILE");
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_SYSERR ==
-      GNUNET_DISK_directory_create_for_file (fn))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not setup directory for auditor private key file 
`%s'\n",
-                fn);
-    GNUNET_free (fn);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_SYSERR ==
-      GNUNET_CRYPTO_eddsa_key_from_file (fn,
-                                         GNUNET_YES,
-                                         &is->auditor_priv.eddsa_priv))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not load auditor private key from `%s'\n",
-                fn);
-    GNUNET_free (fn);
-    return GNUNET_SYSERR;
+    GNUNET_asprintf (&authorization,
+                     "%s: %s",
+                     MHD_HTTP_HEADER_AUTHORIZATION,
+                     ss->auth_token);
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_CURL_append_header (is->ctx,
+                                              authorization));
+    GNUNET_free (authorization);
   }
-  GNUNET_free (fn);
-  GNUNET_CRYPTO_eddsa_key_get_public (&is->auditor_priv.eddsa_priv,
-                                      &is->auditor_pub.eddsa_pub);
-  return GNUNET_OK;
+  TALER_TESTING_interpreter_next (is);
 }
 
 
 /**
- * Load the exchange and auditor URLs from the configuration into @a is.
+ * Call GNUNET_CURL_fini(). Done as a separate task to
+ * ensure that all of the command's cleanups have been
+ * executed first.  See #7151.
  *
- * @param[in,out] is state to initialize
+ * @param cls a `struct GNUNET_CURL_Context *` to clean up.
  */
-static enum GNUNET_GenericReturnValue
-load_urls (struct TALER_TESTING_Interpreter *is)
+static void
+deferred_cleanup_cb (void *cls)
 {
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (is->cfg,
-                                             "auditor",
-                                             "BASE_URL",
-                                             &is->auditor_url))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "auditor",
-                               "BASE_URL");
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (is->cfg,
-                                             "exchange",
-                                             "BASE_URL",
-                                             &is->exchange_url))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "exchange",
-                               "BASE_URL");
-    GNUNET_free (is->auditor_url);
-    return GNUNET_SYSERR;
+  struct GNUNET_CURL_Context *ctx = cls;
+
+  GNUNET_CURL_fini (ctx);
+}
+
+
+/**
+ * Cleanup the state from a "authchange" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+authchange_cleanup (void *cls,
+                    const struct TALER_TESTING_Command *cmd)
+{
+  struct AuthchangeState *ss = cls;
+
+  (void) cmd;
+  if (NULL != ss->old_ctx)
+  {
+    (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
+                                     ss->old_ctx);
+    ss->old_ctx = NULL;
   }
-  return GNUNET_OK;
+  GNUNET_free (ss);
 }
 
 
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup (TALER_TESTING_Main main_cb,
-                     void *main_cb_cls,
-                     const struct GNUNET_CONFIGURATION_Handle *cfg,
-                     struct GNUNET_OS_Process *exchanged,
-                     int exchange_connect)
-{
-  struct TALER_TESTING_Interpreter is = {
-    .cfg = cfg,
-    .exchanged = exchanged
-  };
-  struct MainContext main_ctx = {
-    .main_cb = main_cb,
-    .main_cb_cls = main_cb_cls,
-    /* needed to init the curl ctx */
-    .is = &is,
-  };
-  struct GNUNET_SIGNAL_Context *shc_chld;
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_authorization (const char *label,
+                                     const char *auth_token)
+{
+  struct AuthchangeState *ss;
 
-  if (GNUNET_OK !=
-      load_keys (&is))
-    return GNUNET_SYSERR;
-  if (GNUNET_OK !=
-      load_urls (&is))
-    return GNUNET_SYSERR;
-  sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
-  GNUNET_assert (NULL != sigpipe);
-  shc_chld = GNUNET_SIGNAL_handler_install (
-    SIGCHLD,
-    &sighandler_child_death);
-  is.ctx = GNUNET_CURL_init (
-    &GNUNET_CURL_gnunet_scheduler_reschedule,
-    &is.rc);
-  GNUNET_CURL_enable_async_scope_header (is.ctx,
-                                         "Taler-Correlation-Id");
-  GNUNET_assert (NULL != is.ctx);
-  is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx);
+  ss = GNUNET_new (struct AuthchangeState);
+  ss->auth_token = auth_token;
 
-  /* Blocking */
-  if (GNUNET_YES == exchange_connect)
-    GNUNET_SCHEDULER_run (&main_wrapper_exchange_connect,
-                          &main_ctx);
-  else
-    GNUNET_SCHEDULER_run (&main_wrapper_exchange_agnostic,
-                          &main_ctx);
-  if (NULL != is.final_cleanup_cb)
-    is.final_cleanup_cb (is.final_cleanup_cb_cls);
-  GNUNET_free (main_ctx.exchange_url);
-  GNUNET_SIGNAL_handler_uninstall (shc_chld);
-  GNUNET_DISK_pipe_close (sigpipe);
-  sigpipe = NULL;
-  GNUNET_free (is.auditor_url);
-  GNUNET_free (is.exchange_url);
-  return is.result;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &authchange_run,
+      .cleanup = &authchange_cleanup
+    };
+
+    return cmd;
+  }
 }
 
 
diff --git a/src/testing/testing_api_misc.c b/src/testing/testing_api_misc.c
new file mode 100644
index 00000000..665402fd
--- /dev/null
+++ b/src/testing/testing_api_misc.c
@@ -0,0 +1,377 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-2023 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 testing/testing_api_misc.c
+ * @brief non-command functions useful for writing tests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_fakebank_lib.h"
+
+
+bool
+TALER_TESTING_has_in_name (const char *prog,
+                           const char *marker)
+{
+  size_t name_pos;
+  size_t pos;
+
+  if (! prog || ! marker)
+    return false;
+
+  pos = 0;
+  name_pos = 0;
+  while (prog[pos])
+  {
+    if ('/' == prog[pos])
+      name_pos = pos + 1;
+    pos++;
+  }
+  if (name_pos == pos)
+    return true;
+  return (NULL != strstr (prog + name_pos,
+                          marker));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+  const char *cfg_file,
+  const char *exchange_account_section,
+  enum TALER_TESTING_BankSystem bs,
+  struct TALER_TESTING_Credentials *ua)
+{
+  unsigned long long port;
+  char *exchange_payto_uri;
+
+  ua->cfg = GNUNET_CONFIGURATION_create ();
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_load (ua->cfg,
+                                 cfg_file))
+  {
+    GNUNET_break (0);
+    GNUNET_CONFIGURATION_destroy (ua->cfg);
+    return GNUNET_SYSERR;
+  }
+  if (0 !=
+      strncasecmp (exchange_account_section,
+                   "exchange-account-",
+                   strlen ("exchange-account-")))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             exchange_account_section,
+                                             "PAYTO_URI",
+                                             &exchange_payto_uri))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               exchange_account_section,
+                               "PAYTO_URI");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_number (ua->cfg,
+                                             "bank",
+                                             "HTTP_PORT",
+                                             &port))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "bank",
+                               "HTTP_PORT");
+    return GNUNET_SYSERR;
+  }
+  {
+    char *csn;
+
+    GNUNET_asprintf (&csn,
+                     "exchange-accountcredentials-%s",
+                     &exchange_account_section[strlen ("exchange-account-")]);
+    if (GNUNET_OK !=
+        TALER_BANK_auth_parse_cfg (ua->cfg,
+                                   csn,
+                                   &ua->ba))
+    {
+      GNUNET_break (0);
+      GNUNET_free (csn);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_free (csn);
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &ua->exchange_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             "auditor",
+                                             "BASE_URL",
+                                             &ua->auditor_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "auditor",
+                               "BASE_URL");
+    return GNUNET_SYSERR;
+  }
+
+  switch (bs)
+  {
+  case TALER_TESTING_BS_FAKEBANK:
+    ua->exchange_payto
+      = exchange_payto_uri;
+    ua->user42_payto
+      = GNUNET_strdup ("payto://x-taler-bank/localhost/42?receiver-name=42");
+    ua->user43_payto
+      = GNUNET_strdup ("payto://x-taler-bank/localhost/43?receiver-name=43");
+    break;
+  case TALER_TESTING_BS_IBAN:
+    ua->exchange_payto
+      = exchange_payto_uri;
+    ua->user42_payto
+      = GNUNET_strdup (
+          
"payto://iban/SANDBOXX/FR7630006000011234567890189?receiver-name=User42");
+    ua->user43_payto
+      = GNUNET_strdup (
+          "payto://iban/SANDBOXX/GB33BUKB20201555555555?receiver-name=User43");
+    break;
+  }
+  return GNUNET_OK;
+}
+
+
+json_t *
+TALER_TESTING_make_wire_details (const char *payto)
+{
+  struct TALER_WireSaltP salt;
+
+  /* salt must be constant for aggregation tests! */
+  memset (&salt,
+          47,
+          sizeof (salt));
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("payto_uri",
+                             payto),
+    GNUNET_JSON_pack_data_auto ("salt",
+                                &salt));
+}
+
+
+/**
+ * Remove @a option directory from @a section in @a cfg.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+remove_dir (const struct GNUNET_CONFIGURATION_Handle *cfg,
+            const char *section,
+            const char *option)
+{
+  char *dir;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               section,
+                                               option,
+                                               &dir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               option);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_YES ==
+      GNUNET_DISK_directory_test (dir,
+                                  GNUNET_NO))
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_directory_remove (dir));
+  GNUNET_free (dir);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_cleanup_files_cfg (
+  void *cls,
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *dir;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "exchange-offline",
+                                               "SECM_TOFU_FILE",
+                                               &dir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "SECM_TOFU_FILE");
+    return GNUNET_SYSERR;
+  }
+  if ( (0 != unlink (dir)) &&
+       (ENOENT != errno) )
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                              "unlink",
+                              dir);
+    GNUNET_free (dir);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (dir);
+  if (GNUNET_OK !=
+      remove_dir (cfg,
+                  "taler-exchange-secmod-eddsa",
+                  "KEY_DIR"))
+    return GNUNET_SYSERR;
+  if (GNUNET_OK !=
+      remove_dir (cfg,
+                  "taler-exchange-secmod-rsa",
+                  "KEY_DIR"))
+    return GNUNET_SYSERR;
+  return GNUNET_OK;
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_Amount *amount,
+  bool age_restricted)
+{
+  struct GNUNET_TIME_Timestamp now;
+  struct TALER_EXCHANGE_DenomPublicKey *pk;
+  char *str;
+
+  now = GNUNET_TIME_timestamp_get ();
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+  {
+    pk = &keys->denom_keys[i];
+    if ( (0 == TALER_amount_cmp (amount,
+                                 &pk->value)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     >=,
+                                     pk->valid_from)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     <,
+                                     pk->withdraw_valid_until)) &&
+         (age_restricted == (0 != pk->key.age_mask.bits)) )
+      return pk;
+  }
+  /* do 2nd pass to check if expiration times are to blame for
+   * failure */
+  str = TALER_amount_to_string (amount);
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+  {
+    pk = &keys->denom_keys[i];
+    if ( (0 == TALER_amount_cmp (amount,
+                                 &pk->value)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     <,
+                                     pk->valid_from) ||
+          GNUNET_TIME_timestamp_cmp (now,
+                                     >,
+                                     pk->withdraw_valid_until) ) &&
+         (age_restricted == (0 != pk->key.age_mask.bits)) )
+    {
+      GNUNET_log
+        (GNUNET_ERROR_TYPE_WARNING,
+        "Have denomination key for `%s', but with wrong"
+        " expiration range %llu vs [%llu,%llu)\n",
+        str,
+        (unsigned long long) now.abs_time.abs_value_us,
+        (unsigned long long) pk->valid_from.abs_time.abs_value_us,
+        (unsigned long long) pk->withdraw_valid_until.abs_time.abs_value_us);
+      GNUNET_free (str);
+      return NULL;
+    }
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "No denomination key for amount %s found\n",
+              str);
+  GNUNET_free (str);
+  return NULL;
+}
+
+
+int
+TALER_TESTING_wait_httpd_ready (const char *base_url)
+{
+  char *wget_cmd;
+  unsigned int iter;
+
+  GNUNET_asprintf (&wget_cmd,
+                   "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null",
+                   base_url); // make sure ends with '/'
+  /* give child time to start and bind against the socket */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Waiting for HTTP service to be ready (check with: %s)\n",
+              wget_cmd);
+  iter = 0;
+  do
+  {
+    if (10 == iter)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to launch HTTP service (or `wget')\n");
+      GNUNET_free (wget_cmd);
+      return 77;
+    }
+    sleep (1);
+    iter++;
+  }
+  while (0 != system (wget_cmd));
+  GNUNET_free (wget_cmd);
+  return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_url_port_free (const char *url)
+{
+  const char *port;
+  long pnum;
+
+  port = strrchr (url,
+                  (unsigned char) ':');
+  if (NULL == port)
+    pnum = 80;
+  else
+    pnum = strtol (port + 1, NULL, 10);
+  if (GNUNET_OK !=
+      GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+                                     pnum))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Port %u not available.\n",
+                (unsigned int) pnum);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
diff --git a/src/testing/testing_api_traits.c b/src/testing/testing_api_traits.c
index db94e81a..d84e2c37 100644
--- a/src/testing/testing_api_traits.c
+++ b/src/testing/testing_api_traits.c
@@ -67,7 +67,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
       return GNUNET_OK;
     }
   }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Trait %s/%u not found.\n",
               trait,
               index);
@@ -75,4 +75,31 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
 }
 
 
+struct TALER_EXCHANGE_Handle *
+TALER_TESTING_get_exchange (struct TALER_TESTING_Interpreter *is)
+{
+  struct TALER_EXCHANGE_Handle *exchange;
+  const struct TALER_TESTING_Command *exchange_cmd;
+
+  exchange_cmd
+    = TALER_TESTING_interpreter_get_command (is,
+                                             "exchange");
+  if (NULL == exchange_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_exchange (exchange_cmd,
+                                        &exchange))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return NULL;
+  }
+  return exchange;
+}
+
+
 /* end of testing_api_traits.c */
diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c
index cf81d915..f0d99fe6 100644
--- a/src/util/age_restriction.c
+++ b/src/util/age_restriction.c
@@ -135,7 +135,7 @@ ecdsa_create_from_seed (
 enum GNUNET_GenericReturnValue
 TALER_age_restriction_commit (
   const struct TALER_AgeMask *mask,
-  const uint8_t age,
+  uint8_t age,
   const struct GNUNET_HashCode *seed,
   struct TALER_AgeCommitmentProof *ncp)
 {
@@ -147,7 +147,7 @@ TALER_age_restriction_commit (
   GNUNET_assert (NULL != mask);
   GNUNET_assert (NULL != seed);
   GNUNET_assert (NULL != ncp);
-  GNUNET_assert (mask->bits & 1); /* fist bit must have been set */
+  GNUNET_assert (mask->bits & 1); /* first bit must have been set */
 
   num_pub = __builtin_popcount (mask->bits) - 1;
   num_priv = get_age_group (mask, age);

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]