gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: major refactor of fakebank imple


From: gnunet
Subject: [taler-exchange] branch master updated: major refactor of fakebank implementation
Date: Thu, 21 Sep 2023 00:01:53 +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 48e58124 major refactor of fakebank implementation
48e58124 is described below

commit 48e58124fb61ded372f147d00d112f108c997f81
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Sep 21 00:01:45 2023 +0200

    major refactor of fakebank implementation
---
 src/bank-lib/Makefile.am                           |   29 +-
 src/bank-lib/bank_api_admin.c                      |    4 +-
 src/bank-lib/fakebank.c                            | 4540 +-------------------
 src/bank-lib/fakebank.h                            |  696 +++
 src/bank-lib/fakebank_api_check.c                  |  238 +
 src/bank-lib/fakebank_bank.c                       |  224 +
 src/bank-lib/fakebank_bank.h                       |   54 +
 src/bank-lib/fakebank_bank_accounts_withdrawals.c  |  101 +
 src/bank-lib/fakebank_bank_accounts_withdrawals.h  |   50 +
 src/bank-lib/fakebank_bank_get_accounts.c          |   80 +
 src/bank-lib/fakebank_bank_get_accounts.h          |   48 +
 .../fakebank_bank_get_accounts_withdrawals.c       |  111 +
 .../fakebank_bank_get_accounts_withdrawals.h       |   53 +
 src/bank-lib/fakebank_bank_get_root.c              |   58 +
 src/bank-lib/fakebank_bank_get_root.h              |   44 +
 .../fakebank_bank_post_accounts_withdrawals.c      |  196 +
 .../fakebank_bank_post_accounts_withdrawals.h      |   54 +
 ...fakebank_bank_post_accounts_withdrawals_abort.c |   97 +
 ...fakebank_bank_post_accounts_withdrawals_abort.h |   50 +
 ...kebank_bank_post_accounts_withdrawals_confirm.c |  139 +
 ...kebank_bank_post_accounts_withdrawals_confirm.h |   50 +
 src/bank-lib/fakebank_bank_testing_register.c      |  128 +
 src/bank-lib/fakebank_bank_testing_register.h      |   53 +
 src/bank-lib/fakebank_common_lookup.c              |  103 +
 src/bank-lib/fakebank_common_lookup.h              |   62 +
 src/bank-lib/fakebank_common_lp.c                  |  344 ++
 src/bank-lib/fakebank_common_lp.h                  |  100 +
 src/bank-lib/fakebank_common_make_admin_transfer.c |  117 +
 src/bank-lib/fakebank_common_make_admin_transfer.h |   56 +
 src/bank-lib/fakebank_common_parser.c              |  138 +
 src/bank-lib/fakebank_common_parser.h              |   50 +
 src/bank-lib/fakebank_common_transact.c            |  261 ++
 src/bank-lib/fakebank_common_transact.h            |   76 +
 src/bank-lib/fakebank_stop.c                       |  192 +
 src/bank-lib/fakebank_tbi.c                        |  126 +
 src/bank-lib/fakebank_tbi.h                        |   54 +
 .../fakebank_tbi_get_withdrawal_operation.c        |  127 +
 .../fakebank_tbi_get_withdrawal_operation.h        |   51 +
 .../fakebank_tbi_post_withdrawal_operation.c       |  221 +
 .../fakebank_tbi_post_withdrawal_operation.h       |   53 +
 src/bank-lib/fakebank_tbr.c                        |   74 +
 src/bank-lib/fakebank_tbr.h                        |   58 +
 src/bank-lib/fakebank_tbr_get_history.c            |  305 ++
 src/bank-lib/fakebank_tbr_get_history.h            |   52 +
 src/bank-lib/fakebank_tbr_get_root.c               |   50 +
 src/bank-lib/fakebank_tbr_get_root.h               |   45 +
 src/bank-lib/fakebank_twg.c                        |  106 +
 src/bank-lib/fakebank_twg.h                        |   56 +
 src/bank-lib/fakebank_twg_admin_add_incoming.c     |  160 +
 src/bank-lib/fakebank_twg_admin_add_incoming.h     |   52 +
 src/bank-lib/fakebank_twg_get_root.c               |   58 +
 src/bank-lib/fakebank_twg_get_root.h               |   46 +
 src/bank-lib/fakebank_twg_history.c                |  537 +++
 src/bank-lib/fakebank_twg_history.h                |   67 +
 src/bank-lib/fakebank_twg_transfer.c               |  178 +
 src/bank-lib/fakebank_twg_transfer.h               |   55 +
 56 files changed, 6699 insertions(+), 4428 deletions(-)

diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am
index ffd428a6..783ed825 100644
--- a/src/bank-lib/Makefile.am
+++ b/src/bank-lib/Makefile.am
@@ -58,7 +58,34 @@ libtalerfakebank_la_LDFLAGS = \
   -version-info 0:0:0 \
   -no-undefined
 libtalerfakebank_la_SOURCES = \
-  fakebank.c
+  fakebank.c fakebank.h \
+  fakebank_api_check.c \
+  fakebank_common_lookup.c fakebank_common_lookup.h \
+  fakebank_common_lp.c fakebank_common_lp.h \
+  fakebank_common_make_admin_transfer.c fakebank_common_make_admin_transfer.h \
+  fakebank_common_parser.c fakebank_common_parser.h \
+  fakebank_common_transact.c fakebank_common_transact.h \
+  fakebank_stop.c \
+  fakebank_bank.c fakebank_bank.h \
+  fakebank_bank_accounts_withdrawals.c fakebank_bank_accounts_withdrawals.h \
+  fakebank_bank_get_accounts.c fakebank_bank_get_accounts.h \
+  fakebank_bank_get_accounts_withdrawals.c 
fakebank_bank_get_accounts_withdrawals.h \
+  fakebank_bank_get_root.c fakebank_bank_get_root.h \
+  fakebank_bank_post_accounts_withdrawals.c 
fakebank_bank_post_accounts_withdrawals.h \
+  fakebank_bank_post_accounts_withdrawals_abort.c 
fakebank_bank_post_accounts_withdrawals_abort.h \
+  fakebank_bank_post_accounts_withdrawals_confirm.c 
fakebank_bank_post_accounts_withdrawals_confirm.h \
+  fakebank_bank_testing_register.c fakebank_bank_testing_register.h \
+  fakebank_tbr.c fakebank_tbr.h \
+  fakebank_tbr_get_history.c fakebank_tbr_get_history.h \
+  fakebank_tbr_get_root.c fakebank_tbr_get_root.h \
+  fakebank_tbi.c fakebank_tbi.h \
+  fakebank_tbi_get_withdrawal_operation.c 
fakebank_tbi_get_withdrawal_operation.h \
+  fakebank_tbi_post_withdrawal_operation.c 
fakebank_tbi_post_withdrawal_operation.h \
+  fakebank_twg.c fakebank_twg.h \
+  fakebank_twg_admin_add_incoming.c fakebank_twg_admin_add_incoming.h \
+  fakebank_twg_get_root.c fakebank_twg_get_root.h \
+  fakebank_twg_history.c fakebank_twg_history.h \
+  fakebank_twg_transfer.c fakebank_twg_transfer.h
 libtalerfakebank_la_LIBADD = \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/mhd/libtalermhd.la \
diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c
index 0b8e80e9..47d8a5c6 100644
--- a/src/bank-lib/bank_api_admin.c
+++ b/src/bank-lib/bank_api_admin.c
@@ -16,7 +16,7 @@
 */
 /**
  * @file bank-lib/bank_api_admin.c
- * @brief Implementation of the /admin/ requests of the bank's HTTP API
+ * @brief Implementation of the /admin/add-incoming requests of the bank's 
HTTP API
  * @author Christian Grothoff
  */
 #include "platform.h"
@@ -27,7 +27,7 @@
 
 
 /**
- * @brief An admin/add-incoming Handle
+ * @brief An /admin/add-incoming Handle
  */
 struct TALER_BANK_AdminAddIncomingHandle
 {
diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c
index d0fbf769..84669557 100644
--- a/src/bank-lib/fakebank.c
+++ b/src/bank-lib/fakebank.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2016-2022 Taler Systems SA
+  (C) 2016-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
@@ -21,4366 +21,54 @@
  * @brief library that fakes being a Taler bank for testcases
  * @author Christian Grothoff <christian@grothoff.org>
  */
-// TODO: support adding WAD transfers
-
-#include "platform.h"
-#include <pthread.h>
-#include <poll.h>
-#ifdef __linux__
-#include <sys/eventfd.h>
-#endif
-#include "taler_fakebank_lib.h"
-#include "taler_bank_service.h"
-#include "taler_mhd_lib.h"
-#include <gnunet/gnunet_mhd_compat.h>
-
-/**
- * Maximum POST request size (for /admin/add-incoming)
- */
-#define REQUEST_BUFFER_MAX (4 * 1024)
-
-/**
- * How long are exchange base URLs allowed to be at most?
- * Set to a relatively low number as this does contribute
- * significantly to our RAM consumption.
- */
-#define MAX_URL_LEN 64
-
-/**
- * Per account information.
- */
-struct Account;
-
-
-/**
- * Types of long polling activities.
- */
-enum LongPollType
-{
-  /**
-   * Transfer TO the exchange.
-   */
-  LP_CREDIT,
-
-  /**
-   * Transfer FROM the exchange.
-   */
-  LP_DEBIT,
-
-  /**
-   * Withdraw operation completion/abort.
-   */
-  LP_WITHDRAW
-
-};
-
-/**
- * Client waiting for activity on this account.
- */
-struct LongPoller
-{
-
-  /**
-   * Kept in a DLL.
-   */
-  struct LongPoller *next;
-
-  /**
-   * Kept in a DLL.
-   */
-  struct LongPoller *prev;
-
-  /**
-   * Account this long poller is waiting on.
-   */
-  struct Account *account;
-
-  /**
-   * Withdraw operation we are waiting on,
-   * only if @e type is #LP_WITHDRAW, otherwise NULL.
-   */
-  const struct WithdrawalOperation *wo;
-
-  /**
-   * Entry in the heap for this long poller.
-   */
-  struct GNUNET_CONTAINER_HeapNode *hn;
-
-  /**
-   * Client that is waiting for transactions.
-   */
-  struct MHD_Connection *conn;
-
-  /**
-   * When will this long poller time out?
-   */
-  struct GNUNET_TIME_Absolute timeout;
-
-  /**
-   * What does the @e connection wait for?
-   */
-  enum LongPollType type;
-
-};
-
-
-/**
- * Details about a transcation we (as the simulated bank) received.
- */
-struct Transaction;
-
-
-/**
- * Information we keep per withdraw operation.
- */
-struct WithdrawalOperation
-{
-  /**
-   * Unique (random) operation ID.
-   */
-  struct GNUNET_ShortHashCode wopid;
-
-  /**
-   * Debited account.
-   */
-  struct Account *debit_account;
-
-  /**
-   * Target exchange account, or NULL if unknown.
-   */
-  const struct Account *exchange_account;
-
-  /**
-   * RowID of the resulting transaction, if any. Otherwise 0.
-   */
-  uint64_t row_id;
-
-  /**
-   * Amount transferred.
-   */
-  struct TALER_Amount amount;
-
-  /**
-   * Public key of the reserve, wire transfer subject.
-   */
-  struct TALER_ReservePublicKeyP reserve_pub;
-
-  /**
-   * When was the transaction made? 0 if not yet.
-   */
-  struct GNUNET_TIME_Timestamp timestamp;
-
-  /**
-   * Was the withdrawal aborted?
-   */
-  bool aborted;
-
-  /**
-   * Did the bank confirm the withdrawal?
-   */
-  bool confirmation_done;
-
-  /**
-   * Is @e reserve_pub initialized?
-   */
-  bool selection_done;
-
-};
-
-
-/**
- * Per account information.
- */
-struct Account
-{
-
-  /**
-   * Inbound transactions for this account in a MDLL.
-   */
-  struct Transaction *in_head;
-
-  /**
-   * Inbound transactions for this account in a MDLL.
-   */
-  struct Transaction *in_tail;
-
-  /**
-   * Outbound transactions for this account in a MDLL.
-   */
-  struct Transaction *out_head;
-
-  /**
-   * Outbound transactions for this account in a MDLL.
-   */
-  struct Transaction *out_tail;
-
-  /**
-   * Kept in a DLL.
-   */
-  struct LongPoller *lp_head;
-
-  /**
-   * Kept in a DLL.
-   */
-  struct LongPoller *lp_tail;
-
-  /**
-   * Account name (string, not payto!)
-   */
-  char *account_name;
-
-  /**
-   * Receiver name for payto:// URIs.
-   */
-  char *receiver_name;
-
-  /**
-   * Payto URI for this account.
-   */
-  char *payto_uri;
-
-  /**
-   * Password set for the account (if any).
-   */
-  char *password;
-
-  /**
-   * Current account balance.
-   */
-  struct TALER_Amount balance;
-
-  /**
-   * true if the balance is negative.
-   */
-  bool is_negative;
-
-};
-
-
-/**
- * Details about a transcation we (as the simulated bank) received.
- */
-struct Transaction
-{
-  /**
-   * We store inbound transactions in a MDLL.
-   */
-  struct Transaction *next_in;
-
-  /**
-   * We store inbound transactions in a MDLL.
-   */
-  struct Transaction *prev_in;
-
-  /**
-   * We store outbound transactions in a MDLL.
-   */
-  struct Transaction *next_out;
-
-  /**
-   * We store outbound transactions in a MDLL.
-   */
-  struct Transaction *prev_out;
-
-  /**
-   * Amount to be transferred.
-   */
-  struct TALER_Amount amount;
-
-  /**
-   * Account to debit.
-   */
-  struct Account *debit_account;
-
-  /**
-   * Account to credit.
-   */
-  struct Account *credit_account;
-
-  /**
-   * Random unique identifier for the request.
-   * Used to detect idempotent requests.
-   */
-  struct GNUNET_HashCode request_uid;
-
-  /**
-   * When did the transaction happen?
-   */
-  struct GNUNET_TIME_Timestamp date;
-
-  /**
-   * Number of this transaction.
-   */
-  uint64_t row_id;
-
-  /**
-   * What does the @e subject contain?
-   */
-  enum
-  {
-    /**
-     * Transfer TO the exchange.
-     */
-    T_CREDIT,
-
-    /**
-     * Transfer FROM the exchange.
-     */
-    T_DEBIT,
-
-    /**
-     * Exchange-to-exchange WAD transfer.
-     */
-    T_WAD,
-  } type;
-
-  /**
-   * Wire transfer subject.
-   */
-  union
-  {
-
-    /**
-     * Used if @e type is T_DEBIT.
-     */
-    struct
-    {
-
-      /**
-       * Subject of the transfer.
-       */
-      struct TALER_WireTransferIdentifierRawP wtid;
-
-      /**
-       * Base URL of the exchange.
-       */
-      char exchange_base_url[MAX_URL_LEN];
-
-    } debit;
-
-    /**
-     * Used if @e type is T_CREDIT.
-     */
-    struct
-    {
-
-      /**
-       * Reserve public key of the credit operation.
-       */
-      struct TALER_ReservePublicKeyP reserve_pub;
-
-    } credit;
-
-    /**
-     * Used if @e type is T_WAD.
-     */
-    struct
-    {
-
-      /**
-       * Subject of the transfer.
-       */
-      struct TALER_WadIdentifierP wad;
-
-      /**
-       * Base URL of the originating exchange.
-       */
-      char origin_base_url[MAX_URL_LEN];
-
-    } wad;
-
-  } subject;
-
-  /**
-   * Has this transaction not yet been subjected to
-   * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and
-   * should thus be counted in #TALER_FAKEBANK_check_empty()?
-   */
-  bool unchecked;
-};
-
-
-/**
- * Function called to clean up context of a connection.
- *
- * @param ctx context to clean up
- */
-typedef void
-(*ConnectionCleaner)(void *ctx);
-
-/**
- * Universal context we keep per connection.
- */
-struct ConnectionContext
-{
-  /**
-   * Function we call upon completion to clean up.
-   */
-  ConnectionCleaner ctx_cleaner;
-
-  /**
-   * Request-handler specific context.
-   */
-  void *ctx;
-};
-
-
-/**
- * This is the "base" structure for both the /history and the
- * /history-range API calls.
- */
-struct HistoryArgs
-{
-
-  /**
-   * Bank account number of the requesting client.
-   */
-  uint64_t account_number;
-
-  /**
-   * Index of the starting transaction, exclusive (!).
-   */
-  uint64_t start_idx;
-
-  /**
-   * Requested number of results and order
-   * (positive: ascending, negative: descending)
-   */
-  int64_t delta;
-
-  /**
-   * Timeout for long polling.
-   */
-  struct GNUNET_TIME_Relative lp_timeout;
-
-  /**
-   * true if starting point was given.
-   */
-  bool have_start;
-
-};
-
-
-/**
- * Context we keep per history request.
- */
-struct HistoryContext
-{
-  /**
-   * When does this request time out.
-   */
-  struct GNUNET_TIME_Absolute timeout;
-
-  /**
-   * Client arguments for this request.
-   */
-  struct HistoryArgs ha;
-
-  /**
-   * Account the request is about.
-   */
-  struct Account *acc;
-
-  /**
-   * Payto URI of the account.
-   */
-  char *payto_uri;
-
-  /**
-   * JSON object we are building to return.
-   */
-  json_t *history;
-
-};
-
-
-/**
- * Function called to clean up a history context.
- *
- * @param cls a `struct HistoryContext *`
- */
-static void
-history_cleanup (void *cls)
-{
-  struct HistoryContext *hc = cls;
-
-  GNUNET_free (hc->payto_uri);
-  json_decref (hc->history);
-  GNUNET_free (hc);
-}
-
-
-/**
- * Context we keep per get withdrawal operation request.
- */
-struct WithdrawContext
-{
-  /**
-   * When does this request time out.
-   */
-  struct GNUNET_TIME_Absolute timeout;
-
-  /**
-   * The withdrawal operation this is about.
-   */
-  struct WithdrawalOperation *wo;
-
-};
-
-
-/**
- * Function called to clean up a withdraw context.
- *
- * @param cls a `struct WithdrawContext *`
- */
-static void
-withdraw_cleanup (void *cls)
-{
-  struct WithdrawContext *wc = cls;
-
-  GNUNET_free (wc);
-}
-
-
-/**
- * Handle for the fake bank.
- */
-struct TALER_FAKEBANK_Handle
-{
-  /**
-   * We store transactions in a revolving array.
-   */
-  struct Transaction **transactions;
-
-  /**
-   * HTTP server we run to pretend to be the "test" bank.
-   */
-  struct MHD_Daemon *mhd_bank;
-
-  /**
-   * Task running HTTP server for the "test" bank,
-   * unless we are using a thread pool (then NULL).
-   */
-  struct GNUNET_SCHEDULER_Task *mhd_task;
-
-  /**
-   * Task for expiring long-polling connections,
-   * unless we are using a thread pool (then NULL).
-   */
-  struct GNUNET_SCHEDULER_Task *lp_task;
-
-  /**
-   * Task for expiring long-polling connections, unless we are using the
-   * GNUnet scheduler (then NULL).
-   */
-  pthread_t lp_thread;
-
-  /**
-   * MIN-heap of long pollers, sorted by timeout.
-   */
-  struct GNUNET_CONTAINER_Heap *lp_heap;
-
-  /**
-   * Hashmap of reserve public keys to
-   * `struct Transaction` with that reserve public
-   * key. Used to prevent public-key re-use.
-   */
-  struct GNUNET_CONTAINER_MultiPeerMap *rpubs;
-
-  /**
-   * Hashmap of short hashes (wopids) to
-   * `struct WithdrawalOperation`.
-   * Used to lookup withdrawal operations.
-   */
-  struct GNUNET_CONTAINER_MultiShortmap *wops;
-
-  /**
-   * (Base) URL to suggest for the exchange.  Can
-   * be NULL if there is no suggestion to be made.
-   */
-  char *exchange_url;
-
-  /**
-   * Lock for accessing @a rpubs map.
-   */
-  pthread_mutex_t rpubs_lock;
-
-  /**
-   * Hashmap of hashes of account names to `struct Account`.
-   */
-  struct GNUNET_CONTAINER_MultiHashMap *accounts;
-
-  /**
-   * Lock for accessing @a accounts hash map.
-   */
-  pthread_mutex_t accounts_lock;
-
-  /**
-   * Hashmap of hashes of transaction request_uids to `struct Transaction`.
-   */
-  struct GNUNET_CONTAINER_MultiHashMap *uuid_map;
-
-  /**
-   * Lock for accessing @a uuid_map.
-   */
-  pthread_mutex_t uuid_map_lock;
-
-  /**
-   * Lock for accessing the internals of
-   * accounts and transaction array entries.
-   */
-  pthread_mutex_t big_lock;
-
-  /**
-   * How much money should be put into new accounts
-   * on /register.
-   */
-  struct TALER_Amount signup_bonus;
-
-  /**
-   * Current transaction counter.
-   */
-  uint64_t serial_counter;
-
-  /**
-   * Number of transactions we keep in memory (at most).
-   */
-  uint64_t ram_limit;
-
-  /**
-   * Currency used by the fakebank.
-   */
-  char *currency;
-
-  /**
-   * Hostname of the fakebank.
-   */
-  char *hostname;
-
-  /**
-   * BaseURL of the fakebank.
-   */
-  char *my_baseurl;
-
-  /**
-   * Our port number.
-   */
-  uint16_t port;
-
-#ifdef __linux__
-  /**
-   * Event FD to signal @a lp_thread a change in
-   * @a lp_heap.
-   */
-  int lp_event;
-#else
-  /**
-   * Pipe input to signal @a lp_thread a change in
-   * @a lp_heap.
-   */
-  int lp_event_in;
-
-  /**
-   * Pipe output to signal @a lp_thread a change in
-   * @a lp_heap.
-   */
-  int lp_event_out;
-#endif
-
-  /**
-   * Set to true once we are shutting down.
-   */
-  bool in_shutdown;
-
-  /**
-   * Should we run MHD immediately again?
-   */
-  bool mhd_again;
-
-#if EPOLL_SUPPORT
-  /**
-   * Boxed @e mhd_fd.
-   */
-  struct GNUNET_NETWORK_Handle *mhd_rfd;
-
-  /**
-   * File descriptor to use to wait for MHD.
-   */
-  int mhd_fd;
-#endif
-};
-
-
-/**
- * Task run whenever HTTP server operations are pending.
- *
- * @param cls the `struct TALER_FAKEBANK_Handle`
- */
-static void
-run_mhd (void *cls);
-
-
-/**
- * Find withdrawal operation @a wopid in @a h.
- *
- * @param h fakebank handle
- * @param wopid withdrawal operation ID as a string
- * @return NULL if operation was not found
- */
-static struct WithdrawalOperation *
-lookup_withdrawal_operation (struct TALER_FAKEBANK_Handle *h,
-                             const char *wopid)
-{
-  struct GNUNET_ShortHashCode sh;
-
-  if (NULL == h->wops)
-    return NULL;
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (wopid,
-                                     strlen (wopid),
-                                     &sh,
-                                     sizeof (sh)))
-  {
-    GNUNET_break_op (0);
-    return NULL;
-  }
-  return GNUNET_CONTAINER_multishortmap_get (h->wops,
-                                             &sh);
-}
-
-
-/**
- * Trigger the @a lp. Frees associated resources,
- * except the entry of @a lp in the timeout heap.
- * Must be called while the ``big lock`` is held.
- *
- * @param[in] lp long poller to trigger
- * @param[in,out] h fakebank handle
- */
-static void
-lp_trigger (struct LongPoller *lp,
-            struct TALER_FAKEBANK_Handle *h)
-{
-  struct Account *acc = lp->account;
-
-  GNUNET_CONTAINER_DLL_remove (acc->lp_head,
-                               acc->lp_tail,
-                               lp);
-  MHD_resume_connection (lp->conn);
-  GNUNET_free (lp);
-  h->mhd_again = true;
-#ifdef __linux__
-  if (-1 == h->lp_event)
-#else
-  if ( (-1 == h->lp_event_in) &&
-       (-1 == h->lp_event_out) )
-#endif
-  {
-    if (NULL != h->mhd_task)
-      GNUNET_SCHEDULER_cancel (h->mhd_task);
-    h->mhd_task =
-      GNUNET_SCHEDULER_add_now (&run_mhd,
-                                h);
-  }
-}
-
-
-/**
- * Thread that is run to wake up connections that have hit their timeout. Runs
- * until in_shutdown is set to true. Must be send signals via lp_event on
- * shutdown and/or whenever the heap changes to an earlier timeout.
- *
- * @param cls a `struct TALER_FAKEBANK_Handle *`
- * @return NULL
- */
-static void *
-lp_expiration_thread (void *cls)
-{
-  struct TALER_FAKEBANK_Handle *h = cls;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  while (! h->in_shutdown)
-  {
-    struct LongPoller *lp;
-    int timeout_ms;
-
-    lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
-    while ( (NULL != lp) &&
-            GNUNET_TIME_absolute_is_past (lp->timeout))
-    {
-      GNUNET_assert (lp ==
-                     GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
-      lp_trigger (lp,
-                  h);
-      lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
-    }
-    if (NULL != lp)
-    {
-      struct GNUNET_TIME_Relative rem;
-      unsigned long long left_ms;
-
-      rem = GNUNET_TIME_absolute_get_remaining (lp->timeout);
-      left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
-      if (left_ms > INT_MAX)
-        timeout_ms = INT_MAX;
-      else
-        timeout_ms = (int) left_ms;
-    }
-    else
-    {
-      timeout_ms = -1; /* infinity */
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    {
-      struct pollfd p = {
-#ifdef __linux__
-        .fd = h->lp_event,
-#else
-        .fd = h->lp_event_out,
-#endif
-        .events = POLLIN
-      };
-      int ret;
-
-      ret = poll (&p,
-                  1,
-                  timeout_ms);
-      if (-1 == ret)
-      {
-        if (EINTR != errno)
-          GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
-                               "poll");
-      }
-      else if (1 == ret)
-      {
-        /* clear event */
-        uint64_t ev;
-        ssize_t iret;
-
-#ifdef __linux__
-        iret = read (h->lp_event,
-#else
-        iret = read (h->lp_event_out,
-#endif
-                     &ev,
-                     sizeof (ev));
-        if (-1 == iret)
-        {
-          GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
-                               "read");
-        }
-        else
-        {
-          GNUNET_break (sizeof (uint64_t) == iret);
-        }
-      }
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return NULL;
-}
-
-
-/**
- * Lookup account with @a name, and if it does not exist, create it.
- *
- * @param[in,out] h bank to lookup account at
- * @param name account name to resolve
- * @param receiver_name receiver name in payto:// URI,
- *         NULL if the account must already exist
- * @return account handle, NULL if account does not yet exist
- */
-static struct Account *
-lookup_account (struct TALER_FAKEBANK_Handle *h,
-                const char *name,
-                const char *receiver_name)
-{
-  struct GNUNET_HashCode hc;
-  size_t slen;
-  struct Account *account;
-
-  memset (&hc,
-          0,
-          sizeof (hc));
-  slen = strlen (name);
-  GNUNET_CRYPTO_hash (name,
-                      slen,
-                      &hc);
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->accounts_lock));
-  account = GNUNET_CONTAINER_multihashmap_get (h->accounts,
-                                               &hc);
-  if (NULL == account)
-  {
-    if (NULL == receiver_name)
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->accounts_lock));
-      return NULL;
-    }
-    account = GNUNET_new (struct Account);
-    account->account_name = GNUNET_strdup (name);
-    account->receiver_name = GNUNET_strdup (receiver_name);
-    GNUNET_asprintf (&account->payto_uri,
-                     "payto://x-taler-bank/%s/%s?receiver-name=%s",
-                     h->hostname,
-                     account->account_name,
-                     account->receiver_name);
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (h->currency,
-                                          &account->balance));
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CONTAINER_multihashmap_put (h->accounts,
-                                                      &hc,
-                                                      account,
-                                                      
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->accounts_lock));
-  return account;
-}
-
-
-/**
- * Generate log messages for failed check operation.
- *
- * @param h handle to output transaction log for
- */
-static void
-check_log (struct TALER_FAKEBANK_Handle *h)
-{
-  for (uint64_t i = 0; i<h->ram_limit; i++)
-  {
-    struct Transaction *t = h->transactions[i];
-
-    if (NULL == t)
-      continue;
-    if (! t->unchecked)
-      continue;
-    switch (t->type)
-    {
-    case T_DEBIT:
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "%s -> %s (%s) %s (%s)\n",
-                  t->debit_account->account_name,
-                  t->credit_account->account_name,
-                  TALER_amount2s (&t->amount),
-                  t->subject.debit.exchange_base_url,
-                  "DEBIT");
-      break;
-    case T_CREDIT:
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "%s -> %s (%s) %s (%s)\n",
-                  t->debit_account->account_name,
-                  t->credit_account->account_name,
-                  TALER_amount2s (&t->amount),
-                  TALER_B2S (&t->subject.credit.reserve_pub),
-                  "CREDIT");
-      break;
-    case T_WAD:
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "%s -> %s (%s) %s[%s] (%s)\n",
-                  t->debit_account->account_name,
-                  t->credit_account->account_name,
-                  TALER_amount2s (&t->amount),
-                  t->subject.wad.origin_base_url,
-                  TALER_B2S (&t->subject.wad),
-                  "WAD");
-      break;
-    }
-  }
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
-                            const struct TALER_Amount *want_amount,
-                            const char *want_debit,
-                            const char *want_credit,
-                            const char *exchange_base_url,
-                            struct TALER_WireTransferIdentifierRawP *wtid)
-{
-  struct Account *debit_account;
-  struct Account *credit_account;
-
-  GNUNET_assert (0 ==
-                 strcasecmp (want_amount->currency,
-                             h->currency));
-  debit_account = lookup_account (h,
-                                  want_debit,
-                                  NULL);
-  credit_account = lookup_account (h,
-                                   want_credit,
-                                   NULL);
-  if (NULL == debit_account)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "I wanted: %s->%s (%s) from exchange %s (DEBIT), but debit 
account does not even exist!\n",
-                want_debit,
-                want_credit,
-                TALER_amount2s (want_amount),
-                exchange_base_url);
-    return GNUNET_SYSERR;
-  }
-  if (NULL == credit_account)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "I wanted: %s->%s (%s) from exchange %s (DEBIT), but credit 
account does not even exist!\n",
-                want_debit,
-                want_credit,
-                TALER_amount2s (want_amount),
-                exchange_base_url);
-    return GNUNET_SYSERR;
-  }
-  for (struct Transaction *t = debit_account->out_tail;
-       NULL != t;
-       t = t->prev_out)
-  {
-    if ( (t->unchecked) &&
-         (credit_account == t->credit_account) &&
-         (T_DEBIT == t->type) &&
-         (0 == TALER_amount_cmp (want_amount,
-                                 &t->amount)) &&
-         (0 == strcasecmp (exchange_base_url,
-                           t->subject.debit.exchange_base_url)) )
-    {
-      *wtid = t->subject.debit.wtid;
-      t->unchecked = false;
-      return GNUNET_OK;
-    }
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "Did not find matching transaction! I have:\n");
-  check_log (h);
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "I wanted: %s->%s (%s) from exchange %s (DEBIT)\n",
-              want_debit,
-              want_credit,
-              TALER_amount2s (want_amount),
-              exchange_base_url);
-  return GNUNET_SYSERR;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
-                             const struct TALER_Amount *want_amount,
-                             const char *want_debit,
-                             const char *want_credit,
-                             const struct TALER_ReservePublicKeyP *reserve_pub)
-{
-  struct Account *debit_account;
-  struct Account *credit_account;
-
-  GNUNET_assert (0 == strcasecmp (want_amount->currency,
-                                  h->currency));
-  debit_account = lookup_account (h,
-                                  want_debit,
-                                  NULL);
-  credit_account = lookup_account (h,
-                                   want_credit,
-                                   NULL);
-  if (NULL == debit_account)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but debit 
account is unknown.\n",
-                want_debit,
-                want_credit,
-                TALER_amount2s (want_amount),
-                TALER_B2S (reserve_pub));
-    return GNUNET_SYSERR;
-  }
-  if (NULL == credit_account)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but credit 
account is unknown.\n",
-                want_debit,
-                want_credit,
-                TALER_amount2s (want_amount),
-                TALER_B2S (reserve_pub));
-    return GNUNET_SYSERR;
-  }
-  for (struct Transaction *t = credit_account->in_tail;
-       NULL != t;
-       t = t->prev_in)
-  {
-    if ( (t->unchecked) &&
-         (debit_account == t->debit_account) &&
-         (T_CREDIT == t->type) &&
-         (0 == TALER_amount_cmp (want_amount,
-                                 &t->amount)) &&
-         (0 == GNUNET_memcmp (reserve_pub,
-                              &t->subject.credit.reserve_pub)) )
-    {
-      t->unchecked = false;
-      return GNUNET_OK;
-    }
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "Did not find matching transaction!\nI have:\n");
-  check_log (h);
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "I wanted:\n%s -> %s (%s) with subject %s (CREDIT)\n",
-              want_debit,
-              want_credit,
-              TALER_amount2s (want_amount),
-              TALER_B2S (reserve_pub));
-  return GNUNET_SYSERR;
-}
-
-
-/**
- * Update @a account balance by @a amount.
- *
- * The @a big_lock must already be locked when calling
- * this function.
- *
- * @param[in,out] account account to update
- * @param amount balance change
- * @param debit true to subtract, false to add @a amount
- */
-static void
-update_balance (struct Account *account,
-                const struct TALER_Amount *amount,
-                bool debit)
-{
-  if (debit == account->is_negative)
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_add (&account->balance,
-                                     &account->balance,
-                                     amount));
-    return;
-  }
-  if (0 <= TALER_amount_cmp (&account->balance,
-                             amount))
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_subtract (&account->balance,
-                                          &account->balance,
-                                          amount));
-  }
-  else
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_subtract (&account->balance,
-                                          amount,
-                                          &account->balance));
-    account->is_negative = ! account->is_negative;
-  }
-}
-
-
-/**
- * Add transaction to the debit and credit accounts,
- * updating the balances as needed.
- *
- * The transaction @a t must already be locked
- * when calling this function!
- *
- * @param[in,out] h bank handle
- * @param[in,out] t transaction to add to account lists
- */
-static void
-post_transaction (struct TALER_FAKEBANK_Handle *h,
-                  struct Transaction *t)
-{
-  struct Account *debit_acc = t->debit_account;
-  struct Account *credit_acc = t->credit_account;
-  uint64_t row_id;
-  struct Transaction *old;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  row_id = ++h->serial_counter;
-  old = h->transactions[row_id % h->ram_limit];
-  h->transactions[row_id % h->ram_limit] = t;
-  t->row_id = row_id;
-  GNUNET_CONTAINER_MDLL_insert_tail (out,
-                                     debit_acc->out_head,
-                                     debit_acc->out_tail,
-                                     t);
-  update_balance (debit_acc,
-                  &t->amount,
-                  true);
-  GNUNET_CONTAINER_MDLL_insert_tail (in,
-                                     credit_acc->in_head,
-                                     credit_acc->in_tail,
-                                     t);
-  update_balance (credit_acc,
-                  &t->amount,
-                  false);
-  if (NULL != old)
-  {
-    struct Account *da;
-    struct Account *ca;
-
-    da = old->debit_account;
-    ca = old->credit_account;
-    /* slot was already in use, must clean out old
-       entry first! */
-    GNUNET_CONTAINER_MDLL_remove (out,
-                                  da->out_head,
-                                  da->out_tail,
-                                  old);
-    GNUNET_CONTAINER_MDLL_remove (in,
-                                  ca->in_head,
-                                  ca->in_tail,
-                                  old);
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  if ( (NULL != old) &&
-       (T_DEBIT == old->type) )
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->uuid_map_lock));
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CONTAINER_multihashmap_remove (h->uuid_map,
-                                                         &old->request_uid,
-                                                         old));
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->uuid_map_lock));
-  }
-  GNUNET_free (old);
-}
-
-
-/**
- * Trigger long pollers that might have been waiting
- * for @a t.
- *
- * @param h fakebank handle
- * @param t transaction to notify on
- */
-static void
-notify_transaction (struct TALER_FAKEBANK_Handle *h,
-                    struct Transaction *t)
-{
-  struct Account *debit_acc = t->debit_account;
-  struct Account *credit_acc = t->credit_account;
-  struct LongPoller *nxt;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  for (struct LongPoller *lp = debit_acc->lp_head;
-       NULL != lp;
-       lp = nxt)
-  {
-    nxt = lp->next;
-    if (LP_DEBIT == lp->type)
-    {
-      GNUNET_assert (lp ==
-                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
-      lp_trigger (lp,
-                  h);
-    }
-  }
-  for (struct LongPoller *lp = credit_acc->lp_head;
-       NULL != lp;
-       lp = nxt)
-  {
-    nxt = lp->next;
-    if (LP_CREDIT == lp->type)
-    {
-      GNUNET_assert (lp ==
-                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
-      lp_trigger (lp,
-                  h);
-    }
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-}
-
-
-/**
- * Tell the fakebank to create another wire transfer *from* an exchange.
- *
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param subject wire transfer subject to use
- * @param exchange_base_url exchange URL
- * @param request_uid unique number to make the request unique, or NULL to 
create one
- * @param[out] ret_row_id pointer to store the row ID of this transaction
- * @param[out] timestamp set to the time of the transfer
- * @return #GNUNET_YES if the transfer was successful,
- *         #GNUNET_SYSERR if the request_uid was reused for a different 
transfer
- */
-static enum GNUNET_GenericReturnValue
-make_transfer (
-  struct TALER_FAKEBANK_Handle *h,
-  const char *debit_account,
-  const char *credit_account,
-  const struct TALER_Amount *amount,
-  const struct TALER_WireTransferIdentifierRawP *subject,
-  const char *exchange_base_url,
-  const struct GNUNET_HashCode *request_uid,
-  uint64_t *ret_row_id,
-  struct GNUNET_TIME_Timestamp *timestamp)
-{
-  struct Transaction *t;
-  struct Account *debit_acc;
-  struct Account *credit_acc;
-  size_t url_len;
-
-  GNUNET_assert (0 == strcasecmp (amount->currency,
-                                  h->currency));
-  GNUNET_assert (NULL != debit_account);
-  GNUNET_assert (NULL != credit_account);
-  GNUNET_break (0 != strncasecmp ("payto://",
-                                  debit_account,
-                                  strlen ("payto://")));
-  GNUNET_break (0 != strncasecmp ("payto://",
-                                  credit_account,
-                                  strlen ("payto://")));
-  url_len = strlen (exchange_base_url);
-  GNUNET_assert (url_len < MAX_URL_LEN);
-  debit_acc = lookup_account (h,
-                              debit_account,
-                              debit_account);
-  credit_acc = lookup_account (h,
-                               credit_account,
-                               credit_account);
-  if (NULL != request_uid)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->uuid_map_lock));
-    t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map,
-                                           request_uid);
-    if (NULL != t)
-    {
-      if ( (debit_acc != t->debit_account) ||
-            (credit_acc != t->credit_account) ||
-           (0 != TALER_amount_cmp (amount,
-                                   &t->amount)) ||
-           (T_DEBIT != t->type) ||
-           (0 != GNUNET_memcmp (subject,
-                                &t->subject.debit.wtid)) )
-      {
-        /* Transaction exists, but with different details. */
-        GNUNET_break (0);
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->uuid_map_lock));
-        return GNUNET_SYSERR;
-      }
-      *ret_row_id = t->row_id;
-      *timestamp = t->date;
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->uuid_map_lock));
-      return GNUNET_OK;
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->uuid_map_lock));
-  }
-  t = GNUNET_new (struct Transaction);
-  t->unchecked = true;
-  t->debit_account = debit_acc;
-  t->credit_account = credit_acc;
-  t->amount = *amount;
-  t->date = GNUNET_TIME_timestamp_get ();
-  if (NULL != timestamp)
-    *timestamp = t->date;
-  t->type = T_DEBIT;
-  GNUNET_memcpy (t->subject.debit.exchange_base_url,
-                 exchange_base_url,
-                 url_len);
-  t->subject.debit.wtid = *subject;
-  if (NULL == request_uid)
-    GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
-                                      &t->request_uid);
-  else
-    t->request_uid = *request_uid;
-  post_transaction (h,
-                    t);
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->uuid_map_lock));
-  GNUNET_assert (GNUNET_OK ==
-                 GNUNET_CONTAINER_multihashmap_put (
-                   h->uuid_map,
-                   &t->request_uid,
-                   t,
-                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->uuid_map_lock));
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Making transfer %llu from %s to %s over %s and subject %s; for 
exchange: %s\n",
-              (unsigned long long) t->row_id,
-              debit_account,
-              credit_account,
-              TALER_amount2s (amount),
-              TALER_B2S (subject),
-              exchange_base_url);
-  *ret_row_id = t->row_id;
-  notify_transaction (h,
-                      t);
-  return GNUNET_OK;
-}
-
-
-/**
- * Tell the fakebank to create another wire transfer *to* an exchange.
- *
- * @param h fake bank handle
- * @param debit_account account to debit
- * @param credit_account account to credit
- * @param amount amount to transfer
- * @param reserve_pub reserve public key to use in subject
- * @param[out] row_id serial_id of the transfer
- * @param[out] timestamp when was the transfer made
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-make_admin_transfer (
-  struct TALER_FAKEBANK_Handle *h,
-  const char *debit_account,
-  const char *credit_account,
-  const struct TALER_Amount *amount,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  uint64_t *row_id,
-  struct GNUNET_TIME_Timestamp *timestamp)
-{
-  struct Transaction *t;
-  const struct GNUNET_PeerIdentity *pid;
-  struct Account *debit_acc;
-  struct Account *credit_acc;
-
-  GNUNET_static_assert (sizeof (*pid) ==
-                        sizeof (*reserve_pub));
-  pid = (const struct GNUNET_PeerIdentity *) reserve_pub;
-  GNUNET_assert (NULL != debit_account);
-  GNUNET_assert (NULL != credit_account);
-  GNUNET_assert (0 == strcasecmp (amount->currency,
-                                  h->currency));
-  GNUNET_break (0 != strncasecmp ("payto://",
-                                  debit_account,
-                                  strlen ("payto://")));
-  GNUNET_break (0 != strncasecmp ("payto://",
-                                  credit_account,
-                                  strlen ("payto://")));
-  debit_acc = lookup_account (h,
-                              debit_account,
-                              debit_account);
-  credit_acc = lookup_account (h,
-                               credit_account,
-                               credit_account);
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->rpubs_lock));
-  t = GNUNET_CONTAINER_multipeermap_get (h->rpubs,
-                                         pid);
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->rpubs_lock));
-  if (NULL != t)
-  {
-    /* duplicate reserve public key not allowed */
-    GNUNET_break_op (0);
-    return GNUNET_NO;
-  }
-
-  t = GNUNET_new (struct Transaction);
-  t->unchecked = true;
-  t->debit_account = debit_acc;
-  t->credit_account = credit_acc;
-  t->amount = *amount;
-  t->date = GNUNET_TIME_timestamp_get ();
-  if (NULL != timestamp)
-    *timestamp = t->date;
-  t->type = T_CREDIT;
-  t->subject.credit.reserve_pub = *reserve_pub;
-  post_transaction (h,
-                    t);
-  if (NULL != row_id)
-    *row_id = t->row_id;
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->rpubs_lock));
-  GNUNET_assert (GNUNET_OK ==
-                 GNUNET_CONTAINER_multipeermap_put (
-                   h->rpubs,
-                   pid,
-                   t,
-                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->rpubs_lock));
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Making transfer from %s to %s over %s and subject %s at row 
%llu\n",
-              debit_account,
-              credit_account,
-              TALER_amount2s (amount),
-              TALER_B2S (reserve_pub),
-              (unsigned long long) t->row_id);
-  notify_transaction (h,
-                      t);
-  return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h)
-{
-  for (uint64_t i = 0; i<h->ram_limit; i++)
-  {
-    struct Transaction *t = h->transactions[i];
-
-    if ( (NULL != t) &&
-         (t->unchecked) )
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Expected empty transaction set, but I have:\n");
-      check_log (h);
-      return GNUNET_SYSERR;
-    }
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Helper function to free memory when finished.
- *
- * @param cls NULL
- * @param key key of the account to free (ignored)
- * @param val a `struct Account` to free.
- */
-static enum GNUNET_GenericReturnValue
-free_account (void *cls,
-              const struct GNUNET_HashCode *key,
-              void *val)
-{
-  struct Account *account = val;
-
-  (void) cls;
-  (void) key;
-  GNUNET_assert (NULL == account->lp_head);
-  GNUNET_free (account->account_name);
-  GNUNET_free (account->receiver_name);
-  GNUNET_free (account->payto_uri);
-  GNUNET_free (account->password);
-  GNUNET_free (account);
-  return GNUNET_OK;
-}
-
-
-/**
- * Helper function to free memory when finished.
- *
- * @param cls NULL
- * @param key key of the operation to free (ignored)
- * @param val a `struct WithdrawalOperation *` to free.
- */
-static enum GNUNET_GenericReturnValue
-free_withdraw_op (void *cls,
-                  const struct GNUNET_ShortHashCode *key,
-                  void *val)
-{
-  struct WithdrawalOperation *wo = val;
-
-  (void) cls;
-  (void) key;
-  GNUNET_free (wo);
-  return GNUNET_OK;
-}
-
-
-void
-TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
-{
-  if (NULL != h->lp_task)
-  {
-    GNUNET_SCHEDULER_cancel (h->lp_task);
-    h->lp_task = NULL;
-  }
-#if EPOLL_SUPPORT
-  if (NULL != h->mhd_rfd)
-  {
-    GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd);
-    h->mhd_rfd = NULL;
-  }
-#endif
-#ifdef __linux__
-  if (-1 != h->lp_event)
-#else
-  if (-1 != h->lp_event_in && -1 != h->lp_event_out)
-#endif
-  {
-    uint64_t val = 1;
-    void *ret;
-    struct LongPoller *lp;
-
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-    h->in_shutdown = true;
-    while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
-      lp_trigger (lp,
-                  h);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    GNUNET_break (sizeof (val) ==
-#ifdef __linux__
-                  write (h->lp_event,
-#else
-                  write (h->lp_event_in,
-#endif
-                         &val,
-                         sizeof (val)));
-    GNUNET_break (0 ==
-                  pthread_join (h->lp_thread,
-                                &ret));
-    GNUNET_break (NULL == ret);
-#ifdef __linux__
-    GNUNET_break (0 == close (h->lp_event));
-    h->lp_event = -1;
-#else
-    GNUNET_break (0 == close (h->lp_event_in));
-    GNUNET_break (0 == close (h->lp_event_out));
-    h->lp_event_in = -1;
-    h->lp_event_out = -1;
-#endif
-  }
-  else
-  {
-    struct LongPoller *lp;
-
-    while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
-      lp_trigger (lp,
-                  h);
-  }
-  if (NULL != h->mhd_bank)
-  {
-    MHD_stop_daemon (h->mhd_bank);
-    h->mhd_bank = NULL;
-  }
-  if (NULL != h->mhd_task)
-  {
-    GNUNET_SCHEDULER_cancel (h->mhd_task);
-    h->mhd_task = NULL;
-  }
-  if (NULL != h->accounts)
-  {
-    GNUNET_CONTAINER_multihashmap_iterate (h->accounts,
-                                           &free_account,
-                                           NULL);
-    GNUNET_CONTAINER_multihashmap_destroy (h->accounts);
-  }
-  if (NULL != h->wops)
-  {
-    GNUNET_CONTAINER_multishortmap_iterate (h->wops,
-                                            &free_withdraw_op,
-                                            NULL);
-    GNUNET_CONTAINER_multishortmap_destroy (h->wops);
-  }
-  GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map);
-  GNUNET_CONTAINER_multipeermap_destroy (h->rpubs);
-  GNUNET_CONTAINER_heap_destroy (h->lp_heap);
-  GNUNET_assert (0 ==
-                 pthread_mutex_destroy (&h->big_lock));
-  GNUNET_assert (0 ==
-                 pthread_mutex_destroy (&h->uuid_map_lock));
-  GNUNET_assert (0 ==
-                 pthread_mutex_destroy (&h->accounts_lock));
-  GNUNET_assert (0 ==
-                 pthread_mutex_destroy (&h->rpubs_lock));
-  for (uint64_t i = 0; i<h->ram_limit; i++)
-    GNUNET_free (h->transactions[i]);
-  GNUNET_free (h->transactions);
-  GNUNET_free (h->my_baseurl);
-  GNUNET_free (h->currency);
-  GNUNET_free (h->exchange_url);
-  GNUNET_free (h->hostname);
-  GNUNET_free (h);
-}
-
-
-/**
- * Function called whenever MHD is done with a request.  If the
- * request was a POST, we may have stored a `struct Buffer *` in the
- * @a con_cls that might still need to be cleaned up.  Call the
- * respective function to free the memory.
- *
- * @param cls client-defined closure
- * @param connection connection handle
- * @param con_cls value as set by the last call to
- *        the #MHD_AccessHandlerCallback
- * @param toe reason for request termination
- * @see #MHD_OPTION_NOTIFY_COMPLETED
- * @ingroup request
- */
-static void
-handle_mhd_completion_callback (void *cls,
-                                struct MHD_Connection *connection,
-                                void **con_cls,
-                                enum MHD_RequestTerminationCode toe)
-{
-  /*  struct TALER_FAKEBANK_Handle *h = cls; */
-  struct ConnectionContext *cc = *con_cls;
-  (void) cls;
-  (void) connection;
-  (void) toe;
-  if (NULL == cc)
-    return;
-  cc->ctx_cleaner (cc->ctx);
-  GNUNET_free (cc);
-}
-
-
-/**
- * Handle incoming HTTP request for /admin/add/incoming.
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account account into which to deposit the funds (credit)
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct ConnectionContext *`)
- * @return MHD result code
- */
-static MHD_RESULT
-handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h,
-                           struct MHD_Connection *connection,
-                           const char *account,
-                           const char *upload_data,
-                           size_t *upload_data_size,
-                           void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  enum GNUNET_JSON_PostResult pr;
-  json_t *json;
-  uint64_t row_id;
-  struct GNUNET_TIME_Timestamp timestamp;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
-    *con_cls = cc;
-  }
-  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
-                                connection,
-                                &cc->ctx,
-                                upload_data,
-                                upload_data_size,
-                                &json);
-  switch (pr)
-  {
-  case GNUNET_JSON_PR_OUT_OF_MEMORY:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_CONTINUE:
-    return MHD_YES;
-  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_JSON_INVALID:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_SUCCESS:
-    break;
-  }
-  {
-    const char *debit_account;
-    struct TALER_Amount amount;
-    struct TALER_ReservePublicKeyP reserve_pub;
-    char *debit;
-    enum GNUNET_GenericReturnValue ret;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                   &reserve_pub),
-      GNUNET_JSON_spec_string ("debit_account",
-                               &debit_account),
-      TALER_JSON_spec_amount ("amount",
-                              h->currency,
-                              &amount),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        (ret = TALER_MHD_parse_json_data (connection,
-                                          json,
-                                          spec)))
-    {
-      GNUNET_break_op (0);
-      json_decref (json);
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-    }
-    if (0 != strcasecmp (amount.currency,
-                         h->currency))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Currency `%s' does not match our configuration\n",
-                  amount.currency);
-      json_decref (json);
-      return TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_CONFLICT,
-        TALER_EC_GENERIC_CURRENCY_MISMATCH,
-        NULL);
-    }
-    debit = TALER_xtalerbank_account_from_payto (debit_account);
-    if (NULL == debit)
-    {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_BAD_REQUEST,
-        TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
-        debit_account);
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Receiving incoming wire transfer: %s->%s, subject: %s, 
amount: %s\n",
-                debit,
-                account,
-                TALER_B2S (&reserve_pub),
-                TALER_amount2s (&amount));
-    ret = make_admin_transfer (h,
-                               debit,
-                               account,
-                               &amount,
-                               &reserve_pub,
-                               &row_id,
-                               &timestamp);
-    GNUNET_free (debit);
-    if (GNUNET_OK != ret)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Reserve public key not unique\n");
-      json_decref (json);
-      return TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_CONFLICT,
-        TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
-        NULL);
-    }
-  }
-  json_decref (json);
-
-  /* Finally build response object */
-  return TALER_MHD_REPLY_JSON_PACK (connection,
-                                    MHD_HTTP_OK,
-                                    GNUNET_JSON_pack_uint64 ("row_id",
-                                                             row_id),
-                                    GNUNET_JSON_pack_timestamp ("timestamp",
-                                                                timestamp));
-}
-
-
-/**
- * Handle incoming HTTP request for /transfer.
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account account making the transfer
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request (a `struct ConnectionContext *`)
- * @return MHD result code
- */
-static MHD_RESULT
-handle_transfer (struct TALER_FAKEBANK_Handle *h,
-                 struct MHD_Connection *connection,
-                 const char *account,
-                 const char *upload_data,
-                 size_t *upload_data_size,
-                 void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  enum GNUNET_JSON_PostResult pr;
-  json_t *json;
-  uint64_t row_id;
-  struct GNUNET_TIME_Timestamp ts;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
-    *con_cls = cc;
-  }
-  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
-                                connection,
-                                &cc->ctx,
-                                upload_data,
-                                upload_data_size,
-                                &json);
-  switch (pr)
-  {
-  case GNUNET_JSON_PR_OUT_OF_MEMORY:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_CONTINUE:
-    return MHD_YES;
-  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_JSON_INVALID:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_SUCCESS:
-    break;
-  }
-  {
-    struct GNUNET_HashCode uuid;
-    struct TALER_WireTransferIdentifierRawP wtid;
-    const char *credit_account;
-    char *credit;
-    const char *base_url;
-    struct TALER_Amount amount;
-    enum GNUNET_GenericReturnValue ret;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("request_uid",
-                                   &uuid),
-      TALER_JSON_spec_amount ("amount",
-                              h->currency,
-                              &amount),
-      GNUNET_JSON_spec_string ("exchange_base_url",
-                               &base_url),
-      GNUNET_JSON_spec_fixed_auto ("wtid",
-                                   &wtid),
-      GNUNET_JSON_spec_string ("credit_account",
-                               &credit_account),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        (ret = TALER_MHD_parse_json_data (connection,
-                                          json,
-                                          spec)))
-    {
-      GNUNET_break_op (0);
-      json_decref (json);
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-    }
-    {
-      enum GNUNET_GenericReturnValue ret;
-
-      credit = TALER_xtalerbank_account_from_payto (credit_account);
-      if (NULL == credit)
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (
-          connection,
-          MHD_HTTP_BAD_REQUEST,
-          TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
-          credit_account);
-      }
-      ret = make_transfer (h,
-                           account,
-                           credit,
-                           &amount,
-                           &wtid,
-                           base_url,
-                           &uuid,
-                           &row_id,
-                           &ts);
-      if (GNUNET_OK != ret)
-      {
-        MHD_RESULT res;
-        char *uids;
-
-        GNUNET_break (0);
-        uids = GNUNET_STRINGS_data_to_string_alloc (&uuid,
-                                                    sizeof (uuid));
-        json_decref (json);
-        res = TALER_MHD_reply_with_error (connection,
-                                          MHD_HTTP_CONFLICT,
-                                          
TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED,
-                                          uids);
-        GNUNET_free (uids);
-        return res;
-      }
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Receiving incoming wire transfer: %s->%s, subject: %s, 
amount: %s, from %s\n",
-                  account,
-                  credit,
-                  TALER_B2S (&wtid),
-                  TALER_amount2s (&amount),
-                  base_url);
-      GNUNET_free (credit);
-    }
-  }
-  json_decref (json);
-
-  /* Finally build response object */
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_uint64 ("row_id",
-                             row_id),
-    GNUNET_JSON_pack_timestamp ("timestamp",
-                                ts));
-}
-
-
-/**
- * Handle incoming HTTP request for / (home page).
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @return MHD result code
- */
-static MHD_RESULT
-handle_home_page (struct TALER_FAKEBANK_Handle *h,
-                  struct MHD_Connection *connection)
-{
-  MHD_RESULT ret;
-  struct MHD_Response *resp;
-#define HELLOMSG "Hello, Fakebank!"
-
-  (void) h;
-  resp = MHD_create_response_from_buffer (
-    strlen (HELLOMSG),
-    HELLOMSG,
-    MHD_RESPMEM_MUST_COPY);
-  ret = MHD_queue_response (connection,
-                            MHD_HTTP_OK,
-                            resp);
-  MHD_destroy_response (resp);
-  return ret;
-}
-
-
-/**
- * Parse URL history arguments, of _both_ APIs:
- * /history/incoming and /history/outgoing.
- *
- * @param h bank handle to work on
- * @param connection MHD connection.
- * @param[out] ha will contain the parsed values.
- * @return #GNUNET_OK only if the parsing succeeds,
- *         #GNUNET_SYSERR if it failed,
- *         #GNUNET_NO if it failed and an error was returned
- */
-static enum GNUNET_GenericReturnValue
-parse_history_common_args (const struct TALER_FAKEBANK_Handle *h,
-                           struct MHD_Connection *connection,
-                           struct HistoryArgs *ha)
-{
-  const char *start;
-  const char *delta;
-  const char *long_poll_ms;
-  unsigned long long lp_timeout;
-  unsigned long long sval;
-  long long d;
-  char dummy;
-
-  start = MHD_lookup_connection_value (connection,
-                                       MHD_GET_ARGUMENT_KIND,
-                                       "start");
-  ha->have_start = (NULL != start);
-  delta = MHD_lookup_connection_value (connection,
-                                       MHD_GET_ARGUMENT_KIND,
-                                       "delta");
-  long_poll_ms = MHD_lookup_connection_value (connection,
-                                              MHD_GET_ARGUMENT_KIND,
-                                              "long_poll_ms");
-  lp_timeout = 0;
-  if ( (NULL == delta) ||
-       (1 != sscanf (delta,
-                     "%lld%c",
-                     &d,
-                     &dummy)) )
-  {
-    /* Fail if one of the above failed.  */
-    /* Invalid request, given that this is fakebank we impolitely
-     * just kill the connection instead of returning a nice error.
-     */
-    GNUNET_break_op (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                        "delta"))
-           ? GNUNET_NO
-           : GNUNET_SYSERR;
-  }
-  if ( (NULL != long_poll_ms) &&
-       (1 != sscanf (long_poll_ms,
-                     "%llu%c",
-                     &lp_timeout,
-                     &dummy)) )
-  {
-    /* Fail if one of the above failed.  */
-    /* Invalid request, given that this is fakebank we impolitely
-     * just kill the connection instead of returning a nice error.
-     */
-    GNUNET_break_op (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                        "long_poll_ms"))
-           ? GNUNET_NO
-           : GNUNET_SYSERR;
-  }
-  if ( (NULL != start) &&
-       (1 != sscanf (start,
-                     "%llu%c",
-                     &sval,
-                     &dummy)) )
-  {
-    /* Fail if one of the above failed.  */
-    /* Invalid request, given that this is fakebank we impolitely
-     * just kill the connection instead of returning a nice error.
-     */
-    GNUNET_break_op (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                        "start"))
-           ? GNUNET_NO
-           : GNUNET_SYSERR;
-  }
-  if (NULL == start)
-    ha->start_idx = (d > 0) ? 0 : h->serial_counter;
-  else
-    ha->start_idx = (uint64_t) sval;
-  ha->delta = (int64_t) d;
-  if (0 == ha->delta)
-  {
-    GNUNET_break_op (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                        "delta"))
-           ? GNUNET_NO
-           : GNUNET_SYSERR;
-  }
-  ha->lp_timeout
-    = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
-                                     lp_timeout);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Request for %lld records from %llu\n",
-              (long long) ha->delta,
-              (unsigned long long) ha->start_idx);
-  return GNUNET_OK;
-}
-
-
-/**
- * Task run when a long poller is about to time out.
- * Only used in single-threaded mode.
- *
- * @param cls a `struct TALER_FAKEBANK_Handle *`
- */
-static void
-lp_timeout (void *cls)
-{
-  struct TALER_FAKEBANK_Handle *h = cls;
-  struct LongPoller *lp;
-
-  h->lp_task = NULL;
-  while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap)))
-  {
-    if (GNUNET_TIME_absolute_is_future (lp->timeout))
-      break;
-    GNUNET_assert (lp ==
-                   GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Timeout reached for long poller %p\n",
-                lp->conn);
-    lp_trigger (lp,
-                h);
-  }
-  if (NULL == lp)
-    return;
-  h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout,
-                                        &lp_timeout,
-                                        h);
-}
-
-
-/**
- * Reschedule the timeout task of @a h for time @a t.
- *
- * @param h fakebank handle
- * @param t when will the next connection timeout expire
- */
-static void
-reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h,
-                       struct GNUNET_TIME_Absolute t)
-{
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Scheduling timeout task for %s\n",
-              GNUNET_STRINGS_absolute_time_to_string (t));
-#ifdef __linux__
-  if (-1 != h->lp_event)
-#else
-  if (-1 != h->lp_event_in && -1 != h->lp_event_out)
-#endif
-  {
-    uint64_t num = 1;
-
-    GNUNET_break (sizeof (num) ==
-#ifdef __linux__
-                  write (h->lp_event,
-#else
-                  write (h->lp_event_in,
-#endif
-                         &num,
-                         sizeof (num)));
-  }
-  else
-  {
-    if (NULL != h->lp_task)
-      GNUNET_SCHEDULER_cancel (h->lp_task);
-    h->lp_task = GNUNET_SCHEDULER_add_at (t,
-                                          &lp_timeout,
-                                          h);
-  }
-}
-
-
-/**
- * Start long-polling for @a connection and @a acc
- * for transfers in @a dir. Must be called with the
- * "big lock" held.
- *
- * @param[in,out] h fakebank handle
- * @param[in,out] connection to suspend
- * @param[in,out] acc account affected
- * @param lp_timeout how long to suspend
- * @param dir direction of transfers to watch for
- * @param wo withdraw operation to watch, only
- *        if @a dir is #LP_WITHDRAW
- */
-static void
-start_lp (struct TALER_FAKEBANK_Handle *h,
-          struct MHD_Connection *connection,
-          struct Account *acc,
-          struct GNUNET_TIME_Relative lp_timeout,
-          enum LongPollType dir,
-          const struct WithdrawalOperation *wo)
-{
-  struct LongPoller *lp;
-  bool toc;
-
-  lp = GNUNET_new (struct LongPoller);
-  lp->account = acc;
-  lp->wo = wo;
-  lp->conn = connection;
-  lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout);
-  lp->type = dir;
-  lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap,
-                                         lp,
-                                         lp->timeout.abs_value_us);
-  toc = (lp ==
-         GNUNET_CONTAINER_heap_peek (h->lp_heap));
-  GNUNET_CONTAINER_DLL_insert (acc->lp_head,
-                               acc->lp_tail,
-                               lp);
-  MHD_suspend_connection (connection);
-  if (toc)
-    reschedule_lp_timeout (h,
-                           lp->timeout);
-
-}
-
-
-/**
- * Handle incoming HTTP request for /history/outgoing
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account which account the request is about
- * @param con_cls closure for request
- */
-static MHD_RESULT
-handle_debit_history (struct TALER_FAKEBANK_Handle *h,
-                      struct MHD_Connection *connection,
-                      const char *account,
-                      void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  struct HistoryContext *hc;
-  struct Transaction *pos;
-  enum GNUNET_GenericReturnValue ret;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &history_cleanup;
-    *con_cls = cc;
-    hc = GNUNET_new (struct HistoryContext);
-    cc->ctx = hc;
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Handling /history/outgoing connection %p\n",
-                connection);
-    if (GNUNET_OK !=
-        (ret = parse_history_common_args (h,
-                                          connection,
-                                          &hc->ha)))
-    {
-      GNUNET_break_op (0);
-      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-    hc->acc = lookup_account (h,
-                              account,
-                              NULL);
-    if (NULL == hc->acc)
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                         account);
-    }
-    GNUNET_asprintf (&hc->payto_uri,
-                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
-                     account,
-                     hc->acc->receiver_name);
-    /* New invariant: */
-    GNUNET_assert (0 == strcmp (hc->payto_uri,
-                                hc->acc->payto_uri));
-    hc->history = json_array ();
-    if (NULL == hc->history)
-    {
-      GNUNET_break (0);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_NO;
-    }
-    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
-  }
-  else
-  {
-    hc = cc->ctx;
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-  }
-
-  if (! hc->ha.have_start)
-  {
-    pos = (0 > hc->ha.delta)
-      ? hc->acc->out_tail
-      : hc->acc->out_head;
-  }
-  else
-  {
-    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
-    bool overflow;
-    uint64_t dir;
-    bool skip = true;
-
-    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
-    overflow = (t->row_id != hc->ha.start_idx);
-    /* If account does not match, linear scan for
-       first matching account. */
-    while ( (! overflow) &&
-            (NULL != t) &&
-            (t->debit_account != hc->acc) )
-    {
-      skip = false;
-      t = h->transactions[(t->row_id + dir) % h->ram_limit];
-      if ( (NULL != t) &&
-           (t->row_id == hc->ha.start_idx) )
-        overflow = true; /* full circle, give up! */
-    }
-    if ( (NULL == t) ||
-         overflow)
-    {
-      /* FIXME: these conditions are unclear to me. */
-      if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) &&
-           (0 < hc->ha.delta))
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        if (overflow)
-        {
-          return TALER_MHD_reply_with_ec (
-            connection,
-            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
-            NULL);
-        }
-        goto finish;
-      }
-      if (h->in_shutdown)
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        goto finish;
-      }
-      start_lp (h,
-                connection,
-                hc->acc,
-                GNUNET_TIME_absolute_get_remaining (hc->timeout),
-                LP_DEBIT,
-                NULL);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_YES;
-    }
-    if (t->debit_account != hc->acc)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Invalid start specified, transaction %llu not with account 
%s!\n",
-                  (unsigned long long) hc->ha.start_idx,
-                  account);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_NO;
-    }
-    if (skip)
-    {
-      /* range is exclusive, skip the matching entry */
-      if (0 > hc->ha.delta)
-        pos = t->prev_out;
-      else
-        pos = t->next_out;
-    }
-    else
-    {
-      pos = t;
-    }
-  }
-  if (NULL != pos)
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Returning %lld debit transactions starting (inclusive) from 
%llu\n",
-                (long long) hc->ha.delta,
-                (unsigned long long) pos->row_id);
-  while ( (0 != hc->ha.delta) &&
-          (NULL != pos) )
-  {
-    json_t *trans;
-    char *credit_payto;
-
-    if (T_DEBIT != pos->type)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Unexpected CREDIT transaction #%llu for account `%s'\n",
-                  (unsigned long long) pos->row_id,
-                  account);
-      if (0 > hc->ha.delta)
-        pos = pos->prev_in;
-      if (0 < hc->ha.delta)
-        pos = pos->next_in;
-      continue;
-    }
-    GNUNET_asprintf (&credit_payto,
-                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
-                     pos->credit_account->account_name,
-                     pos->credit_account->receiver_name);
-
-    trans = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_uint64 ("row_id",
-                               pos->row_id),
-      GNUNET_JSON_pack_timestamp ("date",
-                                  pos->date),
-      TALER_JSON_pack_amount ("amount",
-                              &pos->amount),
-      GNUNET_JSON_pack_string ("credit_account",
-                               credit_payto),
-      GNUNET_JSON_pack_string ("exchange_base_url",
-                               pos->subject.debit.exchange_base_url),
-      GNUNET_JSON_pack_data_auto ("wtid",
-                                  &pos->subject.debit.wtid));
-    GNUNET_assert (NULL != trans);
-    GNUNET_free (credit_payto);
-    GNUNET_assert (0 ==
-                   json_array_append_new (hc->history,
-                                          trans));
-    if (hc->ha.delta > 0)
-      hc->ha.delta--;
-    else
-      hc->ha.delta++;
-    if (0 > hc->ha.delta)
-      pos = pos->prev_out;
-    if (0 < hc->ha.delta)
-      pos = pos->next_out;
-  }
-  if ( (0 == json_array_size (hc->history)) &&
-       (! h->in_shutdown) &&
-       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
-       (0 < hc->ha.delta))
-  {
-    start_lp (h,
-              connection,
-              hc->acc,
-              GNUNET_TIME_absolute_get_remaining (hc->timeout),
-              LP_DEBIT,
-              NULL);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return MHD_YES;
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-finish:
-  if (0 == json_array_size (hc->history))
-  {
-    GNUNET_break (h->in_shutdown ||
-                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
-    return TALER_MHD_reply_static (connection,
-                                   MHD_HTTP_NO_CONTENT,
-                                   NULL,
-                                   NULL,
-                                   0);
-  }
-  {
-    json_t *h = hc->history;
-
-    hc->history = NULL;
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string (
-        "debit_account",
-        hc->payto_uri),
-      GNUNET_JSON_pack_array_steal (
-        "outgoing_transactions",
-        h));
-  }
-}
-
-
-/**
- * Handle incoming HTTP request for /history/incoming
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account which account the request is about
- * @param con_cls closure for request (NULL or &special_ptr)
- * @return MHD result code
- */
-static MHD_RESULT
-handle_credit_history (struct TALER_FAKEBANK_Handle *h,
-                       struct MHD_Connection *connection,
-                       const char *account,
-                       void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  struct HistoryContext *hc;
-  const struct Transaction *pos;
-  enum GNUNET_GenericReturnValue ret;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &history_cleanup;
-    *con_cls = cc;
-    hc = GNUNET_new (struct HistoryContext);
-    cc->ctx = hc;
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Handling /history/incoming connection %p\n",
-                connection);
-    if (GNUNET_OK !=
-        (ret = parse_history_common_args (h,
-                                          connection,
-                                          &hc->ha)))
-    {
-      GNUNET_break_op (0);
-      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-    hc->acc = lookup_account (h,
-                              account,
-                              NULL);
-    if (NULL == hc->acc)
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                         account);
-    }
-    /* FIXME: was simply: acc->payto_uri -- same!? */
-    GNUNET_asprintf (&hc->payto_uri,
-                     "payto://x-taler-bank/%s/%s?receiver-name=%s",
-                     h->hostname,
-                     account,
-                     hc->acc->receiver_name);
-    GNUNET_assert (0 == strcmp (hc->payto_uri,
-                                hc->acc->payto_uri));
-    hc->history = json_array ();
-    if (NULL == hc->history)
-    {
-      GNUNET_break (0);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_NO;
-    }
-    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
-  }
-  else
-  {
-    hc = cc->ctx;
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-  }
-
-  if (! hc->ha.have_start)
-  {
-    pos = (0 > hc->ha.delta)
-          ? hc->acc->in_tail
-          : hc->acc->in_head;
-  }
-  else
-  {
-    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
-    bool overflow;
-    uint64_t dir;
-    bool skip = true;
-
-    overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
-    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
-    /* If account does not match, linear scan for
-       first matching account. */
-    while ( (! overflow) &&
-            (NULL != t) &&
-            (t->credit_account != hc->acc) )
-    {
-      skip = false;
-      t = h->transactions[(t->row_id + dir) % h->ram_limit];
-      if ( (NULL != t) &&
-           (t->row_id == hc->ha.start_idx) )
-        overflow = true; /* full circle, give up! */
-    }
-    if ( (NULL == t) ||
-         overflow)
-    {
-      /* FIXME: these conditions are unclear to me. */
-      if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
-          (0 < hc->ha.delta))
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        if (overflow)
-          return TALER_MHD_reply_with_ec (
-            connection,
-            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
-            NULL);
-        goto finish;
-      }
-      if (h->in_shutdown)
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        goto finish;
-      }
-      start_lp (h,
-                connection,
-                hc->acc,
-                GNUNET_TIME_absolute_get_remaining (hc->timeout),
-                LP_CREDIT,
-                NULL);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_YES;
-    }
-    if (skip)
-    {
-      /* range from application is exclusive, skip the
-  matching entry */
-      if (0 > hc->ha.delta)
-        pos = t->prev_in;
-      else
-        pos = t->next_in;
-    }
-    else
-    {
-      pos = t;
-    }
-  }
-  if (NULL != pos)
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Returning %lld credit transactions starting (inclusive) from 
%llu\n",
-                (long long) hc->ha.delta,
-                (unsigned long long) pos->row_id);
-  while ( (0 != hc->ha.delta) &&
-          (NULL != pos) )
-  {
-    json_t *trans;
-
-    if (T_CREDIT != pos->type)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Unexpected DEBIT transaction #%llu for account `%s'\n",
-                  (unsigned long long) pos->row_id,
-                  account);
-      if (0 > hc->ha.delta)
-        pos = pos->prev_in;
-      if (0 < hc->ha.delta)
-        pos = pos->next_in;
-      continue;
-    }
-    trans = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_uint64 ("row_id",
-                               pos->row_id),
-      GNUNET_JSON_pack_timestamp ("date",
-                                  pos->date),
-      TALER_JSON_pack_amount ("amount",
-                              &pos->amount),
-      GNUNET_JSON_pack_string ("debit_account",
-                               pos->debit_account->payto_uri),
-      GNUNET_JSON_pack_data_auto ("reserve_pub",
-                                  &pos->subject.credit.reserve_pub));
-    GNUNET_assert (NULL != trans);
-    GNUNET_assert (0 ==
-                   json_array_append_new (hc->history,
-                                          trans));
-    if (hc->ha.delta > 0)
-      hc->ha.delta--;
-    else
-      hc->ha.delta++;
-    if (0 > hc->ha.delta)
-      pos = pos->prev_in;
-    if (0 < hc->ha.delta)
-      pos = pos->next_in;
-  }
-  if ( (0 == json_array_size (hc->history)) &&
-       (! h->in_shutdown) &&
-       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
-       (0 < hc->ha.delta))
-  {
-    start_lp (h,
-              connection,
-              hc->acc,
-              GNUNET_TIME_absolute_get_remaining (hc->timeout),
-              LP_CREDIT,
-              NULL);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return MHD_YES;
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-finish:
-  if (0 == json_array_size (hc->history))
-  {
-    GNUNET_break (h->in_shutdown ||
-                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
-    return TALER_MHD_reply_static (connection,
-                                   MHD_HTTP_NO_CONTENT,
-                                   NULL,
-                                   NULL,
-                                   0);
-  }
-  {
-    json_t *h = hc->history;
-
-    hc->history = NULL;
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string (
-        "credit_account",
-        hc->payto_uri),
-      GNUNET_JSON_pack_array_steal (
-        "incoming_transactions",
-        h));
-  }
-}
-
-
-/**
- * Handle incoming HTTP request.
- *
- * @param h our handle
- * @param connection the connection
- * @param url the requested url
- * @param method the method (POST, GET, ...)
- * @param account which account should process the request
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure
- * @return MHD result code
- */
-static MHD_RESULT
-serve (struct TALER_FAKEBANK_Handle *h,
-       struct MHD_Connection *connection,
-       const char *account,
-       const char *url,
-       const char *method,
-       const char *upload_data,
-       size_t *upload_data_size,
-       void **con_cls)
-{
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Fakebank, serving URL `%s' for account `%s'\n",
-              url,
-              account);
-  if (0 == strcasecmp (method,
-                       MHD_HTTP_METHOD_GET))
-  {
-    if ( (0 == strcmp (url,
-                       "/history/incoming")) &&
-         (NULL != account) )
-      return handle_credit_history (h,
-                                    connection,
-                                    account,
-                                    con_cls);
-    if ( (0 == strcmp (url,
-                       "/history/outgoing")) &&
-         (NULL != account) )
-      return handle_debit_history (h,
-                                   connection,
-                                   account,
-                                   con_cls);
-    if (0 == strcmp (url,
-                     "/"))
-      return handle_home_page (h,
-                               connection);
-  }
-  else if (0 == strcasecmp (method,
-                            MHD_HTTP_METHOD_POST))
-  {
-    if ( (0 == strcmp (url,
-                       "/admin/add-incoming")) &&
-         (NULL != account) )
-      return handle_admin_add_incoming (h,
-                                        connection,
-                                        account,
-                                        upload_data,
-                                        upload_data_size,
-                                        con_cls);
-    if ( (0 == strcmp (url,
-                       "/transfer")) &&
-         (NULL != account) )
-      return handle_transfer (h,
-                              connection,
-                              account,
-                              upload_data,
-                              upload_data_size,
-                              con_cls);
-  }
-  /* Unexpected URL path, just close the connection. */
-  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
-                   method,
-                   url);
-  GNUNET_break_op (0);
-  return TALER_MHD_reply_with_error (
-    connection,
-    MHD_HTTP_NOT_FOUND,
-    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-    url);
-}
-
-
-/**
- * Handle GET /withdrawal-operation/{wopid} request.
- *
- * @param h the handle
- * @param connection the connection
- * @param wopid the withdrawal operation identifier
- * @param lp how long is the long-polling timeout
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-get_withdrawal_operation (struct TALER_FAKEBANK_Handle *h,
-                          struct MHD_Connection *connection,
-                          const char *wopid,
-                          struct GNUNET_TIME_Relative lp,
-                          void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  struct WithdrawContext *wc;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &withdraw_cleanup;
-    *con_cls = cc;
-    wc = GNUNET_new (struct WithdrawContext);
-    cc->ctx = wc;
-    wc->wo = lookup_withdrawal_operation (h,
-                                          wopid);
-    if (NULL == wc->wo)
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                         wopid);
-    }
-    wc->timeout = GNUNET_TIME_relative_to_absolute (lp);
-  }
-  else
-  {
-    wc = cc->ctx;
-  }
-  if (GNUNET_TIME_absolute_is_past (wc->timeout) ||
-      h->in_shutdown ||
-      wc->wo->confirmation_done ||
-      wc->wo->aborted)
-  {
-    json_t *wt;
-
-    wt = json_array ();
-    GNUNET_assert (NULL != wt);
-    GNUNET_assert (0 ==
-                   json_array_append_new (wt,
-                                          json_string ("x-taler-bank")));
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_bool ("aborted",
-                             wc->wo->aborted),
-      GNUNET_JSON_pack_bool ("selection_done",
-                             wc->wo->selection_done),
-      GNUNET_JSON_pack_bool ("transfer_done",
-                             wc->wo->confirmation_done),
-      GNUNET_JSON_pack_allow_null (
-        GNUNET_JSON_pack_string ("suggested_exchange",
-                                 h->exchange_url)),
-      TALER_JSON_pack_amount ("amount",
-                              &wc->wo->amount),
-      GNUNET_JSON_pack_array_steal ("wire_types",
-                                    wt));
-  }
-
-  start_lp (h,
-            connection,
-            wc->wo->debit_account,
-            GNUNET_TIME_absolute_get_remaining (wc->timeout),
-            LP_WITHDRAW,
-            wc->wo);
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return MHD_YES;
-}
-
-
-/**
- * Handle POST /withdrawal-operation/ request.
- *
- * @param h our handle
- * @param connection the connection
- * @param wopid the withdrawal operation identifier
- * @param reserve_pub public key of the reserve
- * @param exchange_payto_uri payto://-URI of the exchange
- * @return MHD result code
- */
-static MHD_RESULT
-do_post_withdrawal (struct TALER_FAKEBANK_Handle *h,
-                    struct MHD_Connection *connection,
-                    const char *wopid,
-                    const struct TALER_ReservePublicKeyP *reserve_pub,
-                    const char *exchange_payto_uri)
-{
-  struct WithdrawalOperation *wo;
-  char *credit_name;
-  struct Account *credit_account;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  wo = lookup_withdrawal_operation (h,
-                                    wopid);
-  if (NULL == wo)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       wopid);
-  }
-  if ( (wo->selection_done) &&
-       (0 != GNUNET_memcmp (&wo->reserve_pub,
-                            reserve_pub)) )
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       
TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
-                                       "reserve public key changed");
-  }
-  {
-    /* check if reserve_pub is already in use */
-    const struct GNUNET_PeerIdentity *pid;
-
-    pid = (const struct GNUNET_PeerIdentity *) &wo->reserve_pub;
-    if (GNUNET_CONTAINER_multipeermap_contains (h->rpubs,
-                                                pid))
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_CONFLICT,
-                                         
TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
-                                         NULL);
-    }
-  }
-  credit_name = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
-  if (NULL == credit_name)
-  {
-    GNUNET_break_op (0);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
-                                       NULL);
-  }
-  credit_account = lookup_account (h,
-                                   credit_name,
-                                   NULL);
-  if (NULL == credit_account)
-  {
-    MHD_RESULT res;
-
-    GNUNET_break_op (0);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    res = TALER_MHD_reply_with_error (connection,
-                                      MHD_HTTP_NOT_FOUND,
-                                      TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                      credit_name);
-    GNUNET_free (credit_name);
-    return res;
-  }
-  GNUNET_free (credit_name);
-  if ( (NULL != wo->exchange_account) &&
-       (credit_account != wo->exchange_account) )
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       
TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
-                                       "exchange account changed");
-  }
-  wo->exchange_account = credit_account;
-  wo->reserve_pub = *reserve_pub;
-  wo->selection_done = true;
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_bool ("transfer_done",
-                           wo->confirmation_done));
-}
-
-
-/**
- * Handle POST /withdrawal-operation/ request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param wopid the withdrawal operation identifier
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-post_withdrawal_operation (struct TALER_FAKEBANK_Handle *h,
-                           struct MHD_Connection *connection,
-                           const char *wopid,
-                           const void *upload_data,
-                           size_t *upload_data_size,
-                           void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  enum GNUNET_JSON_PostResult pr;
-  json_t *json;
-  MHD_RESULT res;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
-    *con_cls = cc;
-  }
-  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
-                                connection,
-                                &cc->ctx,
-                                upload_data,
-                                upload_data_size,
-                                &json);
-  switch (pr)
-  {
-  case GNUNET_JSON_PR_OUT_OF_MEMORY:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_CONTINUE:
-    return MHD_YES;
-  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_JSON_INVALID:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_SUCCESS:
-    break;
-  }
-
-  {
-    struct TALER_ReservePublicKeyP reserve_pub;
-    const char *exchange_payto_url;
-    enum GNUNET_GenericReturnValue ret;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                   &reserve_pub),
-      GNUNET_JSON_spec_string ("selected_exchange",
-                               &exchange_payto_url),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        (ret = TALER_MHD_parse_json_data (connection,
-                                          json,
-                                          spec)))
-    {
-      GNUNET_break_op (0);
-      json_decref (json);
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-    }
-    res = do_post_withdrawal (h,
-                              connection,
-                              wopid,
-                              &reserve_pub,
-                              exchange_payto_url);
-  }
-  json_decref (json);
-  return res;
-}
-
-
-/**
- * Handle incoming HTTP request to the bank integration API.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param url the requested url
- * @param method the method (POST, GET, ...)
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-handle_bank_integration (struct TALER_FAKEBANK_Handle *h,
-                         struct MHD_Connection *connection,
-                         const char *url,
-                         const char *method,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         void **con_cls)
-{
-  if (0 == strcasecmp (method,
-                       MHD_HTTP_METHOD_HEAD))
-    method = MHD_HTTP_METHOD_GET;
-  if ( (0 == strcmp (url,
-                     "/config")) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_GET)) )
-  {
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string ("version",
-                               "0:0:0"),
-      GNUNET_JSON_pack_string ("currency",
-                               h->currency),
-      GNUNET_JSON_pack_string ("name",
-                               "taler-bank-integration"));
-  }
-  if ( (0 == strncmp (url,
-                      "/withdrawal-operation/",
-                      strlen ("/withdrawal-operation/"))) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_GET)) )
-  {
-    const char *wopid = &url[strlen ("/withdrawal-operation/")];
-    const char *lp_s
-      = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "long_poll_ms");
-    struct GNUNET_TIME_Relative lp = GNUNET_TIME_UNIT_ZERO;
-
-    if (NULL != lp_s)
-    {
-      unsigned long long d;
-      char dummy;
-
-      if (1 != sscanf (lp_s,
-                       "%llu%c",
-                       &d,
-                       &dummy))
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                           "long_poll_ms");
-      }
-      lp = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
-                                          d);
-    }
-    return get_withdrawal_operation (h,
-                                     connection,
-                                     wopid,
-                                     lp,
-                                     con_cls);
-
-  }
-  if ( (0 == strncmp (url,
-                      "/withdrawal-operation/",
-                      strlen ("/withdrawal-operation/"))) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_POST)) )
-  {
-    const char *wopid = &url[strlen ("/withdrawal-operation/")];
-    return post_withdrawal_operation (h,
-                                      connection,
-                                      wopid,
-                                      upload_data,
-                                      upload_data_size,
-                                      con_cls);
-  }
-
-  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
-                   method,
-                   url);
-  GNUNET_break_op (0);
-  return TALER_MHD_reply_with_error (
-    connection,
-    MHD_HTTP_NOT_FOUND,
-    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-    url);
-}
-
-
-/**
- * Handle GET /accounts/${account_name} request
- * to the Taler bank access API.
- *
- * @param h the handle
- * @param connection the connection
- * @param account_name name of the account
- * @return MHD result code
- */
-static MHD_RESULT
-get_account_access (struct TALER_FAKEBANK_Handle *h,
-                    struct MHD_Connection *connection,
-                    const char *account_name)
-{
-  struct Account *acc;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  acc = lookup_account (h,
-                        account_name,
-                        NULL);
-  if (NULL == acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                       account_name);
-  }
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_string ("payto_uri",
-                             acc->payto_uri),
-    GNUNET_JSON_pack_object_steal (
-      "balance",
-      GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_string ("credit_debit_indicator",
-                                 acc->is_negative
-                                 ? "debit"
-                                 : "credit"),
-        TALER_JSON_pack_amount ("amount",
-                                &acc->balance))));
-}
-
-
-/**
- * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request
- * to the Taler bank access API.
- *
- * @param h the handle
- * @param connection the connection
- * @param account_name name of the account
- * @param withdrawal_id withdrawal ID to return status of
- * @return MHD result code
- */
-static MHD_RESULT
-get_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h,
-                                struct MHD_Connection *connection,
-                                const char *account_name,
-                                const char *withdrawal_id)
-{
-  struct WithdrawalOperation *wo;
-  struct Account *acc;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  wo = lookup_withdrawal_operation (h,
-                                    withdrawal_id);
-  if (NULL == wo)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       withdrawal_id);
-  }
-  acc = lookup_account (h,
-                        account_name,
-                        NULL);
-  if (NULL == acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                       account_name);
-  }
-  if (wo->debit_account != acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       account_name);
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_bool ("aborted",
-                           wo->aborted),
-    GNUNET_JSON_pack_bool ("selection_done",
-                           wo->selection_done),
-    GNUNET_JSON_pack_bool ("transfer_done",
-                           wo->confirmation_done),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("selected_exchange_account",
-                               wo->exchange_account->payto_uri)),
-    GNUNET_JSON_pack_allow_null (
-      wo->selection_done
-      ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub",
-                                    &wo->reserve_pub)
-      : GNUNET_JSON_pack_string ("selected_reserve_pub",
-                                 NULL)),
-    TALER_JSON_pack_amount ("amount",
-                            &wo->amount));
-}
-
-
-/**
- * Handle POST /accounts/$account_name/withdrawals request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param account_name name of the account
- * @param amount amont to withdraw
- * @return MHD result code
- */
-static MHD_RESULT
-do_post_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h,
-                                    struct MHD_Connection *connection,
-                                    const char *account_name,
-                                    const struct TALER_Amount *amount)
-{
-  struct Account *acc;
-  struct WithdrawalOperation *wo;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  acc = lookup_account (h,
-                        account_name,
-                        NULL);
-  if (NULL == acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                       account_name);
-  }
-  wo = GNUNET_new (struct WithdrawalOperation);
-  wo->debit_account = acc;
-  wo->amount = *amount;
-  if (NULL == h->wops)
-  {
-    h->wops = GNUNET_CONTAINER_multishortmap_create (32,
-                                                     GNUNET_YES);
-  }
-  while (1)
-  {
-    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
-                                &wo->wopid,
-                                sizeof (wo->wopid));
-    if (GNUNET_OK ==
-        GNUNET_CONTAINER_multishortmap_put (h->wops,
-                                            &wo->wopid,
-                                            wo,
-                                            
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
-      break;
-  }
-  {
-    char *wopids;
-    char *uri;
-    MHD_RESULT res;
-
-    wopids = GNUNET_STRINGS_data_to_string_alloc (&wo->wopid,
-                                                  sizeof (wo->wopid));
-    GNUNET_asprintf (&uri,
-                     "taler+http://withdraw/%s:%u/taler-bank-integration/%s";,
-                     h->hostname,
-                     (unsigned int) h->port,
-                     wopids);
-    GNUNET_free (wopids);
-    res = TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string ("taler_withdraw_uri",
-                               uri),
-      GNUNET_JSON_pack_data_auto ("withdrawal_id",
-                                  &wo->wopid));
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    GNUNET_free (uri);
-    return res;
-  }
-}
-
-
-/**
- * Handle POST /accounts/$account_name/withdrawals request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param account_name name of the account
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-post_account_withdrawals_access (struct TALER_FAKEBANK_Handle *h,
-                                 struct MHD_Connection *connection,
-                                 const char *account_name,
-                                 const void *upload_data,
-                                 size_t *upload_data_size,
-                                 void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  enum GNUNET_JSON_PostResult pr;
-  json_t *json;
-  MHD_RESULT res;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
-    *con_cls = cc;
-  }
-  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
-                                connection,
-                                &cc->ctx,
-                                upload_data,
-                                upload_data_size,
-                                &json);
-  switch (pr)
-  {
-  case GNUNET_JSON_PR_OUT_OF_MEMORY:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_CONTINUE:
-    return MHD_YES;
-  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_JSON_INVALID:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_SUCCESS:
-    break;
-  }
-
-  {
-    struct TALER_Amount amount;
-    enum GNUNET_GenericReturnValue ret;
-    struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount ("amount",
-                              h->currency,
-                              &amount),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        (ret = TALER_MHD_parse_json_data (connection,
-                                          json,
-                                          spec)))
-    {
-      GNUNET_break_op (0);
-      json_decref (json);
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-    }
-    res = do_post_account_withdrawals_access (h,
-                                              connection,
-                                              account_name,
-                                              &amount);
-  }
-  json_decref (json);
-  return res;
-}
-
-
-/**
- * Handle POST /testing/register request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-post_testing_register (struct TALER_FAKEBANK_Handle *h,
-                       struct MHD_Connection *connection,
-                       const void *upload_data,
-                       size_t *upload_data_size,
-                       void **con_cls)
-{
-  struct ConnectionContext *cc = *con_cls;
-  enum GNUNET_JSON_PostResult pr;
-  json_t *json;
-  MHD_RESULT res;
-
-  if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
-    *con_cls = cc;
-  }
-  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
-                                connection,
-                                &cc->ctx,
-                                upload_data,
-                                upload_data_size,
-                                &json);
-  switch (pr)
-  {
-  case GNUNET_JSON_PR_OUT_OF_MEMORY:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_CONTINUE:
-    return MHD_YES;
-  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_JSON_INVALID:
-    GNUNET_break (0);
-    return MHD_NO;
-  case GNUNET_JSON_PR_SUCCESS:
-    break;
-  }
-
-  {
-    const char *username;
-    const char *password;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_string ("username",
-                               &username),
-      GNUNET_JSON_spec_string ("password",
-                               &password),
-      GNUNET_JSON_spec_end ()
-    };
-    enum GNUNET_GenericReturnValue ret;
-    struct Account *acc;
-
-    if (GNUNET_OK !=
-        (ret = TALER_MHD_parse_json_data (connection,
-                                          json,
-                                          spec)))
-    {
-      GNUNET_break_op (0);
-      json_decref (json);
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-    }
-    acc = lookup_account (h,
-                          username,
-                          NULL);
-    if (NULL != acc)
-    {
-      if (0 != strcmp (password,
-                       acc->password))
-      {
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_CONFLICT,
-                                           TALER_EC_BANK_REGISTER_CONFLICT,
-                                           "password");
-      }
-    }
-    else
-    {
-      acc = lookup_account (h,
-                            username,
-                            username);
-      GNUNET_assert (NULL != acc);
-      acc->password = GNUNET_strdup (password);
-      acc->balance = h->signup_bonus; /* magic money creation! */
-    }
-    res = TALER_MHD_reply_static (connection,
-                                  MHD_HTTP_NO_CONTENT,
-                                  NULL,
-                                  NULL,
-                                  0);
-  }
-  json_decref (json);
-  return res;
-}
+#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_tbi.h"
+#include "fakebank_tbr.h"
+#include "fakebank_twg.h"
 
 
 /**
- * Notify long pollers that a @a wo was updated.
- * Must be called with the "big_lock" still held.
+ * Function called whenever MHD is done with a request.  If the
+ * request was a POST, we may have stored a `struct Buffer *` in the
+ * @a con_cls that might still need to be cleaned up.  Call the
+ * respective function to free the memory.
  *
- * @param h fakebank handle
- * @param wo withdraw operation that finished
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
+ * @param connection connection handle
+ * @param con_cls a `struct ConnectionContext *`
+ *        the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
  */
 static void
-notify_withdrawal (struct TALER_FAKEBANK_Handle *h,
-                   const struct WithdrawalOperation *wo)
-{
-  struct Account *debit_acc = wo->debit_account;
-  struct LongPoller *nxt;
-
-  for (struct LongPoller *lp = debit_acc->lp_head;
-       NULL != lp;
-       lp = nxt)
-  {
-    nxt = lp->next;
-    if ( (LP_WITHDRAW == lp->type) &&
-         (wo == lp->wo) )
-    {
-      GNUNET_assert (lp ==
-                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
-      lp_trigger (lp,
-                  h);
-    }
-  }
-}
-
-
-/**
- * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/abort 
request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param account_name name of the debited account
- * @param withdrawal_id the withdrawal operation identifier
- * @return MHD result code
- */
-static MHD_RESULT
-access_withdrawals_abort (struct TALER_FAKEBANK_Handle *h,
-                          struct MHD_Connection *connection,
-                          const char *account_name,
-                          const char *withdrawal_id)
-{
-  struct WithdrawalOperation *wo;
-  struct Account *acc;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  wo = lookup_withdrawal_operation (h,
-                                    withdrawal_id);
-  if (NULL == wo)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       withdrawal_id);
-  }
-  acc = lookup_account (h,
-                        account_name,
-                        NULL);
-  if (NULL == acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                       account_name);
-  }
-  if (wo->debit_account != acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       account_name);
-  }
-  if (wo->confirmation_done)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       TALER_EC_BANK_ABORT_CONFIRM_CONFLICT,
-                                       account_name);
-  }
-  wo->aborted = true;
-  notify_withdrawal (h,
-                     wo);
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return TALER_MHD_reply_json (connection,
-                               json_object (), /* FIXME: #7301 */
-                               MHD_HTTP_OK);
-}
-
-
-/**
- * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm 
request.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param account_name name of the debited account
- * @param withdrawal_id the withdrawal operation identifier
- * @return MHD result code
- */
-static MHD_RESULT
-access_withdrawals_confirm (struct TALER_FAKEBANK_Handle *h,
-                            struct MHD_Connection *connection,
-                            const char *account_name,
-                            const char *withdrawal_id)
-{
-  struct WithdrawalOperation *wo;
-  struct Account *acc;
-
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  wo = lookup_withdrawal_operation (h,
-                                    withdrawal_id);
-  if (NULL == wo)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       withdrawal_id);
-  }
-  acc = lookup_account (h,
-                        account_name,
-                        NULL);
-  if (NULL == acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                       account_name);
-  }
-  if (wo->debit_account != acc)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
-                                       account_name);
-  }
-  if (NULL == wo->exchange_account)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED,
-                                       NULL);
-  }
-  if (wo->aborted)
-  {
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       TALER_EC_BANK_CONFIRM_ABORT_CONFLICT,
-                                       account_name);
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  if (GNUNET_OK !=
-      make_admin_transfer (h,
-                           wo->debit_account->account_name,
-                           wo->exchange_account->account_name,
-                           &wo->amount,
-                           &wo->reserve_pub,
-                           &wo->row_id,
-                           &wo->timestamp))
-  {
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       
TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
-                                       NULL);
-  }
-  /* Re-acquiring the lock and continuing to operate on 'wo'
-     is currently (!) acceptable because we NEVER free 'wo'
-     until shutdown. We may want to revise this if keeping
-     all withdraw operations in RAM becomes an issue... */
-  GNUNET_assert (0 ==
-                 pthread_mutex_lock (&h->big_lock));
-  wo->confirmation_done = true;
-  notify_withdrawal (h,
-                     wo);
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-  return TALER_MHD_reply_json (connection,
-                               json_object (),
-                               MHD_HTTP_OK);
-}
-
-
-/**
- * Handle incoming HTTP request to the Taler bank access API.
- *
- * @param h our fakebank handle
- * @param connection the connection
- * @param url the requested url
- * @param method the method (POST, GET, ...)
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure for request
- * @return MHD result code
- */
-static MHD_RESULT
-handle_bank_access (struct TALER_FAKEBANK_Handle *h,
-                    struct MHD_Connection *connection,
-                    const char *url,
-                    const char *method,
-                    const char *upload_data,
-                    size_t *upload_data_size,
-                    void **con_cls)
-{
-  if (0 == strcasecmp (method,
-                       MHD_HTTP_METHOD_HEAD))
-    method = MHD_HTTP_METHOD_GET;
-  if ( (0 == strcmp (url,
-                     "/config")) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_GET)) )
-  {
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string ("version",
-                               "0:0:0"),
-      GNUNET_JSON_pack_string ("currency",
-                               h->currency),
-      GNUNET_JSON_pack_string ("name",
-                               "taler-bank-access"));
-  }
-  if ( (0 == strcmp (url,
-                     "/public-accounts")) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_GET)) )
-  {
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_array_steal ("public_accounts",
-                                    json_array ()));
-  }
-  if ( (0 == strncmp (url,
-                      "/accounts/",
-                      strlen ("/accounts/"))) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_POST)) )
-  {
-    const char *acc_name = &url[strlen ("/accounts/")];
-    const char *end_acc = strchr (acc_name,
-                                  '/');
-    char *acc;
-    MHD_RESULT ret;
-
-    if ( (NULL == end_acc) ||
-         (0 != strncmp (end_acc,
-                        "/withdrawals",
-                        strlen ("/withdrawals"))) )
-    {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-                                         acc_name);
-    }
-    acc = GNUNET_strndup (acc_name,
-                          end_acc - acc_name);
-    end_acc += strlen ("/withdrawals");
-    if ('/' == *end_acc)
-    {
-      const char *wid = end_acc + 1;
-      const char *opid = strchr (wid,
-                                 '/');
-      char *wi;
-
-      if ( (NULL == opid) ||
-           ( (0 != strcmp (opid,
-                           "/abort")) &&
-             (0 != strcmp (opid,
-                           "/confirm")) ) )
-      {
-        GNUNET_break_op (0);
-        GNUNET_free (acc);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_NOT_FOUND,
-                                           TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-                                           acc_name);
-      }
-      wi = GNUNET_strndup (wid,
-                           opid - wid);
-      if (0 == strcmp (opid,
-                       "/abort"))
-      {
-        ret = access_withdrawals_abort (h,
-                                        connection,
-                                        acc,
-                                        wi);
-        GNUNET_free (wi);
-        GNUNET_free (acc);
-        return ret;
-      }
-      if (0 == strcmp (opid,
-                       "/confirm"))
-      {
-        ret = access_withdrawals_confirm (h,
-                                          connection,
-                                          acc,
-                                          wi);
-        GNUNET_free (wi);
-        GNUNET_free (acc);
-        return ret;
-      }
-      GNUNET_assert (0);
-    }
-    ret = post_account_withdrawals_access (h,
-                                           connection,
-                                           acc,
-                                           upload_data,
-                                           upload_data_size,
-                                           con_cls);
-    GNUNET_free (acc);
-    return ret;
-  }
-
-  if ( (0 == strncmp (url,
-                      "/accounts/",
-                      strlen ("/accounts/"))) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_GET)) )
-  {
-    const char *acc_name = &url[strlen ("/accounts/")];
-    const char *end_acc = strchr (acc_name,
-                                  '/');
-    const char *wid;
-    char *acc;
-    MHD_RESULT ret;
-
-    if (NULL == end_acc)
-    {
-      ret = get_account_access (h,
-                                connection,
-                                acc_name);
-      return ret;
-    }
-    if (0 != strncmp (end_acc,
-                      "/withdrawals/",
-                      strlen ("/withdrawals/")))
-    {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-                                         acc_name);
-    }
-    acc = GNUNET_strndup (acc_name,
-                          end_acc - acc_name);
-    wid = &end_acc[strlen ("/withdrawals/")];
-    ret = get_account_withdrawals_access (h,
-                                          connection,
-                                          acc,
-                                          wid);
-    GNUNET_free (acc);
-    return ret;
-  }
-  /* FIXME: implement transactions API: 1.12.2 */
-
-  /* registration API */
-  if ( (0 == strcmp (url,
-                     "/testing/register")) &&
-       (0 == strcasecmp (method,
-                         MHD_HTTP_METHOD_POST)) )
-  {
-    return post_testing_register (h,
-                                  connection,
-                                  upload_data,
-                                  upload_data_size,
-                                  con_cls);
-  }
-
-  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
-                   method,
-                   url);
-  GNUNET_break_op (0);
-  return TALER_MHD_reply_with_error (
-    connection,
-    MHD_HTTP_NOT_FOUND,
-    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-    url);
-}
-
-
-/**
- * Handle incoming HTTP request for /history/incoming
- * of the Anastasis API.  This one can return transactions
- * created by debits from the exchange!
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @param account which account the request is about
- * @param con_cls closure for request (NULL or &special_ptr)
- * @return MHD result code
- */
-static MHD_RESULT
-handle_anastasis_credit_history (
-  struct TALER_FAKEBANK_Handle *h,
-  struct MHD_Connection *connection,
-  const char *account,
-  void **con_cls)
+handle_mhd_completion_callback (void *cls,
+                                struct MHD_Connection *connection,
+                                void **con_cls,
+                                enum MHD_RequestTerminationCode toe)
 {
+  struct TALER_FAKEBANK_Handle *h = cls;
   struct ConnectionContext *cc = *con_cls;
-  struct HistoryContext *hc;
-  const struct Transaction *pos;
-  enum GNUNET_GenericReturnValue ret;
 
+  (void) h;
+  (void) connection;
+  (void) toe;
   if (NULL == cc)
-  {
-    cc = GNUNET_new (struct ConnectionContext);
-    cc->ctx_cleaner = &history_cleanup;
-    *con_cls = cc;
-    hc = GNUNET_new (struct HistoryContext);
-    cc->ctx = hc;
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Handling /history/incoming connection %p\n",
-                connection);
-    if (GNUNET_OK !=
-        (ret = parse_history_common_args (h,
-                                          connection,
-                                          &hc->ha)))
-    {
-      GNUNET_break_op (0);
-      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
-    }
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-    hc->acc = lookup_account (h,
-                              account,
-                              NULL);
-    if (NULL == hc->acc)
-    {
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
-                                         account);
-    }
-    /* FIXME: was simply: acc->payto_uri -- same!? */
-    GNUNET_asprintf (&hc->payto_uri,
-                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
-                     account,
-                     hc->acc->receiver_name);
-    GNUNET_assert (0 == strcmp (hc->payto_uri,
-                                hc->acc->payto_uri));
-    hc->history = json_array ();
-    if (NULL == hc->history)
-    {
-      GNUNET_break (0);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_NO;
-    }
-    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
-  }
-  else
-  {
-    hc = cc->ctx;
-    GNUNET_assert (0 ==
-                   pthread_mutex_lock (&h->big_lock));
-  }
-
-  if (! hc->ha.have_start)
-  {
-    pos = (0 > hc->ha.delta)
-          ? hc->acc->in_tail
-          : hc->acc->in_head;
-  }
-  else
-  {
-    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
-    bool overflow;
-    uint64_t dir;
-    bool skip = true;
-
-    overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
-    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
-    /* If account does not match, linear scan for
-       first matching account. */
-    while ( (! overflow) &&
-            (NULL != t) &&
-            (t->credit_account != hc->acc) )
-    {
-      skip = false;
-      t = h->transactions[(t->row_id + dir) % h->ram_limit];
-      if ( (NULL != t) &&
-           (t->row_id == hc->ha.start_idx) )
-        overflow = true; /* full circle, give up! */
-    }
-    if ( (NULL == t) ||
-         overflow)
-    {
-      /* FIXME: these conditions are unclear to me. */
-      if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
-          (0 < hc->ha.delta))
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        if (overflow)
-          return TALER_MHD_reply_with_ec (
-            connection,
-            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
-            NULL);
-        goto finish;
-      }
-      if (h->in_shutdown)
-      {
-        GNUNET_assert (0 ==
-                       pthread_mutex_unlock (&h->big_lock));
-        goto finish;
-      }
-      start_lp (h,
-                connection,
-                hc->acc,
-                GNUNET_TIME_absolute_get_remaining (hc->timeout),
-                LP_CREDIT,
-                NULL);
-      GNUNET_assert (0 ==
-                     pthread_mutex_unlock (&h->big_lock));
-      return MHD_YES;
-    }
-    if (skip)
-    {
-      /* range from application is exclusive, skip the
-  matching entry */
-      if (0 > hc->ha.delta)
-        pos = t->prev_in;
-      else
-        pos = t->next_in;
-    }
-    else
-    {
-      pos = t;
-    }
-  }
-  if (NULL != pos)
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Returning %lld credit transactions starting (inclusive) from 
%llu\n",
-                (long long) hc->ha.delta,
-                (unsigned long long) pos->row_id);
-  while ( (0 != hc->ha.delta) &&
-          (NULL != pos) )
-  {
-    json_t *trans;
-    char *subject;
-
-    if (T_DEBIT != pos->type)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Unexpected CREDIT transaction #%llu for account `%s'\n",
-                  (unsigned long long) pos->row_id,
-                  account);
-      if (0 > hc->ha.delta)
-        pos = pos->prev_in;
-      if (0 < hc->ha.delta)
-        pos = pos->next_in;
-      continue;
-    }
-
-    {
-      char *wtids;
-
-      wtids = GNUNET_STRINGS_data_to_string_alloc (
-        &pos->subject.debit.wtid,
-        sizeof (pos->subject.debit.wtid));
-      GNUNET_asprintf (&subject,
-                       "%s %s",
-                       wtids,
-                       pos->subject.debit.exchange_base_url);
-      GNUNET_free (wtids);
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Found transaction over %s with subject %s\n",
-                TALER_amount2s (&pos->amount),
-                subject);
-    trans = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_string ("type",
-                               "RESERVE"),
-      GNUNET_JSON_pack_uint64 ("row_id",
-                               pos->row_id),
-      GNUNET_JSON_pack_timestamp ("date",
-                                  pos->date),
-      TALER_JSON_pack_amount ("amount",
-                              &pos->amount),
-      GNUNET_JSON_pack_string ("debit_account",
-                               pos->debit_account->payto_uri),
-      GNUNET_JSON_pack_string ("subject",
-                               subject));
-    GNUNET_free (subject);
-    GNUNET_assert (NULL != trans);
-    GNUNET_assert (0 ==
-                   json_array_append_new (hc->history,
-                                          trans));
-    if (hc->ha.delta > 0)
-      hc->ha.delta--;
-    else
-      hc->ha.delta++;
-    if (0 > hc->ha.delta)
-      pos = pos->prev_in;
-    if (0 < hc->ha.delta)
-      pos = pos->next_in;
-  }
-  if ( (0 == json_array_size (hc->history)) &&
-       (! h->in_shutdown) &&
-       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
-       (0 < hc->ha.delta))
-  {
-    start_lp (h,
-              connection,
-              hc->acc,
-              GNUNET_TIME_absolute_get_remaining (hc->timeout),
-              LP_CREDIT,
-              NULL);
-    GNUNET_assert (0 ==
-                   pthread_mutex_unlock (&h->big_lock));
-    return MHD_YES;
-  }
-  GNUNET_assert (0 ==
-                 pthread_mutex_unlock (&h->big_lock));
-finish:
-  if (0 == json_array_size (hc->history))
-  {
-    GNUNET_break (h->in_shutdown ||
-                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
-    return TALER_MHD_reply_static (connection,
-                                   MHD_HTTP_NO_CONTENT,
-                                   NULL,
-                                   NULL,
-                                   0);
-  }
-  {
-    json_t *h = hc->history;
-
-    hc->history = NULL;
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string (
-        "credit_account",
-        hc->payto_uri),
-      GNUNET_JSON_pack_array_steal (
-        "incoming_transactions",
-        h));
-  }
-}
-
-
-/**
- * Handle incoming HTTP request to Anastasis API.
- *
- * @param h our handle
- * @param connection the connection
- * @param url the requested url
- * @param method the method (POST, GET, ...)
- * @param account which account should process the request
- * @param upload_data request data
- * @param upload_data_size size of @a upload_data in bytes
- * @param con_cls closure
- * @return MHD result code
- */
-static MHD_RESULT
-handle_anastasis_api (
-  struct TALER_FAKEBANK_Handle *h,
-  struct MHD_Connection *connection,
-  const char *account,
-  const char *url,
-  const char *method,
-  const char *upload_data,
-  size_t *upload_data_size,
-  void **con_cls)
-{
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Fakebank - Anastasis API: serving URL `%s' for account `%s'\n",
-              url,
-              account);
-  if (0 == strcasecmp (method,
-                       MHD_HTTP_METHOD_GET))
-  {
-    if ( (0 == strcmp (url,
-                       "/history/incoming")) &&
-         (NULL != account) )
-      return handle_anastasis_credit_history (h,
-                                              connection,
-                                              account,
-                                              con_cls);
-    if (0 == strcmp (url,
-                     "/"))
-      return handle_home_page (h,
-                               connection);
-  }
-  /* Unexpected URL path, just close the connection. */
-  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
-                   method,
-                   url);
-  GNUNET_break_op (0);
-  return TALER_MHD_reply_with_error (
-    connection,
-    MHD_HTTP_NOT_FOUND,
-    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
-    url);
+    return;
+  cc->ctx_cleaner (cc->ctx);
+  GNUNET_free (cc);
 }
 
 
@@ -4418,26 +106,26 @@ handle_mhd_request (void *cls,
                     strlen ("/taler-bank-integration/")))
   {
     url += strlen ("/taler-bank-integration");
-    return handle_bank_integration (h,
-                                    connection,
-                                    url,
-                                    method,
-                                    upload_data,
-                                    upload_data_size,
-                                    con_cls);
+    return TALER_FAKEBANK_tbi_main_ (h,
+                                     connection,
+                                     url,
+                                     method,
+                                     upload_data,
+                                     upload_data_size,
+                                     con_cls);
   }
   if (0 == strncmp (url,
                     "/taler-bank-access/",
                     strlen ("/taler-bank-access/")))
   {
     url += strlen ("/taler-bank-access");
-    return handle_bank_access (h,
-                               connection,
-                               url,
-                               method,
-                               upload_data,
-                               upload_data_size,
-                               con_cls);
+    return TALER_FAKEBANK_bank_main_ (h,
+                                      connection,
+                                      url,
+                                      method,
+                                      upload_data,
+                                      upload_data_size,
+                                      con_cls);
   }
   /* Next is duplication to be more libeufin-like: */
   if (0 == strncmp (url,
@@ -4445,13 +133,13 @@ handle_mhd_request (void *cls,
                     strlen ("/access-api/")))
   {
     url += strlen ("/access-api");
-    return handle_bank_access (h,
-                               connection,
-                               url,
-                               method,
-                               upload_data,
-                               upload_data_size,
-                               con_cls);
+    return TALER_FAKEBANK_bank_main_ (h,
+                                      connection,
+                                      url,
+                                      method,
+                                      upload_data,
+                                      upload_data_size,
+                                      con_cls);
   }
   if (0 == strncmp (url,
                     "/anastasis-api/",
@@ -4465,14 +153,14 @@ handle_mhd_request (void *cls,
                                 end - url - 1);
       url = end;
     }
-    return handle_anastasis_api (h,
-                                 connection,
-                                 account,
-                                 url,
-                                 method,
-                                 upload_data,
-                                 upload_data_size,
-                                 con_cls);
+    return TALER_FAKEBANK_tbr_main_ (h,
+                                     connection,
+                                     account,
+                                     url,
+                                     method,
+                                     upload_data,
+                                     upload_data_size,
+                                     con_cls);
   }
 
   if (0 == strncmp (url,
@@ -4489,14 +177,14 @@ handle_mhd_request (void *cls,
                               end - url - 1);
     url = end;
   }
-  ret = serve (h,
-               connection,
-               account,
-               url,
-               method,
-               upload_data,
-               upload_data_size,
-               con_cls);
+  ret = TALER_FAKEBANK_twg_main_ (h,
+                                  connection,
+                                  account,
+                                  url,
+                                  method,
+                                  upload_data,
+                                  upload_data_size,
+                                  con_cls);
   GNUNET_free (account);
   return ret;
 }
@@ -4529,7 +217,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
   h->mhd_task =
     GNUNET_SCHEDULER_add_read_net (tv,
                                    h->mhd_rfd,
-                                   &run_mhd,
+                                   &TALER_FAKEBANK_run_mhd_,
                                    h);
 }
 
@@ -4603,7 +291,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
                                  tv,
                                  wrs,
                                  wws,
-                                 &run_mhd,
+                                 &TALER_FAKEBANK_run_mhd_,
                                  h);
   if (NULL != wrs)
     GNUNET_NETWORK_fdset_destroy (wrs);
@@ -4620,8 +308,8 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
  *
  * @param cls the `struct TALER_FAKEBANK_Handle`
  */
-static void
-run_mhd (void *cls)
+void
+TALER_FAKEBANK_run_mhd_ (void *cls)
 {
   struct TALER_FAKEBANK_Handle *h = cls;
 
@@ -4770,22 +458,23 @@ TALER_FAKEBANK_start3 (const char *hostname,
                    (unsigned int) port);
   if (0 == num_threads)
   {
-    h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
+    h->mhd_bank = MHD_start_daemon (
+      MHD_USE_DEBUG
 #if EPOLL_SUPPORT
-                                    | MHD_USE_EPOLL
+      | MHD_USE_EPOLL
 #endif
-                                    | MHD_USE_DUAL_STACK
-                                    | MHD_ALLOW_SUSPEND_RESUME,
-                                    port,
-                                    NULL, NULL,
-                                    &handle_mhd_request, h,
-                                    MHD_OPTION_NOTIFY_COMPLETED,
-                                    &handle_mhd_completion_callback, h,
-                                    MHD_OPTION_LISTEN_BACKLOG_SIZE,
-                                    (unsigned int) 1024,
-                                    MHD_OPTION_CONNECTION_LIMIT,
-                                    (unsigned int) 65536,
-                                    MHD_OPTION_END);
+      | MHD_USE_DUAL_STACK
+      | MHD_ALLOW_SUSPEND_RESUME,
+      port,
+      NULL, NULL,
+      &handle_mhd_request, h,
+      MHD_OPTION_NOTIFY_COMPLETED,
+      &handle_mhd_completion_callback, h,
+      MHD_OPTION_LISTEN_BACKLOG_SIZE,
+      (unsigned int) 1024,
+      MHD_OPTION_CONNECTION_LIMIT,
+      (unsigned int) 65536,
+      MHD_OPTION_END);
     if (NULL == h->mhd_bank)
     {
       TALER_FAKEBANK_stop (h);
@@ -4828,7 +517,7 @@ TALER_FAKEBANK_start3 (const char *hostname,
     if (0 !=
         pthread_create (&h->lp_thread,
                         NULL,
-                        &lp_expiration_thread,
+                        &TALER_FAKEBANK_lp_expiration_thread_,
                         h))
     {
       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
@@ -4845,24 +534,25 @@ TALER_FAKEBANK_start3 (const char *hostname,
       TALER_FAKEBANK_stop (h);
       return NULL;
     }
-    h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
-                                    | MHD_USE_AUTO_INTERNAL_THREAD
-                                    | MHD_ALLOW_SUSPEND_RESUME
-                                    | MHD_USE_TURBO
-                                    | MHD_USE_TCP_FASTOPEN
-                                    | MHD_USE_DUAL_STACK,
-                                    port,
-                                    NULL, NULL,
-                                    &handle_mhd_request, h,
-                                    MHD_OPTION_NOTIFY_COMPLETED,
-                                    &handle_mhd_completion_callback, h,
-                                    MHD_OPTION_LISTEN_BACKLOG_SIZE,
-                                    (unsigned int) 1024,
-                                    MHD_OPTION_CONNECTION_LIMIT,
-                                    (unsigned int) 65536,
-                                    MHD_OPTION_THREAD_POOL_SIZE,
-                                    num_threads,
-                                    MHD_OPTION_END);
+    h->mhd_bank = MHD_start_daemon (
+      MHD_USE_DEBUG
+      | MHD_USE_AUTO_INTERNAL_THREAD
+      | MHD_ALLOW_SUSPEND_RESUME
+      | MHD_USE_TURBO
+      | MHD_USE_TCP_FASTOPEN
+      | MHD_USE_DUAL_STACK,
+      port,
+      NULL, NULL,
+      &handle_mhd_request, h,
+      MHD_OPTION_NOTIFY_COMPLETED,
+      &handle_mhd_completion_callback, h,
+      MHD_OPTION_LISTEN_BACKLOG_SIZE,
+      (unsigned int) 1024,
+      MHD_OPTION_CONNECTION_LIMIT,
+      (unsigned int) 65536,
+      MHD_OPTION_THREAD_POOL_SIZE,
+      num_threads,
+      MHD_OPTION_END);
     if (NULL == h->mhd_bank)
     {
       GNUNET_break (0);
diff --git a/src/bank-lib/fakebank.h b/src/bank-lib/fakebank.h
new file mode 100644
index 00000000..a9285ec1
--- /dev/null
+++ b/src/bank-lib/fakebank.h
@@ -0,0 +1,696 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank.h
+ * @brief general state of the fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_H
+#define FAKEBANK_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * How long are exchange base URLs allowed to be at most?
+ * Set to a relatively low number as this does contribute
+ * significantly to our RAM consumption.
+ */
+#define MAX_URL_LEN 64
+
+
+/**
+ * Maximum POST request size.
+ */
+#define REQUEST_BUFFER_MAX (4 * 1024)
+
+
+/**
+ * Per account information.
+ */
+struct Account;
+
+
+/**
+ * Types of long polling activities.
+ */
+enum LongPollType
+{
+  /**
+   * Transfer TO the exchange.
+   */
+  LP_CREDIT,
+
+  /**
+   * Transfer FROM the exchange.
+   */
+  LP_DEBIT,
+
+  /**
+   * Withdraw operation completion/abort.
+   */
+  LP_WITHDRAW
+
+};
+
+/**
+ * Client waiting for activity on this account.
+ */
+struct LongPoller
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct LongPoller *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct LongPoller *prev;
+
+  /**
+   * Fakebank this long poller belongs with.
+   */
+  struct TALER_FAKEBANK_Handle *h;
+
+  /**
+   * Account this long poller is waiting on.
+   */
+  struct Account *account;
+
+  /**
+   * Withdraw operation we are waiting on,
+   * only if @e type is #LP_WITHDRAW, otherwise NULL.
+   */
+  const struct WithdrawalOperation *wo;
+
+  /**
+   * Entry in the heap for this long poller.
+   */
+  struct GNUNET_CONTAINER_HeapNode *hn;
+
+  /**
+   * Client that is waiting for transactions.
+   */
+  struct MHD_Connection *conn;
+
+  /**
+   * When will this long poller time out?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * What does the @e connection wait for?
+   */
+  enum LongPollType type;
+
+};
+
+
+/**
+ * Details about a transcation we (as the simulated bank) received.
+ */
+struct Transaction;
+
+
+/**
+ * Information we keep per withdraw operation.
+ */
+struct WithdrawalOperation
+{
+  /**
+   * Unique (random) operation ID.
+   */
+  struct GNUNET_ShortHashCode wopid;
+
+  /**
+   * Debited account.
+   */
+  struct Account *debit_account;
+
+  /**
+   * Target exchange account, or NULL if unknown.
+   */
+  const struct Account *exchange_account;
+
+  /**
+   * RowID of the resulting transaction, if any. Otherwise 0.
+   */
+  uint64_t row_id;
+
+  /**
+   * Amount transferred.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Public key of the reserve, wire transfer subject.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * When was the transaction made? 0 if not yet.
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  /**
+   * Was the withdrawal aborted?
+   */
+  bool aborted;
+
+  /**
+   * Did the bank confirm the withdrawal?
+   */
+  bool confirmation_done;
+
+  /**
+   * Is @e reserve_pub initialized?
+   */
+  bool selection_done;
+
+};
+
+
+/**
+ * Per account information.
+ */
+struct Account
+{
+
+  /**
+   * Inbound transactions for this account in a MDLL.
+   */
+  struct Transaction *in_head;
+
+  /**
+   * Inbound transactions for this account in a MDLL.
+   */
+  struct Transaction *in_tail;
+
+  /**
+   * Outbound transactions for this account in a MDLL.
+   */
+  struct Transaction *out_head;
+
+  /**
+   * Outbound transactions for this account in a MDLL.
+   */
+  struct Transaction *out_tail;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct LongPoller *lp_head;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct LongPoller *lp_tail;
+
+  /**
+   * Account name (string, not payto!)
+   */
+  char *account_name;
+
+  /**
+   * Receiver name for payto:// URIs.
+   */
+  char *receiver_name;
+
+  /**
+   * Payto URI for this account.
+   */
+  char *payto_uri;
+
+  /**
+   * Password set for the account (if any).
+   */
+  char *password;
+
+  /**
+   * Current account balance.
+   */
+  struct TALER_Amount balance;
+
+  /**
+   * true if the balance is negative.
+   */
+  bool is_negative;
+
+};
+
+
+/**
+ * Details about a transcation we (as the simulated bank) received.
+ */
+struct Transaction
+{
+  /**
+   * We store inbound transactions in a MDLL.
+   */
+  struct Transaction *next_in;
+
+  /**
+   * We store inbound transactions in a MDLL.
+   */
+  struct Transaction *prev_in;
+
+  /**
+   * We store outbound transactions in a MDLL.
+   */
+  struct Transaction *next_out;
+
+  /**
+   * We store outbound transactions in a MDLL.
+   */
+  struct Transaction *prev_out;
+
+  /**
+   * Amount to be transferred.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Account to debit.
+   */
+  struct Account *debit_account;
+
+  /**
+   * Account to credit.
+   */
+  struct Account *credit_account;
+
+  /**
+   * Random unique identifier for the request.
+   * Used to detect idempotent requests.
+   */
+  struct GNUNET_HashCode request_uid;
+
+  /**
+   * When did the transaction happen?
+   */
+  struct GNUNET_TIME_Timestamp date;
+
+  /**
+   * Number of this transaction.
+   */
+  uint64_t row_id;
+
+  /**
+   * What does the @e subject contain?
+   */
+  enum
+  {
+    /**
+     * Transfer TO the exchange.
+     */
+    T_CREDIT,
+
+    /**
+     * Transfer FROM the exchange.
+     */
+    T_DEBIT,
+
+    /**
+     * Exchange-to-exchange WAD transfer.
+     */
+    T_WAD,
+  } type;
+
+  /**
+   * Wire transfer subject.
+   */
+  union
+  {
+
+    /**
+     * Used if @e type is T_DEBIT.
+     */
+    struct
+    {
+
+      /**
+       * Subject of the transfer.
+       */
+      struct TALER_WireTransferIdentifierRawP wtid;
+
+      /**
+       * Base URL of the exchange.
+       */
+      char exchange_base_url[MAX_URL_LEN];
+
+    } debit;
+
+    /**
+     * Used if @e type is T_CREDIT.
+     */
+    struct
+    {
+
+      /**
+       * Reserve public key of the credit operation.
+       */
+      struct TALER_ReservePublicKeyP reserve_pub;
+
+    } credit;
+
+    /**
+     * Used if @e type is T_WAD.
+     */
+    struct
+    {
+
+      /**
+       * Subject of the transfer.
+       */
+      struct TALER_WadIdentifierP wad;
+
+      /**
+       * Base URL of the originating exchange.
+       */
+      char origin_base_url[MAX_URL_LEN];
+
+    } wad;
+
+  } subject;
+
+  /**
+   * Has this transaction not yet been subjected to
+   * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and
+   * should thus be counted in #TALER_FAKEBANK_check_empty()?
+   */
+  bool unchecked;
+};
+
+
+/**
+ * Function called to clean up context of a connection.
+ *
+ * @param ctx context to clean up
+ */
+typedef void
+(*ConnectionCleaner)(void *ctx);
+
+/**
+ * Universal context we keep per connection.
+ */
+struct ConnectionContext
+{
+  /**
+   * Function we call upon completion to clean up.
+   */
+  ConnectionCleaner ctx_cleaner;
+
+  /**
+   * Request-handler specific context.
+   */
+  void *ctx;
+};
+
+
+/**
+ * This is the "base" structure for both the /history and the
+ * /history-range API calls.
+ */
+struct HistoryArgs
+{
+
+  /**
+   * Bank account number of the requesting client.
+   */
+  uint64_t account_number;
+
+  /**
+   * Index of the starting transaction, exclusive (!).
+   */
+  uint64_t start_idx;
+
+  /**
+   * Requested number of results and order
+   * (positive: ascending, negative: descending)
+   */
+  int64_t delta;
+
+  /**
+   * Timeout for long polling.
+   */
+  struct GNUNET_TIME_Relative lp_timeout;
+
+  /**
+   * true if starting point was given.
+   */
+  bool have_start;
+
+};
+
+
+/**
+ * Context we keep per history request.
+ */
+struct HistoryContext
+{
+  /**
+   * When does this request time out.
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Client arguments for this request.
+   */
+  struct HistoryArgs ha;
+
+  /**
+   * Account the request is about.
+   */
+  struct Account *acc;
+
+  /**
+   * Payto URI of the account.
+   */
+  char *payto_uri;
+
+  /**
+   * JSON object we are building to return.
+   */
+  json_t *history;
+
+};
+
+
+/**
+ * Context we keep per get withdrawal operation request.
+ */
+struct WithdrawContext
+{
+  /**
+   * When does this request time out.
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * The withdrawal operation this is about.
+   */
+  struct WithdrawalOperation *wo;
+
+};
+
+
+/**
+ * Handle for the fake bank.
+ */
+struct TALER_FAKEBANK_Handle
+{
+  /**
+   * We store transactions in a revolving array.
+   */
+  struct Transaction **transactions;
+
+  /**
+   * HTTP server we run to pretend to be the "test" bank.
+   */
+  struct MHD_Daemon *mhd_bank;
+
+  /**
+   * Task running HTTP server for the "test" bank,
+   * unless we are using a thread pool (then NULL).
+   */
+  struct GNUNET_SCHEDULER_Task *mhd_task;
+
+  /**
+   * Task for expiring long-polling connections,
+   * unless we are using a thread pool (then NULL).
+   */
+  struct GNUNET_SCHEDULER_Task *lp_task;
+
+  /**
+   * Task for expiring long-polling connections, unless we are using the
+   * GNUnet scheduler (then NULL).
+   */
+  pthread_t lp_thread;
+
+  /**
+   * MIN-heap of long pollers, sorted by timeout.
+   */
+  struct GNUNET_CONTAINER_Heap *lp_heap;
+
+  /**
+   * Hashmap of reserve public keys to
+   * `struct Transaction` with that reserve public
+   * key. Used to prevent public-key re-use.
+   */
+  struct GNUNET_CONTAINER_MultiPeerMap *rpubs;
+
+  /**
+   * Hashmap of short hashes (wopids) to
+   * `struct WithdrawalOperation`.
+   * Used to lookup withdrawal operations.
+   */
+  struct GNUNET_CONTAINER_MultiShortmap *wops;
+
+  /**
+   * (Base) URL to suggest for the exchange.  Can
+   * be NULL if there is no suggestion to be made.
+   */
+  char *exchange_url;
+
+  /**
+   * Lock for accessing @a rpubs map.
+   */
+  pthread_mutex_t rpubs_lock;
+
+  /**
+   * Hashmap of hashes of account names to `struct Account`.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *accounts;
+
+  /**
+   * Lock for accessing @a accounts hash map.
+   */
+  pthread_mutex_t accounts_lock;
+
+  /**
+   * Hashmap of hashes of transaction request_uids to `struct Transaction`.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *uuid_map;
+
+  /**
+   * Lock for accessing @a uuid_map.
+   */
+  pthread_mutex_t uuid_map_lock;
+
+  /**
+   * Lock for accessing the internals of
+   * accounts and transaction array entries.
+   */
+  pthread_mutex_t big_lock;
+
+  /**
+   * How much money should be put into new accounts
+   * on /register.
+   */
+  struct TALER_Amount signup_bonus;
+
+  /**
+   * Current transaction counter.
+   */
+  uint64_t serial_counter;
+
+  /**
+   * Number of transactions we keep in memory (at most).
+   */
+  uint64_t ram_limit;
+
+  /**
+   * Currency used by the fakebank.
+   */
+  char *currency;
+
+  /**
+   * Hostname of the fakebank.
+   */
+  char *hostname;
+
+  /**
+   * BaseURL of the fakebank.
+   */
+  char *my_baseurl;
+
+  /**
+   * Our port number.
+   */
+  uint16_t port;
+
+#ifdef __linux__
+  /**
+   * Event FD to signal @a lp_thread a change in
+   * @a lp_heap.
+   */
+  int lp_event;
+#else
+  /**
+   * Pipe input to signal @a lp_thread a change in
+   * @a lp_heap.
+   */
+  int lp_event_in;
+
+  /**
+   * Pipe output to signal @a lp_thread a change in
+   * @a lp_heap.
+   */
+  int lp_event_out;
+#endif
+
+  /**
+   * Set to true once we are shutting down.
+   */
+  bool in_shutdown;
+
+  /**
+   * Should we run MHD immediately again?
+   */
+  bool mhd_again;
+
+#if EPOLL_SUPPORT
+  /**
+   * Boxed @e mhd_fd.
+   */
+  struct GNUNET_NETWORK_Handle *mhd_rfd;
+
+  /**
+   * File descriptor to use to wait for MHD.
+   */
+  int mhd_fd;
+#endif
+};
+
+
+/**
+ * Task run whenever HTTP server operations are pending.
+ *
+ * @param cls the `struct TALER_FAKEBANK_Handle`
+ */
+void
+TALER_FAKEBANK_run_mhd_ (void *cls);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_api_check.c 
b/src/bank-lib/fakebank_api_check.c
new file mode 100644
index 00000000..04656eba
--- /dev/null
+++ b/src/bank-lib/fakebank_api_check.c
@@ -0,0 +1,238 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_api_check.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Generate log messages for failed check operation.
+ *
+ * @param h handle to output transaction log for
+ */
+static void
+check_log (struct TALER_FAKEBANK_Handle *h)
+{
+  for (uint64_t i = 0; i<h->ram_limit; i++)
+  {
+    struct Transaction *t = h->transactions[i];
+
+    if (NULL == t)
+      continue;
+    if (! t->unchecked)
+      continue;
+    switch (t->type)
+    {
+    case T_DEBIT:
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "%s -> %s (%s) %s (%s)\n",
+                  t->debit_account->account_name,
+                  t->credit_account->account_name,
+                  TALER_amount2s (&t->amount),
+                  t->subject.debit.exchange_base_url,
+                  "DEBIT");
+      break;
+    case T_CREDIT:
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "%s -> %s (%s) %s (%s)\n",
+                  t->debit_account->account_name,
+                  t->credit_account->account_name,
+                  TALER_amount2s (&t->amount),
+                  TALER_B2S (&t->subject.credit.reserve_pub),
+                  "CREDIT");
+      break;
+    case T_WAD:
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "%s -> %s (%s) %s[%s] (%s)\n",
+                  t->debit_account->account_name,
+                  t->credit_account->account_name,
+                  TALER_amount2s (&t->amount),
+                  t->subject.wad.origin_base_url,
+                  TALER_B2S (&t->subject.wad),
+                  "WAD");
+      break;
+    }
+  }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h,
+                            const struct TALER_Amount *want_amount,
+                            const char *want_debit,
+                            const char *want_credit,
+                            const char *exchange_base_url,
+                            struct TALER_WireTransferIdentifierRawP *wtid)
+{
+  struct Account *debit_account;
+  struct Account *credit_account;
+
+  GNUNET_assert (0 ==
+                 strcasecmp (want_amount->currency,
+                             h->currency));
+  debit_account = TALER_FAKEBANK_lookup_account_ (h,
+                                                  want_debit,
+                                                  NULL);
+  credit_account = TALER_FAKEBANK_lookup_account_ (h,
+                                                   want_credit,
+                                                   NULL);
+  if (NULL == debit_account)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "I wanted: %s->%s (%s) from exchange %s (DEBIT), but debit 
account does not even exist!\n",
+                want_debit,
+                want_credit,
+                TALER_amount2s (want_amount),
+                exchange_base_url);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == credit_account)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "I wanted: %s->%s (%s) from exchange %s (DEBIT), but credit 
account does not even exist!\n",
+                want_debit,
+                want_credit,
+                TALER_amount2s (want_amount),
+                exchange_base_url);
+    return GNUNET_SYSERR;
+  }
+  for (struct Transaction *t = debit_account->out_tail;
+       NULL != t;
+       t = t->prev_out)
+  {
+    if ( (t->unchecked) &&
+         (credit_account == t->credit_account) &&
+         (T_DEBIT == t->type) &&
+         (0 == TALER_amount_cmp (want_amount,
+                                 &t->amount)) &&
+         (0 == strcasecmp (exchange_base_url,
+                           t->subject.debit.exchange_base_url)) )
+    {
+      *wtid = t->subject.debit.wtid;
+      t->unchecked = false;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "Did not find matching transaction! I have:\n");
+  check_log (h);
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "I wanted: %s->%s (%s) from exchange %s (DEBIT)\n",
+              want_debit,
+              want_credit,
+              TALER_amount2s (want_amount),
+              exchange_base_url);
+  return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h,
+                             const struct TALER_Amount *want_amount,
+                             const char *want_debit,
+                             const char *want_credit,
+                             const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  struct Account *debit_account;
+  struct Account *credit_account;
+
+  GNUNET_assert (0 == strcasecmp (want_amount->currency,
+                                  h->currency));
+  debit_account = TALER_FAKEBANK_lookup_account_ (h,
+                                                  want_debit,
+                                                  NULL);
+  credit_account = TALER_FAKEBANK_lookup_account_ (h,
+                                                   want_credit,
+                                                   NULL);
+  if (NULL == debit_account)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but debit 
account is unknown.\n",
+                want_debit,
+                want_credit,
+                TALER_amount2s (want_amount),
+                TALER_B2S (reserve_pub));
+    return GNUNET_SYSERR;
+  }
+  if (NULL == credit_account)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "I wanted:\n%s -> %s (%s) with subject %s (CREDIT) but credit 
account is unknown.\n",
+                want_debit,
+                want_credit,
+                TALER_amount2s (want_amount),
+                TALER_B2S (reserve_pub));
+    return GNUNET_SYSERR;
+  }
+  for (struct Transaction *t = credit_account->in_tail;
+       NULL != t;
+       t = t->prev_in)
+  {
+    if ( (t->unchecked) &&
+         (debit_account == t->debit_account) &&
+         (T_CREDIT == t->type) &&
+         (0 == TALER_amount_cmp (want_amount,
+                                 &t->amount)) &&
+         (0 == GNUNET_memcmp (reserve_pub,
+                              &t->subject.credit.reserve_pub)) )
+    {
+      t->unchecked = false;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "Did not find matching transaction!\nI have:\n");
+  check_log (h);
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "I wanted:\n%s -> %s (%s) with subject %s (CREDIT)\n",
+              want_debit,
+              want_credit,
+              TALER_amount2s (want_amount),
+              TALER_B2S (reserve_pub));
+  return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h)
+{
+  for (uint64_t i = 0; i<h->ram_limit; i++)
+  {
+    struct Transaction *t = h->transactions[i];
+
+    if ( (NULL != t) &&
+         (t->unchecked) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Expected empty transaction set, but I have:\n");
+      check_log (h);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_bank.c b/src/bank-lib/fakebank_bank.c
new file mode 100644
index 00000000..ede148a4
--- /dev/null
+++ b/src/bank-lib/fakebank_bank.c
@@ -0,0 +1,224 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank.c
+ * @brief Main dispatcher for the Taler Bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank.h"
+#include "fakebank_bank_get_accounts.h"
+#include "fakebank_bank_get_accounts_withdrawals.h"
+#include "fakebank_bank_get_root.h"
+#include "fakebank_bank_post_accounts_withdrawals.h"
+#include "fakebank_bank_post_accounts_withdrawals_abort.h"
+#include "fakebank_bank_post_accounts_withdrawals_confirm.h"
+#include "fakebank_bank_testing_register.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_main_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *url,
+  const char *method,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_HEAD))
+    method = MHD_HTTP_METHOD_GET;
+  if ( (0 == strcmp (url,
+                     "/config")) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_GET)) )
+  {
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string ("version",
+                               "0:0:0"),
+      GNUNET_JSON_pack_string ("currency",
+                               h->currency),
+      GNUNET_JSON_pack_string ("name",
+                               "taler-bank-access"));
+  }
+  if ( (0 == strcmp (url,
+                     "/public-accounts")) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_GET)) )
+  {
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_array_steal ("public_accounts",
+                                    json_array ()));
+  }
+  if ( (0 == strncmp (url,
+                      "/accounts/",
+                      strlen ("/accounts/"))) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_POST)) )
+  {
+    const char *acc_name = &url[strlen ("/accounts/")];
+    const char *end_acc = strchr (acc_name,
+                                  '/');
+    char *acc;
+    MHD_RESULT ret;
+
+    if ( (NULL == end_acc) ||
+         (0 != strncmp (end_acc,
+                        "/withdrawals",
+                        strlen ("/withdrawals"))) )
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+                                         acc_name);
+    }
+    acc = GNUNET_strndup (acc_name,
+                          end_acc - acc_name);
+    end_acc += strlen ("/withdrawals");
+    if ('/' == *end_acc)
+    {
+      const char *wid = end_acc + 1;
+      const char *opid = strchr (wid,
+                                 '/');
+      char *wi;
+
+      if ( (NULL == opid) ||
+           ( (0 != strcmp (opid,
+                           "/abort")) &&
+             (0 != strcmp (opid,
+                           "/confirm")) ) )
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (acc);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+                                           acc_name);
+      }
+      wi = GNUNET_strndup (wid,
+                           opid - wid);
+      if (0 == strcmp (opid,
+                       "/abort"))
+      {
+        ret = TALER_FAKEBANK_bank_withdrawals_abort_ (h,
+                                                      connection,
+                                                      acc,
+                                                      wi);
+        GNUNET_free (wi);
+        GNUNET_free (acc);
+        return ret;
+      }
+      if (0 == strcmp (opid,
+                       "/confirm"))
+      {
+        ret = TALER_FAKEBANK_bank_withdrawals_confirm_ (h,
+                                                        connection,
+                                                        acc,
+                                                        wi);
+        GNUNET_free (wi);
+        GNUNET_free (acc);
+        return ret;
+      }
+      GNUNET_assert (0);
+    }
+    ret = TALER_FAKEBANK_bank_post_account_withdrawals_ (
+      h,
+      connection,
+      acc,
+      upload_data,
+      upload_data_size,
+      con_cls);
+    GNUNET_free (acc);
+    return ret;
+  }
+
+  if ( (0 == strncmp (url,
+                      "/accounts/",
+                      strlen ("/accounts/"))) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_GET)) )
+  {
+    const char *acc_name = &url[strlen ("/accounts/")];
+    const char *end_acc = strchr (acc_name,
+                                  '/');
+    const char *wid;
+    char *acc;
+    MHD_RESULT ret;
+
+    if (NULL == end_acc)
+    {
+      return TALER_FAKEBANK_bank_get_accounts_ (h,
+                                                connection,
+                                                acc_name);
+    }
+    if (0 != strncmp (end_acc,
+                      "/withdrawals/",
+                      strlen ("/withdrawals/")))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+                                         acc_name);
+    }
+    acc = GNUNET_strndup (acc_name,
+                          end_acc - acc_name);
+    wid = &end_acc[strlen ("/withdrawals/")];
+    ret = TALER_FAKEBANK_bank_get_accounts_withdrawals_ (h,
+                                                         connection,
+                                                         acc,
+                                                         wid);
+    GNUNET_free (acc);
+    return ret;
+  }
+  /* FIXME: implement transactions API: 1.12.2 */
+
+  /* registration API */
+  if ( (0 == strcmp (url,
+                     "/testing/register")) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_POST)) )
+  {
+    return TALER_FAKEBANK_bank_testing_register_ (h,
+                                                  connection,
+                                                  upload_data,
+                                                  upload_data_size,
+                                                  con_cls);
+  }
+  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+                   method,
+                   url);
+  GNUNET_break_op (0);
+  return TALER_MHD_reply_with_error (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+    url);
+}
diff --git a/src/bank-lib/fakebank_bank.h b/src/bank-lib/fakebank_bank.h
new file mode 100644
index 00000000..1c51f88f
--- /dev/null
+++ b/src/bank-lib/fakebank_bank.h
@@ -0,0 +1,54 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank.h
+ * @brief Main dispatcher for the Taler Bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_H
+#define FAKEBANK_BANK_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request to the Taler bank API.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_main_ (struct TALER_FAKEBANK_Handle *h,
+                           struct MHD_Connection *connection,
+                           const char *url,
+                           const char *method,
+                           const char *upload_data,
+                           size_t *upload_data_size,
+                           void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.c 
b/src/bank-lib/fakebank_bank_accounts_withdrawals.c
new file mode 100644
index 00000000..ffcff0e2
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.c
@@ -0,0 +1,101 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_accounts_withdrawals.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_accounts_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_account_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id)
+{
+  struct WithdrawalOperation *wo;
+  struct Account *acc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                    withdrawal_id);
+  if (NULL == wo)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       withdrawal_id);
+  }
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+  if (wo->debit_account != acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       account_name);
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_bool ("aborted",
+                           wo->aborted),
+    GNUNET_JSON_pack_bool ("selection_done",
+                           wo->selection_done),
+    GNUNET_JSON_pack_bool ("transfer_done",
+                           wo->confirmation_done),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("selected_exchange_account",
+                               wo->exchange_account->payto_uri)),
+    GNUNET_JSON_pack_allow_null (
+      wo->selection_done
+      ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub",
+                                    &wo->reserve_pub)
+      : GNUNET_JSON_pack_string ("selected_reserve_pub",
+                                 NULL)),
+    TALER_JSON_pack_amount ("amount",
+                            &wo->amount));
+}
diff --git a/src/bank-lib/fakebank_bank_accounts_withdrawals.h 
b/src/bank-lib/fakebank_bank_accounts_withdrawals.h
new file mode 100644
index 00000000..2a598dee
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_accounts_withdrawals.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_accounts_withdrawals.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H
+#define FAKEBANK_BANK_ACCOUNTS_WITHDRAWALS_H
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_account_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_accounts.c 
b/src/bank-lib/fakebank_bank_get_accounts.c
new file mode 100644
index 00000000..e85387d2
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts.c
@@ -0,0 +1,80 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_accounts.c
+ * @brief implements the Taler Bank API "GET /accounts/" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_accounts.h"
+#include "fakebank_common_lookup.h"
+
+/**
+ * Handle GET /accounts/${account_name} request of the Taler bank API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h,
+                                   struct MHD_Connection *connection,
+                                   const char *account_name)
+{
+  struct Account *acc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_string ("payto_uri",
+                             acc->payto_uri),
+    GNUNET_JSON_pack_object_steal (
+      "balance",
+      GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("credit_debit_indicator",
+                                 acc->is_negative
+                                 ? "debit"
+                                 : "credit"),
+        TALER_JSON_pack_amount ("amount",
+                                &acc->balance))));
+}
diff --git a/src/bank-lib/fakebank_bank_get_accounts.h 
b/src/bank-lib/fakebank_bank_get_accounts.h
new file mode 100644
index 00000000..7d387213
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts.h
@@ -0,0 +1,48 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_accounts.h
+ * @brief implements the Taler Bank API "GET /accounts/" handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_ACCOUNTS_H
+#define FAKEBANK_BANK_GET_ACCOUNTS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle GET /accounts/${account_name} request of the Taler bank API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_ (struct TALER_FAKEBANK_Handle *h,
+                                   struct MHD_Connection *connection,
+                                   const char *account_name);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c 
b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c
new file mode 100644
index 00000000..176b6fb4
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.c
@@ -0,0 +1,111 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_accounts_withdrawals.c
+ * @brief implements the Taler Bank API "GET /accounts/ACC/withdrawals/WID" 
handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_accounts_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id)
+{
+  struct WithdrawalOperation *wo;
+  struct Account *acc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                    withdrawal_id);
+  if (NULL == wo)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       withdrawal_id);
+  }
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+  if (wo->debit_account != acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       account_name);
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_bool ("aborted",
+                           wo->aborted),
+    GNUNET_JSON_pack_bool ("selection_done",
+                           wo->selection_done),
+    GNUNET_JSON_pack_bool ("transfer_done",
+                           wo->confirmation_done),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("selected_exchange_account",
+                               wo->exchange_account->payto_uri)),
+    GNUNET_JSON_pack_allow_null (
+      wo->selection_done
+      ? GNUNET_JSON_pack_data_auto ("selected_reserve_pub",
+                                    &wo->reserve_pub)
+      : GNUNET_JSON_pack_string ("selected_reserve_pub",
+                                 NULL)),
+    TALER_JSON_pack_amount ("amount",
+                            &wo->amount));
+}
diff --git a/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h 
b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h
new file mode 100644
index 00000000..53dd873e
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_accounts_withdrawals.h
@@ -0,0 +1,53 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_accounts_withdrawals.h
+ * @brief implements the Taler Bank API "GET /accounts/ACC/withdrawals/WID" 
handler
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_ACCOUNTS_WITHDRAWALS_H
+#define FAKEBANK_BANK_GET_ACCOUNTS_WITHDRAWALS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_accounts_withdrawals.h"
+
+
+/**
+ * Handle GET /accounts/${account_name}/withdrawals/{withdrawal_id} request
+ * to the Taler bank access API.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param withdrawal_id withdrawal ID to return status of
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_accounts_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_get_root.c 
b/src/bank-lib/fakebank_bank_get_root.c
new file mode 100644
index 00000000..8c34697b
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_root.c
@@ -0,0 +1,58 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_root.c
+ * @brief handle a GET "/" request for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_get_root.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h,
+                               struct MHD_Connection *connection)
+{
+  MHD_RESULT ret;
+  struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank!"
+
+  (void) h;
+  resp = MHD_create_response_from_buffer (
+    strlen (HELLOMSG),
+    HELLOMSG,
+    MHD_RESPMEM_MUST_COPY);
+  ret = MHD_queue_response (connection,
+                            MHD_HTTP_OK,
+                            resp);
+  MHD_destroy_response (resp);
+  return ret;
+}
diff --git a/src/bank-lib/fakebank_bank_get_root.h 
b/src/bank-lib/fakebank_bank_get_root.h
new file mode 100644
index 00000000..2eedb94a
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_get_root.h
@@ -0,0 +1,44 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_get_root.c
+ * @brief handle a GET "/" request for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_GET_ROOT_H
+#define FAKEBANK_BANK_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_get_root_ (struct TALER_FAKEBANK_Handle *h,
+                               struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c
new file mode 100644
index 00000000..f05faf14
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.c
@@ -0,0 +1,196 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals.c
+ * @brief implementation of the bank API's POST /accounts/AID/withdrawals 
endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_accounts_withdrawals.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Execute POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param amount amont to withdraw
+ * @return MHD result code
+ */
+static MHD_RESULT
+do_post_account_withdrawals (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const struct TALER_Amount *amount)
+{
+  struct Account *acc;
+  struct WithdrawalOperation *wo;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+  wo = GNUNET_new (struct WithdrawalOperation);
+  wo->debit_account = acc;
+  wo->amount = *amount;
+  if (NULL == h->wops)
+  {
+    h->wops = GNUNET_CONTAINER_multishortmap_create (32,
+                                                     GNUNET_YES);
+  }
+  while (1)
+  {
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                &wo->wopid,
+                                sizeof (wo->wopid));
+    if (GNUNET_OK ==
+        GNUNET_CONTAINER_multishortmap_put (h->wops,
+                                            &wo->wopid,
+                                            wo,
+                                            
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+      break;
+  }
+  {
+    char *wopids;
+    char *uri;
+    MHD_RESULT res;
+
+    wopids = GNUNET_STRINGS_data_to_string_alloc (&wo->wopid,
+                                                  sizeof (wo->wopid));
+    GNUNET_asprintf (&uri,
+                     "taler+http://withdraw/%s:%u/taler-bank-integration/%s";,
+                     h->hostname,
+                     (unsigned int) h->port,
+                     wopids);
+    GNUNET_free (wopids);
+    res = TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string ("taler_withdraw_uri",
+                               uri),
+      GNUNET_JSON_pack_data_auto ("withdrawal_id",
+                                  &wo->wopid));
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    GNUNET_free (uri);
+    return res;
+  }
+}
+
+
+/**
+ * Handle POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_post_account_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  enum GNUNET_JSON_PostResult pr;
+  json_t *json;
+  MHD_RESULT res;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+    *con_cls = cc;
+  }
+  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+                                connection,
+                                &cc->ctx,
+                                upload_data,
+                                upload_data_size,
+                                &json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_CONTINUE:
+    return MHD_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_SUCCESS:
+    break;
+  }
+
+  {
+    struct TALER_Amount amount;
+    enum GNUNET_GenericReturnValue ret;
+    struct GNUNET_JSON_Specification spec[] = {
+      TALER_JSON_spec_amount ("amount",
+                              h->currency,
+                              &amount),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        (ret = TALER_MHD_parse_json_data (connection,
+                                          json,
+                                          spec)))
+    {
+      GNUNET_break_op (0);
+      json_decref (json);
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+    }
+    res = do_post_account_withdrawals (h,
+                                       connection,
+                                       account_name,
+                                       &amount);
+  }
+  json_decref (json);
+  return res;
+}
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h
new file mode 100644
index 00000000..1becf1ef
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals.h
@@ -0,0 +1,54 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals.c
+ * @brief implementation of the bank API's POST /accounts/AID/withdrawals 
endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H
+#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/$account_name/withdrawals request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the account
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_post_account_withdrawals_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c
new file mode 100644
index 00000000..4a252f9b
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c
@@ -0,0 +1,97 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals_abort.c
+ * @brief implement bank API withdrawals /abort endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_accounts_withdrawals_abort.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_abort_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id)
+{
+  struct WithdrawalOperation *wo;
+  struct Account *acc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                    withdrawal_id);
+  if (NULL == wo)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       withdrawal_id);
+  }
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+  if (wo->debit_account != acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       account_name);
+  }
+  if (wo->confirmation_done)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       TALER_EC_BANK_ABORT_CONFIRM_CONFLICT,
+                                       account_name);
+  }
+  wo->aborted = true;
+  TALER_FAKEBANK_notify_withdrawal_ (h,
+                                     wo);
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_reply_json (connection,
+                               json_object (), /* FIXME: #7301 */
+                               MHD_HTTP_OK);
+}
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h
new file mode 100644
index 00000000..ba99e493
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals_abort.h
+ * @brief implement bank API withdrawals /abort endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_ABORT_H
+#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_ABORT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/abort 
request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the debited account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_abort_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c
new file mode 100644
index 00000000..232b4f92
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c
@@ -0,0 +1,139 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.c
+ * @brief implement bank API withdrawals /confirm endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_post_accounts_withdrawals_confirm.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_make_admin_transfer.h"
+
+
+/**
+ * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm 
request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the debited account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_confirm_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id)
+{
+  struct WithdrawalOperation *wo;
+  struct Account *acc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                    withdrawal_id);
+  if (NULL == wo)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       withdrawal_id);
+  }
+  acc = TALER_FAKEBANK_lookup_account_ (h,
+                                        account_name,
+                                        NULL);
+  if (NULL == acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                       account_name);
+  }
+  if (wo->debit_account != acc)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       account_name);
+  }
+  if (NULL == wo->exchange_account)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_BANK_POST_WITHDRAWAL_OPERATION_REQUIRED,
+                                       NULL);
+  }
+  if (wo->aborted)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       TALER_EC_BANK_CONFIRM_ABORT_CONFLICT,
+                                       account_name);
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  if (GNUNET_OK !=
+      TALER_FAKEBANK_make_admin_transfer_ (
+        h,
+        wo->debit_account->account_name,
+        wo->exchange_account->account_name,
+        &wo->amount,
+        &wo->reserve_pub,
+        &wo->row_id,
+        &wo->timestamp))
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+                                       NULL);
+  }
+  /* Re-acquiring the lock and continuing to operate on 'wo'
+     is currently (!) acceptable because we NEVER free 'wo'
+     until shutdown. We may want to revise this if keeping
+     all withdraw operations in RAM becomes an issue... */
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo->confirmation_done = true;
+  TALER_FAKEBANK_notify_withdrawal_ (h,
+                                     wo);
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_reply_json (connection,
+                               json_object (),
+                               MHD_HTTP_OK);
+}
diff --git a/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h 
b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h
new file mode 100644
index 00000000..2c301ca2
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_post_accounts_withdrawals_confirm.h
+ * @brief implement bank API withdrawals /confirm endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_CONFIRM_H
+#define FAKEBANK_BANK_POST_ACCOUNTS_WITHDRAWALS_CONFIRM_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle POST /accounts/{account_name}/withdrawals/{withdrawal_id}/confirm 
request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param account_name name of the debited account
+ * @param withdrawal_id the withdrawal operation identifier
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_withdrawals_confirm_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account_name,
+  const char *withdrawal_id);
+
+#endif
diff --git a/src/bank-lib/fakebank_bank_testing_register.c 
b/src/bank-lib/fakebank_bank_testing_register.c
new file mode 100644
index 00000000..e5720f1a
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_testing_register.c
@@ -0,0 +1,128 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_testing_register.c
+ * @brief implementation of /testing/register endpoint for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_bank_testing_register.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_bank_testing_register_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  enum GNUNET_JSON_PostResult pr;
+  json_t *json;
+  MHD_RESULT res;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+    *con_cls = cc;
+  }
+  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+                                connection,
+                                &cc->ctx,
+                                upload_data,
+                                upload_data_size,
+                                &json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_CONTINUE:
+    return MHD_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_SUCCESS:
+    break;
+  }
+
+  {
+    const char *username;
+    const char *password;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("username",
+                               &username),
+      GNUNET_JSON_spec_string ("password",
+                               &password),
+      GNUNET_JSON_spec_end ()
+    };
+    enum GNUNET_GenericReturnValue ret;
+    struct Account *acc;
+
+    if (GNUNET_OK !=
+        (ret = TALER_MHD_parse_json_data (connection,
+                                          json,
+                                          spec)))
+    {
+      GNUNET_break_op (0);
+      json_decref (json);
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+    }
+    acc = TALER_FAKEBANK_lookup_account_ (h,
+                                          username,
+                                          NULL);
+    if (NULL != acc)
+    {
+      if (0 != strcmp (password,
+                       acc->password))
+      {
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_CONFLICT,
+                                           TALER_EC_BANK_REGISTER_CONFLICT,
+                                           "password");
+      }
+    }
+    else
+    {
+      acc = TALER_FAKEBANK_lookup_account_ (h,
+                                            username,
+                                            username);
+      GNUNET_assert (NULL != acc);
+      acc->password = GNUNET_strdup (password);
+      acc->balance = h->signup_bonus; /* magic money creation! */
+    }
+    res = TALER_MHD_reply_static (connection,
+                                  MHD_HTTP_NO_CONTENT,
+                                  NULL,
+                                  NULL,
+                                  0);
+  }
+  json_decref (json);
+  return res;
+}
diff --git a/src/bank-lib/fakebank_bank_testing_register.h 
b/src/bank-lib/fakebank_bank_testing_register.h
new file mode 100644
index 00000000..d8744ecc
--- /dev/null
+++ b/src/bank-lib/fakebank_bank_testing_register.h
@@ -0,0 +1,53 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_bank_testing_register.h
+ * @brief implementation of /testing/register endpoint for the bank API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_BANK_TESTING_REGISTER_H
+#define FAKEBANK_BANK_TESTING_REGISTER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+
+/**
+ * Handle POST /testing/register request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_bank_testing_register_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_lookup.c 
b/src/bank-lib/fakebank_common_lookup.c
new file mode 100644
index 00000000..b4f85387
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lookup.c
@@ -0,0 +1,103 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_lookup.c
+ * @brief common helper functions related to lookups
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+
+struct WithdrawalOperation *
+TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h,
+                                             const char *wopid)
+{
+  struct GNUNET_ShortHashCode sh;
+
+  if (NULL == h->wops)
+    return NULL;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (wopid,
+                                     strlen (wopid),
+                                     &sh,
+                                     sizeof (sh)))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  return GNUNET_CONTAINER_multishortmap_get (h->wops,
+                                             &sh);
+}
+
+
+struct Account *
+TALER_FAKEBANK_lookup_account_ (struct TALER_FAKEBANK_Handle *h,
+                                const char *name,
+                                const char *receiver_name)
+{
+  struct GNUNET_HashCode hc;
+  size_t slen;
+  struct Account *account;
+
+  memset (&hc,
+          0,
+          sizeof (hc));
+  slen = strlen (name);
+  GNUNET_CRYPTO_hash (name,
+                      slen,
+                      &hc);
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->accounts_lock));
+  account = GNUNET_CONTAINER_multihashmap_get (h->accounts,
+                                               &hc);
+  if (NULL == account)
+  {
+    if (NULL == receiver_name)
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->accounts_lock));
+      return NULL;
+    }
+    account = GNUNET_new (struct Account);
+    account->account_name = GNUNET_strdup (name);
+    account->receiver_name = GNUNET_strdup (receiver_name);
+    GNUNET_asprintf (&account->payto_uri,
+                     "payto://x-taler-bank/%s/%s?receiver-name=%s",
+                     h->hostname,
+                     account->account_name,
+                     account->receiver_name);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (h->currency,
+                                          &account->balance));
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_CONTAINER_multihashmap_put (h->accounts,
+                                                      &hc,
+                                                      account,
+                                                      
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->accounts_lock));
+  return account;
+}
diff --git a/src/bank-lib/fakebank_common_lookup.h 
b/src/bank-lib/fakebank_common_lookup.h
new file mode 100644
index 00000000..b9344774
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lookup.h
@@ -0,0 +1,62 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_lookup.h
+ * @brief common helper functions related to lookups
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+
+#ifndef FAKEBANK_COMMON_LOOKUP_H
+#define FAKEBANK_COMMON_LOOKUP_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Lookup account with @a name, and if it does not exist, create it.
+ *
+ * @param[in,out] h bank to lookup account at
+ * @param name account name to resolve
+ * @param receiver_name receiver name in payto:// URI,
+ *         NULL if the account must already exist
+ * @return account handle, NULL if account does not yet exist
+ */
+struct Account *
+TALER_FAKEBANK_lookup_account_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const char *name,
+  const char *receiver_name);
+
+
+/**
+ * Find withdrawal operation @a wopid in @a h.
+ *
+ * @param h fakebank handle
+ * @param wopid withdrawal operation ID as a string
+ * @return NULL if operation was not found
+ */
+struct WithdrawalOperation *
+TALER_FAKEBANK_lookup_withdrawal_operation_ (struct TALER_FAKEBANK_Handle *h,
+                                             const char *wopid);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_lp.c 
b/src/bank-lib/fakebank_common_lp.c
new file mode 100644
index 00000000..22a9e3ab
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lp.c
@@ -0,0 +1,344 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_lp.c
+ * @brief long-polling support for fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+void
+TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp)
+{
+  struct TALER_FAKEBANK_Handle *h = lp->h;
+  struct Account *acc = lp->account;
+
+  GNUNET_CONTAINER_DLL_remove (acc->lp_head,
+                               acc->lp_tail,
+                               lp);
+  MHD_resume_connection (lp->conn);
+  GNUNET_free (lp);
+  h->mhd_again = true;
+#ifdef __linux__
+  if (-1 == h->lp_event)
+#else
+  if ( (-1 == h->lp_event_in) &&
+       (-1 == h->lp_event_out) )
+#endif
+  {
+    if (NULL != h->mhd_task)
+      GNUNET_SCHEDULER_cancel (h->mhd_task);
+    h->mhd_task =
+      GNUNET_SCHEDULER_add_now (&TALER_FAKEBANK_run_mhd_,
+                                h);
+  }
+}
+
+
+void *
+TALER_FAKEBANK_lp_expiration_thread_ (void *cls)
+{
+  struct TALER_FAKEBANK_Handle *h = cls;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  while (! h->in_shutdown)
+  {
+    struct LongPoller *lp;
+    int timeout_ms;
+
+    lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
+    while ( (NULL != lp) &&
+            GNUNET_TIME_absolute_is_past (lp->timeout))
+    {
+      GNUNET_assert (lp ==
+                     GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
+      TALER_FAKEBANK_lp_trigger_ (lp);
+      lp = GNUNET_CONTAINER_heap_peek (h->lp_heap);
+    }
+    if (NULL != lp)
+    {
+      struct GNUNET_TIME_Relative rem;
+      unsigned long long left_ms;
+
+      rem = GNUNET_TIME_absolute_get_remaining (lp->timeout);
+      left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+      if (left_ms > INT_MAX)
+        timeout_ms = INT_MAX;
+      else
+        timeout_ms = (int) left_ms;
+    }
+    else
+    {
+      timeout_ms = -1; /* infinity */
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    {
+      struct pollfd p = {
+#ifdef __linux__
+        .fd = h->lp_event,
+#else
+        .fd = h->lp_event_out,
+#endif
+        .events = POLLIN
+      };
+      int ret;
+
+      ret = poll (&p,
+                  1,
+                  timeout_ms);
+      if (-1 == ret)
+      {
+        if (EINTR != errno)
+          GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                               "poll");
+      }
+      else if (1 == ret)
+      {
+        /* clear event */
+        uint64_t ev;
+        ssize_t iret;
+
+#ifdef __linux__
+        iret = read (h->lp_event,
+                     &ev,
+                     sizeof (ev));
+#else
+        iret = read (h->lp_event_out,
+                     &ev,
+                     sizeof (ev));
+#endif
+        if (-1 == iret)
+        {
+          GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                               "read");
+        }
+        else
+        {
+          GNUNET_break (sizeof (uint64_t) == iret);
+        }
+      }
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return NULL;
+}
+
+
+/**
+ * Trigger long pollers that might have been waiting
+ * for @a t.
+ *
+ * @param h fakebank handle
+ * @param t transaction to notify on
+ */
+void
+TALER_FAKEBANK_notify_transaction_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct Transaction *t)
+{
+  struct Account *debit_acc = t->debit_account;
+  struct Account *credit_acc = t->credit_account;
+  struct LongPoller *nxt;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  for (struct LongPoller *lp = debit_acc->lp_head;
+       NULL != lp;
+       lp = nxt)
+  {
+    nxt = lp->next;
+    if (LP_DEBIT == lp->type)
+    {
+      GNUNET_assert (lp ==
+                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
+      TALER_FAKEBANK_lp_trigger_ (lp);
+    }
+  }
+  for (struct LongPoller *lp = credit_acc->lp_head;
+       NULL != lp;
+       lp = nxt)
+  {
+    nxt = lp->next;
+    if (LP_CREDIT == lp->type)
+    {
+      GNUNET_assert (lp ==
+                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
+      TALER_FAKEBANK_lp_trigger_ (lp);
+    }
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+}
+
+
+/**
+ * Notify long pollers that a @a wo was updated.
+ * Must be called with the "big_lock" still held.
+ *
+ * @param h fakebank handle
+ * @param wo withdraw operation that finished
+ */
+void
+TALER_FAKEBANK_notify_withdrawal_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const struct WithdrawalOperation *wo)
+{
+  struct Account *debit_acc = wo->debit_account;
+  struct LongPoller *nxt;
+
+  for (struct LongPoller *lp = debit_acc->lp_head;
+       NULL != lp;
+       lp = nxt)
+  {
+    nxt = lp->next;
+    if ( (LP_WITHDRAW == lp->type) &&
+         (wo == lp->wo) )
+    {
+      GNUNET_assert (lp ==
+                     GNUNET_CONTAINER_heap_remove_node (lp->hn));
+      TALER_FAKEBANK_lp_trigger_ (lp);
+    }
+  }
+}
+
+
+/**
+ * Task run when a long poller is about to time out.
+ * Only used in single-threaded mode.
+ *
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
+ */
+static void
+lp_timeout (void *cls)
+{
+  struct TALER_FAKEBANK_Handle *h = cls;
+  struct LongPoller *lp;
+
+  h->lp_task = NULL;
+  while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap)))
+  {
+    if (GNUNET_TIME_absolute_is_future (lp->timeout))
+      break;
+    GNUNET_assert (lp ==
+                   GNUNET_CONTAINER_heap_remove_root (h->lp_heap));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Timeout reached for long poller %p\n",
+                lp->conn);
+    TALER_FAKEBANK_lp_trigger_ (lp);
+  }
+  if (NULL == lp)
+    return;
+  h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout,
+                                        &lp_timeout,
+                                        h);
+}
+
+
+/**
+ * Reschedule the timeout task of @a h for time @a t.
+ *
+ * @param h fakebank handle
+ * @param t when will the next connection timeout expire
+ */
+static void
+reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h,
+                       struct GNUNET_TIME_Absolute t)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Scheduling timeout task for %s\n",
+              GNUNET_STRINGS_absolute_time_to_string (t));
+#ifdef __linux__
+  if (-1 != h->lp_event)
+#else
+  if (-1 != h->lp_event_in && -1 != h->lp_event_out)
+#endif
+  {
+    uint64_t num = 1;
+
+    GNUNET_break (sizeof (num) ==
+#ifdef __linux__
+                  write (h->lp_event,
+                         &num,
+                         sizeof (num)));
+#else
+                  write (h->lp_event_in,
+                         &num,
+                         sizeof (num)));
+#endif
+  }
+  else
+  {
+    if (NULL != h->lp_task)
+      GNUNET_SCHEDULER_cancel (h->lp_task);
+    h->lp_task = GNUNET_SCHEDULER_add_at (t,
+                                          &lp_timeout,
+                                          h);
+  }
+}
+
+
+void
+TALER_FAKEBANK_start_lp_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  struct Account *acc,
+  struct GNUNET_TIME_Relative lp_timeout,
+  enum LongPollType dir,
+  const struct WithdrawalOperation *wo)
+{
+  struct LongPoller *lp;
+  bool toc;
+
+  lp = GNUNET_new (struct LongPoller);
+  lp->account = acc;
+  lp->h = h;
+  lp->wo = wo;
+  lp->conn = connection;
+  lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout);
+  lp->type = dir;
+  lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap,
+                                         lp,
+                                         lp->timeout.abs_value_us);
+  toc = (lp ==
+         GNUNET_CONTAINER_heap_peek (h->lp_heap));
+  GNUNET_CONTAINER_DLL_insert (acc->lp_head,
+                               acc->lp_tail,
+                               lp);
+  MHD_suspend_connection (connection);
+  if (toc)
+    reschedule_lp_timeout (h,
+                           lp->timeout);
+
+}
diff --git a/src/bank-lib/fakebank_common_lp.h 
b/src/bank-lib/fakebank_common_lp.h
new file mode 100644
index 00000000..37094e12
--- /dev/null
+++ b/src/bank-lib/fakebank_common_lp.h
@@ -0,0 +1,100 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_lp.h
+ * @brief long-polling support for fakebank
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_LP_H
+#define FAKEBANK_COMMON_LP_H
+#include "taler_fakebank_lib.h"
+
+
+/**
+ * Trigger the @a lp. Frees associated resources, except the entry of @a lp in
+ * the timeout heap.  Must be called while the ``big lock`` is held.
+ *
+ * @param[in] lp long poller to trigger
+ */
+void
+TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp);
+
+
+/**
+ * Trigger long pollers that might have been waiting
+ * for @a t.
+ *
+ * @param h fakebank handle
+ * @param t transaction to notify on
+ */
+void
+TALER_FAKEBANK_notify_transaction_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct Transaction *t);
+
+
+/**
+ * Notify long pollers that a @a wo was updated.
+ * Must be called with the "big_lock" still held.
+ *
+ * @param h fakebank handle
+ * @param wo withdraw operation that finished
+ */
+void
+TALER_FAKEBANK_notify_withdrawal_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const struct WithdrawalOperation *wo);
+
+
+/**
+ * Start long-polling for @a connection and @a acc
+ * for transfers in @a dir. Must be called with the
+ * "big lock" held.
+ *
+ * @param[in,out] h fakebank handle
+ * @param[in,out] connection to suspend
+ * @param[in,out] acc account affected
+ * @param lp_timeout how long to suspend
+ * @param dir direction of transfers to watch for
+ * @param wo withdraw operation to watch, only
+ *        if @a dir is #LP_WITHDRAW
+ */
+void
+TALER_FAKEBANK_start_lp_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  struct Account *acc,
+  struct GNUNET_TIME_Relative lp_timeout,
+  enum LongPollType dir,
+  const struct WithdrawalOperation *wo);
+
+
+/**
+ * Main routine of a thread that is run to wake up connections that have hit
+ * their timeout. Runs until in_shutdown is set to true. Must be send signals
+ * via lp_event on shutdown and/or whenever the heap changes to an earlier
+ * timeout.
+ *
+ * @param cls a `struct TALER_FAKEBANK_Handle *`
+ * @return NULL
+ */
+void *
+TALER_FAKEBANK_lp_expiration_thread_ (void *cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.c 
b/src/bank-lib/fakebank_common_make_admin_transfer.c
new file mode 100644
index 00000000..4a11d412
--- /dev/null
+++ b/src/bank-lib/fakebank_common_make_admin_transfer.c
@@ -0,0 +1,117 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_make_admin_transfer.c
+ * @brief routine to create transfers to the exchange
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_transact.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_admin_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const char *debit_account,
+  const char *credit_account,
+  const struct TALER_Amount *amount,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  uint64_t *row_id,
+  struct GNUNET_TIME_Timestamp *timestamp)
+{
+  struct Transaction *t;
+  const struct GNUNET_PeerIdentity *pid;
+  struct Account *debit_acc;
+  struct Account *credit_acc;
+
+  GNUNET_static_assert (sizeof (*pid) ==
+                        sizeof (*reserve_pub));
+  pid = (const struct GNUNET_PeerIdentity *) reserve_pub;
+  GNUNET_assert (NULL != debit_account);
+  GNUNET_assert (NULL != credit_account);
+  GNUNET_assert (0 == strcasecmp (amount->currency,
+                                  h->currency));
+  GNUNET_break (0 != strncasecmp ("payto://",
+                                  debit_account,
+                                  strlen ("payto://")));
+  GNUNET_break (0 != strncasecmp ("payto://",
+                                  credit_account,
+                                  strlen ("payto://")));
+  debit_acc = TALER_FAKEBANK_lookup_account_ (h,
+                                              debit_account,
+                                              debit_account);
+  credit_acc = TALER_FAKEBANK_lookup_account_ (h,
+                                               credit_account,
+                                               credit_account);
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->rpubs_lock));
+  t = GNUNET_CONTAINER_multipeermap_get (h->rpubs,
+                                         pid);
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->rpubs_lock));
+  if (NULL != t)
+  {
+    /* duplicate reserve public key not allowed */
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+
+  t = GNUNET_new (struct Transaction);
+  t->unchecked = true;
+  t->debit_account = debit_acc;
+  t->credit_account = credit_acc;
+  t->amount = *amount;
+  t->date = GNUNET_TIME_timestamp_get ();
+  if (NULL != timestamp)
+    *timestamp = t->date;
+  t->type = T_CREDIT;
+  t->subject.credit.reserve_pub = *reserve_pub;
+  TALER_FAKEBANK_transact_ (h,
+                            t);
+  if (NULL != row_id)
+    *row_id = t->row_id;
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->rpubs_lock));
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CONTAINER_multipeermap_put (
+                   h->rpubs,
+                   pid,
+                   t,
+                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->rpubs_lock));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Making transfer from %s to %s over %s and subject %s at row 
%llu\n",
+              debit_account,
+              credit_account,
+              TALER_amount2s (amount),
+              TALER_B2S (reserve_pub),
+              (unsigned long long) t->row_id);
+  TALER_FAKEBANK_notify_transaction_ (h,
+                                      t);
+  return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_make_admin_transfer.h 
b/src/bank-lib/fakebank_common_make_admin_transfer.h
new file mode 100644
index 00000000..841cfb48
--- /dev/null
+++ b/src/bank-lib/fakebank_common_make_admin_transfer.h
@@ -0,0 +1,56 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_make_admin_transfer.h
+ * @brief routine to create transfers to the exchange
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H
+#define FAKEBANK_COMMON_MAKE_ADMIN_TRANSFER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Tell the fakebank to create another wire transfer *to* an exchange.
+ *
+ * @param h fake bank handle
+ * @param debit_account account to debit
+ * @param credit_account account to credit
+ * @param amount amount to transfer
+ * @param reserve_pub reserve public key to use in subject
+ * @param[out] row_id serial_id of the transfer
+ * @param[out] timestamp when was the transfer made
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_admin_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const char *debit_account,
+  const char *credit_account,
+  const struct TALER_Amount *amount,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  uint64_t *row_id,
+  struct GNUNET_TIME_Timestamp *timestamp);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_parser.c 
b/src/bank-lib/fakebank_common_parser.c
new file mode 100644
index 00000000..98b8d609
--- /dev/null
+++ b/src/bank-lib/fakebank_common_parser.c
@@ -0,0 +1,138 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_parser.c
+ * @brief functions to help parse REST requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_common_parse_history_args (
+  const struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  struct HistoryArgs *ha)
+{
+  const char *start;
+  const char *delta;
+  const char *long_poll_ms;
+  unsigned long long lp_timeout;
+  unsigned long long sval;
+  long long d;
+  char dummy;
+
+  start = MHD_lookup_connection_value (connection,
+                                       MHD_GET_ARGUMENT_KIND,
+                                       "start");
+  ha->have_start = (NULL != start);
+  delta = MHD_lookup_connection_value (connection,
+                                       MHD_GET_ARGUMENT_KIND,
+                                       "delta");
+  long_poll_ms = MHD_lookup_connection_value (connection,
+                                              MHD_GET_ARGUMENT_KIND,
+                                              "long_poll_ms");
+  lp_timeout = 0;
+  if ( (NULL == delta) ||
+       (1 != sscanf (delta,
+                     "%lld%c",
+                     &d,
+                     &dummy)) )
+  {
+    /* Fail if one of the above failed.  */
+    /* Invalid request, given that this is fakebank we impolitely
+     * just kill the connection instead of returning a nice error.
+     */
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        "delta"))
+           ? GNUNET_NO
+           : GNUNET_SYSERR;
+  }
+  if ( (NULL != long_poll_ms) &&
+       (1 != sscanf (long_poll_ms,
+                     "%llu%c",
+                     &lp_timeout,
+                     &dummy)) )
+  {
+    /* Fail if one of the above failed.  */
+    /* Invalid request, given that this is fakebank we impolitely
+     * just kill the connection instead of returning a nice error.
+     */
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        "long_poll_ms"))
+           ? GNUNET_NO
+           : GNUNET_SYSERR;
+  }
+  if ( (NULL != start) &&
+       (1 != sscanf (start,
+                     "%llu%c",
+                     &sval,
+                     &dummy)) )
+  {
+    /* Fail if one of the above failed.  */
+    /* Invalid request, given that this is fakebank we impolitely
+     * just kill the connection instead of returning a nice error.
+     */
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        "start"))
+           ? GNUNET_NO
+           : GNUNET_SYSERR;
+  }
+  if (NULL == start)
+    ha->start_idx = (d > 0) ? 0 : h->serial_counter;
+  else
+    ha->start_idx = (uint64_t) sval;
+  ha->delta = (int64_t) d;
+  if (0 == ha->delta)
+  {
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        "delta"))
+           ? GNUNET_NO
+           : GNUNET_SYSERR;
+  }
+  ha->lp_timeout
+    = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+                                     lp_timeout);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Request for %lld records from %llu\n",
+              (long long) ha->delta,
+              (unsigned long long) ha->start_idx);
+  return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_parser.h 
b/src/bank-lib/fakebank_common_parser.h
new file mode 100644
index 00000000..8e0d1464
--- /dev/null
+++ b/src/bank-lib/fakebank_common_parser.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_parser.h
+ * @brief functions to help parse REST requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_PARSER_H
+#define FAKEBANK_COMMON_PARSER_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Parse URL history arguments, of _both_ APIs:
+ * /history/incoming and /history/outgoing.
+ *
+ * @param h bank handle to work on
+ * @param connection MHD connection.
+ * @param[out] ha will contain the parsed values.
+ * @return #GNUNET_OK only if the parsing succeeds,
+ *         #GNUNET_SYSERR if it failed,
+ *         #GNUNET_NO if it failed and an error was returned
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_common_parse_history_args (
+  const struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  struct HistoryArgs *ha);
+
+#endif
diff --git a/src/bank-lib/fakebank_common_transact.c 
b/src/bank-lib/fakebank_common_transact.c
new file mode 100644
index 00000000..a099ef96
--- /dev/null
+++ b/src/bank-lib/fakebank_common_transact.c
@@ -0,0 +1,261 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_transact.c
+ * @brief actual transaction logic for FAKEBANK
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_transact.h"
+
+
+/**
+ * Update @a account balance by @a amount.
+ *
+ * The @a big_lock must already be locked when calling
+ * this function.
+ *
+ * @param[in,out] account account to update
+ * @param amount balance change
+ * @param debit true to subtract, false to add @a amount
+ */
+static void
+update_balance (struct Account *account,
+                const struct TALER_Amount *amount,
+                bool debit)
+{
+  if (debit == account->is_negative)
+  {
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&account->balance,
+                                     &account->balance,
+                                     amount));
+    return;
+  }
+  if (0 <= TALER_amount_cmp (&account->balance,
+                             amount))
+  {
+    GNUNET_assert (0 <=
+                   TALER_amount_subtract (&account->balance,
+                                          &account->balance,
+                                          amount));
+  }
+  else
+  {
+    GNUNET_assert (0 <=
+                   TALER_amount_subtract (&account->balance,
+                                          amount,
+                                          &account->balance));
+    account->is_negative = ! account->is_negative;
+  }
+}
+
+
+/**
+ * Add transaction to the debit and credit accounts,
+ * updating the balances as needed.
+ *
+ * The transaction @a t must already be locked
+ * when calling this function!
+ *
+ * @param[in,out] h bank handle
+ * @param[in,out] t transaction to add to account lists
+ */
+void
+TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h,
+                          struct Transaction *t)
+{
+  struct Account *debit_acc = t->debit_account;
+  struct Account *credit_acc = t->credit_account;
+  uint64_t row_id;
+  struct Transaction *old;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  row_id = ++h->serial_counter;
+  old = h->transactions[row_id % h->ram_limit];
+  h->transactions[row_id % h->ram_limit] = t;
+  t->row_id = row_id;
+  GNUNET_CONTAINER_MDLL_insert_tail (out,
+                                     debit_acc->out_head,
+                                     debit_acc->out_tail,
+                                     t);
+  update_balance (debit_acc,
+                  &t->amount,
+                  true);
+  GNUNET_CONTAINER_MDLL_insert_tail (in,
+                                     credit_acc->in_head,
+                                     credit_acc->in_tail,
+                                     t);
+  update_balance (credit_acc,
+                  &t->amount,
+                  false);
+  if (NULL != old)
+  {
+    struct Account *da;
+    struct Account *ca;
+
+    da = old->debit_account;
+    ca = old->credit_account;
+    /* slot was already in use, must clean out old
+       entry first! */
+    GNUNET_CONTAINER_MDLL_remove (out,
+                                  da->out_head,
+                                  da->out_tail,
+                                  old);
+    GNUNET_CONTAINER_MDLL_remove (in,
+                                  ca->in_head,
+                                  ca->in_tail,
+                                  old);
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  if ( (NULL != old) &&
+       (T_DEBIT == old->type) )
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->uuid_map_lock));
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_CONTAINER_multihashmap_remove (h->uuid_map,
+                                                         &old->request_uid,
+                                                         old));
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->uuid_map_lock));
+  }
+  GNUNET_free (old);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const char *debit_account,
+  const char *credit_account,
+  const struct TALER_Amount *amount,
+  const struct TALER_WireTransferIdentifierRawP *subject,
+  const char *exchange_base_url,
+  const struct GNUNET_HashCode *request_uid,
+  uint64_t *ret_row_id,
+  struct GNUNET_TIME_Timestamp *timestamp)
+{
+  struct Transaction *t;
+  struct Account *debit_acc;
+  struct Account *credit_acc;
+  size_t url_len;
+
+  GNUNET_assert (0 == strcasecmp (amount->currency,
+                                  h->currency));
+  GNUNET_assert (NULL != debit_account);
+  GNUNET_assert (NULL != credit_account);
+  GNUNET_break (0 != strncasecmp ("payto://",
+                                  debit_account,
+                                  strlen ("payto://")));
+  GNUNET_break (0 != strncasecmp ("payto://",
+                                  credit_account,
+                                  strlen ("payto://")));
+  url_len = strlen (exchange_base_url);
+  GNUNET_assert (url_len < MAX_URL_LEN);
+  debit_acc = TALER_FAKEBANK_lookup_account_ (h,
+                                              debit_account,
+                                              debit_account);
+  credit_acc = TALER_FAKEBANK_lookup_account_ (h,
+                                               credit_account,
+                                               credit_account);
+  if (NULL != request_uid)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->uuid_map_lock));
+    t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map,
+                                           request_uid);
+    if (NULL != t)
+    {
+      if ( (debit_acc != t->debit_account) ||
+           (credit_acc != t->credit_account) ||
+           (0 != TALER_amount_cmp (amount,
+                                   &t->amount)) ||
+           (T_DEBIT != t->type) ||
+           (0 != GNUNET_memcmp (subject,
+                                &t->subject.debit.wtid)) )
+      {
+        /* Transaction exists, but with different details. */
+        GNUNET_break (0);
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->uuid_map_lock));
+        return GNUNET_SYSERR;
+      }
+      *ret_row_id = t->row_id;
+      *timestamp = t->date;
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->uuid_map_lock));
+      return GNUNET_OK;
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->uuid_map_lock));
+  }
+  t = GNUNET_new (struct Transaction);
+  t->unchecked = true;
+  t->debit_account = debit_acc;
+  t->credit_account = credit_acc;
+  t->amount = *amount;
+  t->date = GNUNET_TIME_timestamp_get ();
+  if (NULL != timestamp)
+    *timestamp = t->date;
+  t->type = T_DEBIT;
+  GNUNET_memcpy (t->subject.debit.exchange_base_url,
+                 exchange_base_url,
+                 url_len);
+  t->subject.debit.wtid = *subject;
+  if (NULL == request_uid)
+    GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
+                                      &t->request_uid);
+  else
+    t->request_uid = *request_uid;
+  TALER_FAKEBANK_transact_ (h,
+                            t);
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->uuid_map_lock));
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CONTAINER_multihashmap_put (
+                   h->uuid_map,
+                   &t->request_uid,
+                   t,
+                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->uuid_map_lock));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Making transfer %llu from %s to %s over %s and subject %s; for 
exchange: %s\n",
+              (unsigned long long) t->row_id,
+              debit_account,
+              credit_account,
+              TALER_amount2s (amount),
+              TALER_B2S (subject),
+              exchange_base_url);
+  *ret_row_id = t->row_id;
+  TALER_FAKEBANK_notify_transaction_ (h,
+                                      t);
+  return GNUNET_OK;
+}
diff --git a/src/bank-lib/fakebank_common_transact.h 
b/src/bank-lib/fakebank_common_transact.h
new file mode 100644
index 00000000..0914785e
--- /dev/null
+++ b/src/bank-lib/fakebank_common_transact.h
@@ -0,0 +1,76 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_common_transact.h
+ * @brief actual transaction logic for FAKEBANK
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_COMMON_TRANSACT_H
+#define FAKEBANK_COMMON_TRANSACT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Add transaction to the debit and credit accounts,
+ * updating the balances as needed.
+ *
+ * The transaction @a t must already be locked
+ * when calling this function!
+ *
+ * @param[in,out] h bank handle
+ * @param[in,out] t transaction to add to account lists
+ */
+void
+TALER_FAKEBANK_transact_ (struct TALER_FAKEBANK_Handle *h,
+                          struct Transaction *t);
+
+
+/**
+ * Tell the fakebank to create another wire transfer *from* an exchange.
+ *
+ * @param h fake bank handle
+ * @param debit_account account to debit
+ * @param credit_account account to credit
+ * @param amount amount to transfer
+ * @param subject wire transfer subject to use
+ * @param exchange_base_url exchange URL
+ * @param request_uid unique number to make the request unique, or NULL to 
create one
+ * @param[out] ret_row_id pointer to store the row ID of this transaction
+ * @param[out] timestamp set to the time of the transfer
+ * @return #GNUNET_YES if the transfer was successful,
+ *         #GNUNET_SYSERR if the request_uid was reused for a different 
transfer
+ */
+enum GNUNET_GenericReturnValue
+TALER_FAKEBANK_make_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  const char *debit_account,
+  const char *credit_account,
+  const struct TALER_Amount *amount,
+  const struct TALER_WireTransferIdentifierRawP *subject,
+  const char *exchange_base_url,
+  const struct GNUNET_HashCode *request_uid,
+  uint64_t *ret_row_id,
+  struct GNUNET_TIME_Timestamp *timestamp);
+
+#endif
diff --git a/src/bank-lib/fakebank_stop.c b/src/bank-lib/fakebank_stop.c
new file mode 100644
index 00000000..e31d4752
--- /dev/null
+++ b/src/bank-lib/fakebank_stop.c
@@ -0,0 +1,192 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_stop.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include <poll.h>
+#ifdef __linux__
+#include <sys/eventfd.h>
+#endif
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lp.h"
+
+
+/**
+ * Helper function to free memory when finished.
+ *
+ * @param cls NULL
+ * @param key key of the account to free (ignored)
+ * @param val a `struct Account` to free.
+ */
+static enum GNUNET_GenericReturnValue
+free_account (void *cls,
+              const struct GNUNET_HashCode *key,
+              void *val)
+{
+  struct Account *account = val;
+
+  (void) cls;
+  (void) key;
+  GNUNET_assert (NULL == account->lp_head);
+  GNUNET_free (account->account_name);
+  GNUNET_free (account->receiver_name);
+  GNUNET_free (account->payto_uri);
+  GNUNET_free (account->password);
+  GNUNET_free (account);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Helper function to free memory when finished.
+ *
+ * @param cls NULL
+ * @param key key of the operation to free (ignored)
+ * @param val a `struct WithdrawalOperation *` to free.
+ */
+static enum GNUNET_GenericReturnValue
+free_withdraw_op (void *cls,
+                  const struct GNUNET_ShortHashCode *key,
+                  void *val)
+{
+  struct WithdrawalOperation *wo = val;
+
+  (void) cls;
+  (void) key;
+  GNUNET_free (wo);
+  return GNUNET_OK;
+}
+
+
+void
+TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
+{
+  if (NULL != h->lp_task)
+  {
+    GNUNET_SCHEDULER_cancel (h->lp_task);
+    h->lp_task = NULL;
+  }
+#if EPOLL_SUPPORT
+  if (NULL != h->mhd_rfd)
+  {
+    GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd);
+    h->mhd_rfd = NULL;
+  }
+#endif
+#ifdef __linux__
+  if (-1 != h->lp_event)
+#else
+  if (-1 != h->lp_event_in && -1 != h->lp_event_out)
+#endif
+  {
+    uint64_t val = 1;
+    void *ret;
+    struct LongPoller *lp;
+
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+    h->in_shutdown = true;
+    while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
+      TALER_FAKEBANK_lp_trigger_ (lp);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+#ifdef __linux__
+    GNUNET_break (sizeof (val) ==
+                  write (h->lp_event,
+                         &val,
+                         sizeof (val)));
+#else
+    GNUNET_break (sizeof (val) ==
+                  write (h->lp_event_in,
+                         &val,
+                         sizeof (val)));
+#endif
+    GNUNET_break (0 ==
+                  pthread_join (h->lp_thread,
+                                &ret));
+    GNUNET_break (NULL == ret);
+#ifdef __linux__
+    GNUNET_break (0 == close (h->lp_event));
+    h->lp_event = -1;
+#else
+    GNUNET_break (0 == close (h->lp_event_in));
+    GNUNET_break (0 == close (h->lp_event_out));
+    h->lp_event_in = -1;
+    h->lp_event_out = -1;
+#endif
+  }
+  else
+  {
+    struct LongPoller *lp;
+
+    while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
+      TALER_FAKEBANK_lp_trigger_ (lp);
+  }
+  if (NULL != h->mhd_bank)
+  {
+    MHD_stop_daemon (h->mhd_bank);
+    h->mhd_bank = NULL;
+  }
+  if (NULL != h->mhd_task)
+  {
+    GNUNET_SCHEDULER_cancel (h->mhd_task);
+    h->mhd_task = NULL;
+  }
+  if (NULL != h->accounts)
+  {
+    GNUNET_CONTAINER_multihashmap_iterate (h->accounts,
+                                           &free_account,
+                                           NULL);
+    GNUNET_CONTAINER_multihashmap_destroy (h->accounts);
+  }
+  if (NULL != h->wops)
+  {
+    GNUNET_CONTAINER_multishortmap_iterate (h->wops,
+                                            &free_withdraw_op,
+                                            NULL);
+    GNUNET_CONTAINER_multishortmap_destroy (h->wops);
+  }
+  GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map);
+  GNUNET_CONTAINER_multipeermap_destroy (h->rpubs);
+  GNUNET_CONTAINER_heap_destroy (h->lp_heap);
+  GNUNET_assert (0 ==
+                 pthread_mutex_destroy (&h->big_lock));
+  GNUNET_assert (0 ==
+                 pthread_mutex_destroy (&h->uuid_map_lock));
+  GNUNET_assert (0 ==
+                 pthread_mutex_destroy (&h->accounts_lock));
+  GNUNET_assert (0 ==
+                 pthread_mutex_destroy (&h->rpubs_lock));
+  for (uint64_t i = 0; i<h->ram_limit; i++)
+    GNUNET_free (h->transactions[i]);
+  GNUNET_free (h->transactions);
+  GNUNET_free (h->my_baseurl);
+  GNUNET_free (h->currency);
+  GNUNET_free (h->exchange_url);
+  GNUNET_free (h->hostname);
+  GNUNET_free (h);
+}
diff --git a/src/bank-lib/fakebank_tbi.c b/src/bank-lib/fakebank_tbi.c
new file mode 100644
index 00000000..27b930d9
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi.c
@@ -0,0 +1,126 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi.c
+ * @brief main entry point to the Taler Bank Integration (TBI) API 
implementation
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_tbi.h"
+#include "fakebank_tbi_get_withdrawal_operation.h"
+#include "fakebank_tbi_post_withdrawal_operation.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h,
+                          struct MHD_Connection *connection,
+                          const char *url,
+                          const char *method,
+                          const char *upload_data,
+                          size_t *upload_data_size,
+                          void **con_cls)
+{
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_HEAD))
+    method = MHD_HTTP_METHOD_GET;
+  if ( (0 == strcmp (url,
+                     "/config")) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_GET)) )
+  {
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string ("version",
+                               "0:0:0"),
+      GNUNET_JSON_pack_string ("currency",
+                               h->currency),
+      GNUNET_JSON_pack_string ("name",
+                               "taler-bank-integration"));
+  }
+  if ( (0 == strncmp (url,
+                      "/withdrawal-operation/",
+                      strlen ("/withdrawal-operation/"))) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_GET)) )
+  {
+    const char *wopid = &url[strlen ("/withdrawal-operation/")];
+    const char *lp_s
+      = MHD_lookup_connection_value (connection,
+                                     MHD_GET_ARGUMENT_KIND,
+                                     "long_poll_ms");
+    struct GNUNET_TIME_Relative lp = GNUNET_TIME_UNIT_ZERO;
+
+    if (NULL != lp_s)
+    {
+      unsigned long long d;
+      char dummy;
+
+      if (1 != sscanf (lp_s,
+                       "%llu%c",
+                       &d,
+                       &dummy))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                           "long_poll_ms");
+      }
+      lp = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+                                          d);
+    }
+    return TALER_FAKEBANK_tbi_get_withdrawal_operation_ (h,
+                                                         connection,
+                                                         wopid,
+                                                         lp,
+                                                         con_cls);
+
+  }
+  if ( (0 == strncmp (url,
+                      "/withdrawal-operation/",
+                      strlen ("/withdrawal-operation/"))) &&
+       (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_POST)) )
+  {
+    const char *wopid = &url[strlen ("/withdrawal-operation/")];
+
+    return TALER_FAKEBANK_tbi_post_withdrawal (h,
+                                               connection,
+                                               wopid,
+                                               upload_data,
+                                               upload_data_size,
+                                               con_cls);
+  }
+
+  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+                   method,
+                   url);
+  GNUNET_break_op (0);
+  return TALER_MHD_reply_with_error (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+    url);
+}
diff --git a/src/bank-lib/fakebank_tbi.h b/src/bank-lib/fakebank_tbi.h
new file mode 100644
index 00000000..ef9f35fa
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi.h
@@ -0,0 +1,54 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi.c
+ * @brief main entry point to the Taler Bank Integration (TBI) API 
implementation
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+#ifndef FAKEBANK_TBI_H
+#define FAKEBANK_TBI_H
+
+/**
+ * Handle incoming HTTP request to the bank integration API.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_main_ (struct TALER_FAKEBANK_Handle *h,
+                          struct MHD_Connection *connection,
+                          const char *url,
+                          const char *method,
+                          const char *upload_data,
+                          size_t *upload_data_size,
+                          void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c 
b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c
new file mode 100644
index 00000000..fba8c5de
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.c
@@ -0,0 +1,127 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi_get_withdrawal_operation.c
+ * @brief Implementation of the GET /withdrawal-operation/ request of the 
Taler Bank Integration API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_tbi_get_withdrawal_operation.h"
+
+/**
+ * Function called to clean up a withdraw context.
+ *
+ * @param cls a `struct WithdrawContext *`
+ */
+static void
+withdraw_cleanup (void *cls)
+{
+  struct WithdrawContext *wc = cls;
+
+  GNUNET_free (wc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_get_withdrawal_operation_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *wopid,
+  struct GNUNET_TIME_Relative lp,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  struct WithdrawContext *wc;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &withdraw_cleanup;
+    *con_cls = cc;
+    wc = GNUNET_new (struct WithdrawContext);
+    cc->ctx = wc;
+    wc->wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                          wopid);
+    if (NULL == wc->wo)
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                         wopid);
+    }
+    wc->timeout = GNUNET_TIME_relative_to_absolute (lp);
+  }
+  else
+  {
+    wc = cc->ctx;
+  }
+  if (GNUNET_TIME_absolute_is_past (wc->timeout) ||
+      h->in_shutdown ||
+      wc->wo->confirmation_done ||
+      wc->wo->aborted)
+  {
+    json_t *wt;
+
+    wt = json_array ();
+    GNUNET_assert (NULL != wt);
+    GNUNET_assert (0 ==
+                   json_array_append_new (wt,
+                                          json_string ("x-taler-bank")));
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_bool ("aborted",
+                             wc->wo->aborted),
+      GNUNET_JSON_pack_bool ("selection_done",
+                             wc->wo->selection_done),
+      GNUNET_JSON_pack_bool ("transfer_done",
+                             wc->wo->confirmation_done),
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_string ("suggested_exchange",
+                                 h->exchange_url)),
+      TALER_JSON_pack_amount ("amount",
+                              &wc->wo->amount),
+      GNUNET_JSON_pack_array_steal ("wire_types",
+                                    wt));
+  }
+
+  TALER_FAKEBANK_start_lp_ (h,
+                            connection,
+                            wc->wo->debit_account,
+                            GNUNET_TIME_absolute_get_remaining (wc->timeout),
+                            LP_WITHDRAW,
+                            wc->wo);
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return MHD_YES;
+}
diff --git a/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h 
b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h
new file mode 100644
index 00000000..b42e5a76
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_get_withdrawal_operation.h
@@ -0,0 +1,51 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi_get_withdrawal_operation.h
+ * @brief Implementation of the GET /withdrawal-operation/ request of the 
Taler Bank Integration API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H
+#define FAKEBANK_TBI_GET_WITHDRAWAL_OPERATION_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle GET /withdrawal-operation/{wopid} request.
+ *
+ * @param h the handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param lp how long is the long-polling timeout
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_get_withdrawal_operation_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *wopid,
+  struct GNUNET_TIME_Relative lp,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c 
b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c
new file mode 100644
index 00000000..3dbbb3c9
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.c
@@ -0,0 +1,221 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi_post_withdrawal_operation.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_tbi_post_withdrawal_operation.h"
+
+
+/**
+ * Execute POST /withdrawal-operation/ request.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param reserve_pub public key of the reserve
+ * @param exchange_payto_uri payto://-URI of the exchange
+ * @return MHD result code
+ */
+static MHD_RESULT
+do_post_withdrawal (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *wopid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *exchange_payto_uri)
+{
+  struct WithdrawalOperation *wo;
+  char *credit_name;
+  struct Account *credit_account;
+
+  GNUNET_assert (0 ==
+                 pthread_mutex_lock (&h->big_lock));
+  wo = TALER_FAKEBANK_lookup_withdrawal_operation_ (h,
+                                                    wopid);
+  if (NULL == wo)
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_BANK_TRANSACTION_NOT_FOUND,
+                                       wopid);
+  }
+  if ( (wo->selection_done) &&
+       (0 != GNUNET_memcmp (&wo->reserve_pub,
+                            reserve_pub)) )
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
+                                       "reserve public key changed");
+  }
+  {
+    /* check if reserve_pub is already in use */
+    const struct GNUNET_PeerIdentity *pid;
+
+    pid = (const struct GNUNET_PeerIdentity *) &wo->reserve_pub;
+    if (GNUNET_CONTAINER_multipeermap_contains (h->rpubs,
+                                                pid))
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_CONFLICT,
+                                         
TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+                                         NULL);
+    }
+  }
+  credit_name = TALER_xtalerbank_account_from_payto (exchange_payto_uri);
+  if (NULL == credit_name)
+  {
+    GNUNET_break_op (0);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+                                       NULL);
+  }
+  credit_account = TALER_FAKEBANK_lookup_account_ (h,
+                                                   credit_name,
+                                                   NULL);
+  if (NULL == credit_account)
+  {
+    MHD_RESULT res;
+
+    GNUNET_break_op (0);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    res = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_NOT_FOUND,
+                                      TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                      credit_name);
+    GNUNET_free (credit_name);
+    return res;
+  }
+  GNUNET_free (credit_name);
+  if ( (NULL != wo->exchange_account) &&
+       (credit_account != wo->exchange_account) )
+  {
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT,
+                                       "exchange account changed");
+  }
+  wo->exchange_account = credit_account;
+  wo->reserve_pub = *reserve_pub;
+  wo->selection_done = true;
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_bool ("transfer_done",
+                           wo->confirmation_done));
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbi_post_withdrawal (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *wopid,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  enum GNUNET_JSON_PostResult pr;
+  json_t *json;
+  MHD_RESULT res;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+    *con_cls = cc;
+  }
+  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+                                connection,
+                                &cc->ctx,
+                                upload_data,
+                                upload_data_size,
+                                &json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_CONTINUE:
+    return MHD_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_SUCCESS:
+    break;
+  }
+
+  {
+    struct TALER_ReservePublicKeyP reserve_pub;
+    const char *exchange_payto_url;
+    enum GNUNET_GenericReturnValue ret;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                   &reserve_pub),
+      GNUNET_JSON_spec_string ("selected_exchange",
+                               &exchange_payto_url),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        (ret = TALER_MHD_parse_json_data (connection,
+                                          json,
+                                          spec)))
+    {
+      GNUNET_break_op (0);
+      json_decref (json);
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+    }
+    res = do_post_withdrawal (h,
+                              connection,
+                              wopid,
+                              &reserve_pub,
+                              exchange_payto_url);
+  }
+  json_decref (json);
+  return res;
+}
diff --git a/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h 
b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h
new file mode 100644
index 00000000..8873bb5f
--- /dev/null
+++ b/src/bank-lib/fakebank_tbi_post_withdrawal_operation.h
@@ -0,0 +1,53 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbi_post_withdrawal_operation.c
+ * @brief Implementation of the Taler Bank Integration API for POAT 
/withdrawal-operation/ requests
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H
+#define FAKEBANK_TBI_POST_WITHDRAWAL_OPERATION_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle POST /withdrawal-operation/ request.
+ *
+ * @param h our fakebank handle
+ * @param connection the connection
+ * @param wopid the withdrawal operation identifier
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbi_post_withdrawal (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *wopid,
+  const void *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr.c b/src/bank-lib/fakebank_tbr.c
new file mode 100644
index 00000000..de96394e
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr.c
@@ -0,0 +1,74 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr.c
+ * @brief main entry point for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_tbr_get_history.h"
+#include "fakebank_tbr_get_root.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_main_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *url,
+  const char *method,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Fakebank - Anastasis API: serving URL `%s' for account `%s'\n",
+              url,
+              account);
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_GET))
+  {
+    if ( (0 == strcmp (url,
+                       "/history/incoming")) &&
+         (NULL != account) )
+      return TALER_FAKEBANK_tbr_get_history_incoming (h,
+                                                      connection,
+                                                      account,
+                                                      con_cls);
+    if (0 == strcmp (url,
+                     "/"))
+      return TALER_FAKEBANK_tbr_get_root (h,
+                                          connection);
+  }
+  /* Unexpected URL path, just close the connection. */
+  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+                   method,
+                   url);
+  GNUNET_break_op (0);
+  return TALER_MHD_reply_with_error (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+    url);
+}
diff --git a/src/bank-lib/fakebank_tbr.h b/src/bank-lib/fakebank_tbr.h
new file mode 100644
index 00000000..fb9bdfef
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr.h
@@ -0,0 +1,58 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr.h
+ * @brief main entry point for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+#ifndef FAKEBANK_TBR_H
+#define FAKEBANK_TBR_H
+
+/**
+ * Handle incoming HTTP request to the Taler Bank Revenue API.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param account which account should process the request
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_main_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *url,
+  const char *method,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr_get_history.c 
b/src/bank-lib/fakebank_tbr_get_history.c
new file mode 100644
index 00000000..3a7bfa45
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_history.c
@@ -0,0 +1,305 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr_get_history.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_parser.h"
+#include "fakebank_tbr_get_history.h"
+
+
+/**
+ * Function called to clean up a history context.
+ *
+ * @param cls a `struct HistoryContext *`
+ */
+static void
+history_cleanup (void *cls)
+{
+  struct HistoryContext *hc = cls;
+
+  GNUNET_free (hc->payto_uri);
+  json_decref (hc->history);
+  GNUNET_free (hc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_history_incoming (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  struct HistoryContext *hc;
+  const struct Transaction *pos;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &history_cleanup;
+    *con_cls = cc;
+    hc = GNUNET_new (struct HistoryContext);
+    cc->ctx = hc;
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling /history/incoming connection %p\n",
+                connection);
+    if (GNUNET_OK !=
+        (ret = TALER_FAKEBANK_common_parse_history_args (h,
+                                                         connection,
+                                                         &hc->ha)))
+    {
+      GNUNET_break_op (0);
+      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+    hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+                                              account,
+                                              NULL);
+    if (NULL == hc->acc)
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                         account);
+    }
+    /* FIXME: was simply: acc->payto_uri -- same!? */
+    GNUNET_asprintf (&hc->payto_uri,
+                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+                     account,
+                     hc->acc->receiver_name);
+    GNUNET_assert (0 == strcmp (hc->payto_uri,
+                                hc->acc->payto_uri));
+    hc->history = json_array ();
+    if (NULL == hc->history)
+    {
+      GNUNET_break (0);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_NO;
+    }
+    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+  }
+  else
+  {
+    hc = cc->ctx;
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+  }
+
+  if (! hc->ha.have_start)
+  {
+    pos = (0 > hc->ha.delta)
+          ? hc->acc->in_tail
+          : hc->acc->in_head;
+  }
+  else
+  {
+    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+    bool overflow;
+    uint64_t dir;
+    bool skip = true;
+
+    overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
+    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+    /* If account does not match, linear scan for
+       first matching account. */
+    while ( (! overflow) &&
+            (NULL != t) &&
+            (t->credit_account != hc->acc) )
+    {
+      skip = false;
+      t = h->transactions[(t->row_id + dir) % h->ram_limit];
+      if ( (NULL != t) &&
+           (t->row_id == hc->ha.start_idx) )
+        overflow = true; /* full circle, give up! */
+    }
+    if ( (NULL == t) ||
+         overflow)
+    {
+      /* FIXME: these conditions are unclear to me. */
+      if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
+          (0 < hc->ha.delta))
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        if (overflow)
+          return TALER_MHD_reply_with_ec (
+            connection,
+            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+            NULL);
+        goto finish;
+      }
+      if (h->in_shutdown)
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        goto finish;
+      }
+      TALER_FAKEBANK_start_lp_ (h,
+                                connection,
+                                hc->acc,
+                                GNUNET_TIME_absolute_get_remaining (
+                                  hc->timeout),
+                                LP_CREDIT,
+                                NULL);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_YES;
+    }
+    if (skip)
+    {
+      /* range from application is exclusive, skip the
+  matching entry */
+      if (0 > hc->ha.delta)
+        pos = t->prev_in;
+      else
+        pos = t->next_in;
+    }
+    else
+    {
+      pos = t;
+    }
+  }
+  if (NULL != pos)
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Returning %lld credit transactions starting (inclusive) from 
%llu\n",
+                (long long) hc->ha.delta,
+                (unsigned long long) pos->row_id);
+  while ( (0 != hc->ha.delta) &&
+          (NULL != pos) )
+  {
+    json_t *trans;
+    char *subject;
+
+    if (T_DEBIT != pos->type)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Unexpected CREDIT transaction #%llu for account `%s'\n",
+                  (unsigned long long) pos->row_id,
+                  account);
+      if (0 > hc->ha.delta)
+        pos = pos->prev_in;
+      if (0 < hc->ha.delta)
+        pos = pos->next_in;
+      continue;
+    }
+
+    {
+      char *wtids;
+
+      wtids = GNUNET_STRINGS_data_to_string_alloc (
+        &pos->subject.debit.wtid,
+        sizeof (pos->subject.debit.wtid));
+      GNUNET_asprintf (&subject,
+                       "%s %s",
+                       wtids,
+                       pos->subject.debit.exchange_base_url);
+      GNUNET_free (wtids);
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Found transaction over %s with subject %s\n",
+                TALER_amount2s (&pos->amount),
+                subject);
+    trans = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("type",
+                               "RESERVE"),
+      GNUNET_JSON_pack_uint64 ("row_id",
+                               pos->row_id),
+      GNUNET_JSON_pack_timestamp ("date",
+                                  pos->date),
+      TALER_JSON_pack_amount ("amount",
+                              &pos->amount),
+      GNUNET_JSON_pack_string ("debit_account",
+                               pos->debit_account->payto_uri),
+      GNUNET_JSON_pack_string ("subject",
+                               subject));
+    GNUNET_free (subject);
+    GNUNET_assert (NULL != trans);
+    GNUNET_assert (0 ==
+                   json_array_append_new (hc->history,
+                                          trans));
+    if (hc->ha.delta > 0)
+      hc->ha.delta--;
+    else
+      hc->ha.delta++;
+    if (0 > hc->ha.delta)
+      pos = pos->prev_in;
+    if (0 < hc->ha.delta)
+      pos = pos->next_in;
+  }
+  if ( (0 == json_array_size (hc->history)) &&
+       (! h->in_shutdown) &&
+       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+       (0 < hc->ha.delta))
+  {
+    TALER_FAKEBANK_start_lp_ (h,
+                              connection,
+                              hc->acc,
+                              GNUNET_TIME_absolute_get_remaining (hc->timeout),
+                              LP_CREDIT,
+                              NULL);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return MHD_YES;
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+finish:
+  if (0 == json_array_size (hc->history))
+  {
+    GNUNET_break (h->in_shutdown ||
+                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  {
+    json_t *h = hc->history;
+
+    hc->history = NULL;
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string (
+        "credit_account",
+        hc->payto_uri),
+      GNUNET_JSON_pack_array_steal (
+        "incoming_transactions",
+        h));
+  }
+}
diff --git a/src/bank-lib/fakebank_tbr_get_history.h 
b/src/bank-lib/fakebank_tbr_get_history.h
new file mode 100644
index 00000000..99170ab7
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_history.h
@@ -0,0 +1,52 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr_get_history.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBR_GET_HISTORY_H
+#define FAKEBANK_TBR_GET_HISTORY_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /history/incoming
+ * of the Anastasis API.  This one can return transactions
+ * created by debits from the exchange!
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request (NULL or &special_ptr)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_history_incoming (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_tbr_get_root.c 
b/src/bank-lib/fakebank_tbr_get_root.c
new file mode 100644
index 00000000..6e518d66
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_root.c
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr_get_root.c
+ * @brief return the main "/" page for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h,
+                             struct MHD_Connection *connection)
+{
+  MHD_RESULT ret;
+  struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank (Bank Revenue API here)!"
+
+  (void) h;
+  resp = MHD_create_response_from_buffer (
+    strlen (HELLOMSG),
+    HELLOMSG,
+    MHD_RESPMEM_MUST_COPY);
+  ret = MHD_queue_response (connection,
+                            MHD_HTTP_OK,
+                            resp);
+  MHD_destroy_response (resp);
+  return ret;
+}
diff --git a/src/bank-lib/fakebank_tbr_get_root.h 
b/src/bank-lib/fakebank_tbr_get_root.h
new file mode 100644
index 00000000..eda8060b
--- /dev/null
+++ b/src/bank-lib/fakebank_tbr_get_root.h
@@ -0,0 +1,45 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_tbr_get_root.h
+ * @brief return the main "/" page for the Taler Bank Revenue API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TBR_GET_ROOT_H
+#define FAKEBANK_TBR_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_tbr_get_root (struct TALER_FAKEBANK_Handle *h,
+                             struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg.c b/src/bank-lib/fakebank_twg.c
new file mode 100644
index 00000000..f362dd4a
--- /dev/null
+++ b/src/bank-lib/fakebank_twg.c
@@ -0,0 +1,106 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg.c
+ * @brief main entry point for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_twg.h"
+#include "fakebank_twg_admin_add_incoming.h"
+#include "fakebank_twg_get_root.h"
+#include "fakebank_twg_history.h"
+#include "fakebank_twg_transfer.h"
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_main_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *url,
+  const char *method,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Fakebank, serving URL `%s' for account `%s'\n",
+              url,
+              account);
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_GET))
+  {
+    if ( (0 == strcmp (url,
+                       "/history/incoming")) &&
+         (NULL != account) )
+      return TALER_FAKEBANK_twg_get_credit_history_ (h,
+                                                     connection,
+                                                     account,
+                                                     con_cls);
+    if ( (0 == strcmp (url,
+                       "/history/outgoing")) &&
+         (NULL != account) )
+      return TALER_FAKEBANK_twg_get_debit_history_ (h,
+                                                    connection,
+                                                    account,
+                                                    con_cls);
+    if (0 == strcmp (url,
+                     "/"))
+      return TALER_FAKEBANK_twg_get_root_ (h,
+                                           connection);
+  }
+  else if (0 == strcasecmp (method,
+                            MHD_HTTP_METHOD_POST))
+  {
+    if ( (0 == strcmp (url,
+                       "/admin/add-incoming")) &&
+         (NULL != account) )
+      return TALER_FAKEBANK_twg_admin_add_incoming_ (h,
+                                                     connection,
+                                                     account,
+                                                     upload_data,
+                                                     upload_data_size,
+                                                     con_cls);
+    if ( (0 == strcmp (url,
+                       "/transfer")) &&
+         (NULL != account) )
+      return TALER_FAKEBANK_handle_transfer_ (h,
+                                              connection,
+                                              account,
+                                              upload_data,
+                                              upload_data_size,
+                                              con_cls);
+  }
+  /* Unexpected URL path, just close the connection. */
+  TALER_LOG_ERROR ("Breaking URL: %s %s\n",
+                   method,
+                   url);
+  GNUNET_break_op (0);
+  return TALER_MHD_reply_with_error (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+    url);
+}
diff --git a/src/bank-lib/fakebank_twg.h b/src/bank-lib/fakebank_twg.h
new file mode 100644
index 00000000..de808a21
--- /dev/null
+++ b/src/bank-lib/fakebank_twg.h
@@ -0,0 +1,56 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg.h
+ * @brief main entry point for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_H
+#define FAKEBANK_TWG_H
+
+#include "taler_fakebank_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Handle incoming HTTP request to the Taler Wire Gateway
+ * API.
+ *
+ * @param h our handle
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param account which account should process the request
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_main_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *url,
+  const char *method,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.c 
b/src/bank-lib/fakebank_twg_admin_add_incoming.c
new file mode 100644
index 00000000..2db4f1fe
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_admin_add_incoming.c
@@ -0,0 +1,160 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_admin_add_incoming.c
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_make_admin_transfer.h"
+#include "fakebank_twg_admin_add_incoming.h"
+
+MHD_RESULT
+TALER_FAKEBANK_twg_admin_add_incoming_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  enum GNUNET_JSON_PostResult pr;
+  json_t *json;
+  uint64_t row_id;
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+    *con_cls = cc;
+  }
+  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+                                connection,
+                                &cc->ctx,
+                                upload_data,
+                                upload_data_size,
+                                &json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_CONTINUE:
+    return MHD_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_SUCCESS:
+    break;
+  }
+  {
+    const char *debit_account;
+    struct TALER_Amount amount;
+    struct TALER_ReservePublicKeyP reserve_pub;
+    char *debit;
+    enum GNUNET_GenericReturnValue ret;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                   &reserve_pub),
+      GNUNET_JSON_spec_string ("debit_account",
+                               &debit_account),
+      TALER_JSON_spec_amount ("amount",
+                              h->currency,
+                              &amount),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        (ret = TALER_MHD_parse_json_data (connection,
+                                          json,
+                                          spec)))
+    {
+      GNUNET_break_op (0);
+      json_decref (json);
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+    }
+    if (0 != strcasecmp (amount.currency,
+                         h->currency))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Currency `%s' does not match our configuration\n",
+                  amount.currency);
+      json_decref (json);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_CONFLICT,
+        TALER_EC_GENERIC_CURRENCY_MISMATCH,
+        NULL);
+    }
+    debit = TALER_xtalerbank_account_from_payto (debit_account);
+    if (NULL == debit)
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+        debit_account);
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Receiving incoming wire transfer: %s->%s, subject: %s, 
amount: %s\n",
+                debit,
+                account,
+                TALER_B2S (&reserve_pub),
+                TALER_amount2s (&amount));
+    ret = TALER_FAKEBANK_make_admin_transfer_ (h,
+                                               debit,
+                                               account,
+                                               &amount,
+                                               &reserve_pub,
+                                               &row_id,
+                                               &timestamp);
+    GNUNET_free (debit);
+    if (GNUNET_OK != ret)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Reserve public key not unique\n");
+      json_decref (json);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_CONFLICT,
+        TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
+        NULL);
+    }
+  }
+  json_decref (json);
+
+  /* Finally build response object */
+  return TALER_MHD_REPLY_JSON_PACK (connection,
+                                    MHD_HTTP_OK,
+                                    GNUNET_JSON_pack_uint64 ("row_id",
+                                                             row_id),
+                                    GNUNET_JSON_pack_timestamp ("timestamp",
+                                                                timestamp));
+}
diff --git a/src/bank-lib/fakebank_twg_admin_add_incoming.h 
b/src/bank-lib/fakebank_twg_admin_add_incoming.h
new file mode 100644
index 00000000..220a10fc
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_admin_add_incoming.h
@@ -0,0 +1,52 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_admin_add_incoming.h
+ * @brief library that fakes being a Taler bank for testcases
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_ADMIN_ADD_INCOMING_H
+#define FAKEBANK_TWG_ADMIN_ADD_INCOMING_H
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+/**
+ * Handle incoming HTTP request for /admin/add/incoming.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account into which to deposit the funds (credit)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_admin_add_incoming_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_get_root.c 
b/src/bank-lib/fakebank_twg_get_root.c
new file mode 100644
index 00000000..09589890
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_get_root.c
@@ -0,0 +1,58 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_get_root.c
+ * @brief return the "/" page for the taler wire gateway
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_root_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection)
+{
+  MHD_RESULT ret;
+  struct MHD_Response *resp;
+#define HELLOMSG "Hello, Fakebank (Taler Wire Gateway)!"
+
+  (void) h;
+  resp = MHD_create_response_from_buffer (
+    strlen (HELLOMSG),
+    HELLOMSG,
+    MHD_RESPMEM_MUST_COPY);
+  ret = MHD_queue_response (connection,
+                            MHD_HTTP_OK,
+                            resp);
+  MHD_destroy_response (resp);
+  return ret;
+}
diff --git a/src/bank-lib/fakebank_twg_get_root.h 
b/src/bank-lib/fakebank_twg_get_root.h
new file mode 100644
index 00000000..8bbcf419
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_get_root.h
@@ -0,0 +1,46 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_get_root.h
+ * @brief return the "/" page for the taler wire gateway
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_GET_ROOT_H
+#define FAKEBANK_TWG_GET_ROOT_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for "/" (home page).
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_root_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection);
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_history.c 
b/src/bank-lib/fakebank_twg_history.c
new file mode 100644
index 00000000..06228507
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_history.c
@@ -0,0 +1,537 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_history.c
+ * @brief routines to return account histories for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include <pthread.h>
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_lookup.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_common_parser.h"
+
+/**
+ * Function called to clean up a history context.
+ *
+ * @param cls a `struct HistoryContext *`
+ */
+static void
+history_cleanup (void *cls)
+{
+  struct HistoryContext *hc = cls;
+
+  GNUNET_free (hc->payto_uri);
+  json_decref (hc->history);
+  GNUNET_free (hc);
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_get_debit_history_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  struct HistoryContext *hc;
+  struct Transaction *pos;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &history_cleanup;
+    *con_cls = cc;
+    hc = GNUNET_new (struct HistoryContext);
+    cc->ctx = hc;
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling /history/outgoing connection %p\n",
+                connection);
+    if (GNUNET_OK !=
+        (ret = TALER_FAKEBANK_common_parse_history_args (h,
+                                                         connection,
+                                                         &hc->ha)))
+    {
+      GNUNET_break_op (0);
+      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+    hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+                                              account,
+                                              NULL);
+    if (NULL == hc->acc)
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                         account);
+    }
+    GNUNET_asprintf (&hc->payto_uri,
+                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+                     account,
+                     hc->acc->receiver_name);
+    /* New invariant: */
+    GNUNET_assert (0 == strcmp (hc->payto_uri,
+                                hc->acc->payto_uri));
+    hc->history = json_array ();
+    if (NULL == hc->history)
+    {
+      GNUNET_break (0);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_NO;
+    }
+    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+  }
+  else
+  {
+    hc = cc->ctx;
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+  }
+
+  if (! hc->ha.have_start)
+  {
+    pos = (0 > hc->ha.delta)
+      ? hc->acc->out_tail
+      : hc->acc->out_head;
+  }
+  else
+  {
+    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+    bool overflow;
+    uint64_t dir;
+    bool skip = true;
+
+    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+    overflow = (t->row_id != hc->ha.start_idx);
+    /* If account does not match, linear scan for
+       first matching account. */
+    while ( (! overflow) &&
+            (NULL != t) &&
+            (t->debit_account != hc->acc) )
+    {
+      skip = false;
+      t = h->transactions[(t->row_id + dir) % h->ram_limit];
+      if ( (NULL != t) &&
+           (t->row_id == hc->ha.start_idx) )
+        overflow = true; /* full circle, give up! */
+    }
+    if ( (NULL == t) ||
+         overflow)
+    {
+      /* FIXME: these conditions are unclear to me. */
+      if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) &&
+           (0 < hc->ha.delta))
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        if (overflow)
+        {
+          return TALER_MHD_reply_with_ec (
+            connection,
+            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+            NULL);
+        }
+        goto finish;
+      }
+      if (h->in_shutdown)
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        goto finish;
+      }
+      TALER_FAKEBANK_start_lp_ (h,
+                                connection,
+                                hc->acc,
+                                GNUNET_TIME_absolute_get_remaining (
+                                  hc->timeout),
+                                LP_DEBIT,
+                                NULL);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_YES;
+    }
+    if (t->debit_account != hc->acc)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid start specified, transaction %llu not with account 
%s!\n",
+                  (unsigned long long) hc->ha.start_idx,
+                  account);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_NO;
+    }
+    if (skip)
+    {
+      /* range is exclusive, skip the matching entry */
+      if (0 > hc->ha.delta)
+        pos = t->prev_out;
+      else
+        pos = t->next_out;
+    }
+    else
+    {
+      pos = t;
+    }
+  }
+  if (NULL != pos)
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Returning %lld debit transactions starting (inclusive) from 
%llu\n",
+                (long long) hc->ha.delta,
+                (unsigned long long) pos->row_id);
+  while ( (0 != hc->ha.delta) &&
+          (NULL != pos) )
+  {
+    json_t *trans;
+    char *credit_payto;
+
+    if (T_DEBIT != pos->type)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Unexpected CREDIT transaction #%llu for account `%s'\n",
+                  (unsigned long long) pos->row_id,
+                  account);
+      if (0 > hc->ha.delta)
+        pos = pos->prev_in;
+      if (0 < hc->ha.delta)
+        pos = pos->next_in;
+      continue;
+    }
+    GNUNET_asprintf (&credit_payto,
+                     "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+                     pos->credit_account->account_name,
+                     pos->credit_account->receiver_name);
+
+    trans = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_uint64 ("row_id",
+                               pos->row_id),
+      GNUNET_JSON_pack_timestamp ("date",
+                                  pos->date),
+      TALER_JSON_pack_amount ("amount",
+                              &pos->amount),
+      GNUNET_JSON_pack_string ("credit_account",
+                               credit_payto),
+      GNUNET_JSON_pack_string ("exchange_base_url",
+                               pos->subject.debit.exchange_base_url),
+      GNUNET_JSON_pack_data_auto ("wtid",
+                                  &pos->subject.debit.wtid));
+    GNUNET_assert (NULL != trans);
+    GNUNET_free (credit_payto);
+    GNUNET_assert (0 ==
+                   json_array_append_new (hc->history,
+                                          trans));
+    if (hc->ha.delta > 0)
+      hc->ha.delta--;
+    else
+      hc->ha.delta++;
+    if (0 > hc->ha.delta)
+      pos = pos->prev_out;
+    if (0 < hc->ha.delta)
+      pos = pos->next_out;
+  }
+  if ( (0 == json_array_size (hc->history)) &&
+       (! h->in_shutdown) &&
+       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+       (0 < hc->ha.delta))
+  {
+    TALER_FAKEBANK_start_lp_ (h,
+                              connection,
+                              hc->acc,
+                              GNUNET_TIME_absolute_get_remaining (hc->timeout),
+                              LP_DEBIT,
+                              NULL);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return MHD_YES;
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+finish:
+  if (0 == json_array_size (hc->history))
+  {
+    GNUNET_break (h->in_shutdown ||
+                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  {
+    json_t *h = hc->history;
+
+    hc->history = NULL;
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string (
+        "debit_account",
+        hc->payto_uri),
+      GNUNET_JSON_pack_array_steal (
+        "outgoing_transactions",
+        h));
+  }
+}
+
+
+MHD_RESULT
+TALER_FAKEBANK_twg_get_credit_history_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  struct HistoryContext *hc;
+  const struct Transaction *pos;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &history_cleanup;
+    *con_cls = cc;
+    hc = GNUNET_new (struct HistoryContext);
+    cc->ctx = hc;
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling /history/incoming connection %p\n",
+                connection);
+    if (GNUNET_OK !=
+        (ret = TALER_FAKEBANK_common_parse_history_args (h,
+                                                         connection,
+                                                         &hc->ha)))
+    {
+      GNUNET_break_op (0);
+      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+    }
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+    hc->acc = TALER_FAKEBANK_lookup_account_ (h,
+                                              account,
+                                              NULL);
+    if (NULL == hc->acc)
+    {
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_BANK_UNKNOWN_ACCOUNT,
+                                         account);
+    }
+    /* FIXME: was simply: acc->payto_uri -- same!? */
+    GNUNET_asprintf (&hc->payto_uri,
+                     "payto://x-taler-bank/%s/%s?receiver-name=%s",
+                     h->hostname,
+                     account,
+                     hc->acc->receiver_name);
+    GNUNET_assert (0 == strcmp (hc->payto_uri,
+                                hc->acc->payto_uri));
+    hc->history = json_array ();
+    if (NULL == hc->history)
+    {
+      GNUNET_break (0);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_NO;
+    }
+    hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout);
+  }
+  else
+  {
+    hc = cc->ctx;
+    GNUNET_assert (0 ==
+                   pthread_mutex_lock (&h->big_lock));
+  }
+
+  if (! hc->ha.have_start)
+  {
+    pos = (0 > hc->ha.delta)
+          ? hc->acc->in_tail
+          : hc->acc->in_head;
+  }
+  else
+  {
+    struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit];
+    bool overflow;
+    uint64_t dir;
+    bool skip = true;
+
+    overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) );
+    dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1;
+    /* If account does not match, linear scan for
+       first matching account. */
+    while ( (! overflow) &&
+            (NULL != t) &&
+            (t->credit_account != hc->acc) )
+    {
+      skip = false;
+      t = h->transactions[(t->row_id + dir) % h->ram_limit];
+      if ( (NULL != t) &&
+           (t->row_id == hc->ha.start_idx) )
+        overflow = true; /* full circle, give up! */
+    }
+    if ( (NULL == t) ||
+         overflow)
+    {
+      /* FIXME: these conditions are unclear to me. */
+      if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) &&
+          (0 < hc->ha.delta))
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        if (overflow)
+          return TALER_MHD_reply_with_ec (
+            connection,
+            TALER_EC_BANK_ANCIENT_TRANSACTION_GONE,
+            NULL);
+        goto finish;
+      }
+      if (h->in_shutdown)
+      {
+        GNUNET_assert (0 ==
+                       pthread_mutex_unlock (&h->big_lock));
+        goto finish;
+      }
+      TALER_FAKEBANK_start_lp_ (h,
+                                connection,
+                                hc->acc,
+                                GNUNET_TIME_absolute_get_remaining (
+                                  hc->timeout),
+                                LP_CREDIT,
+                                NULL);
+      GNUNET_assert (0 ==
+                     pthread_mutex_unlock (&h->big_lock));
+      return MHD_YES;
+    }
+    if (skip)
+    {
+      /* range from application is exclusive, skip the
+  matching entry */
+      if (0 > hc->ha.delta)
+        pos = t->prev_in;
+      else
+        pos = t->next_in;
+    }
+    else
+    {
+      pos = t;
+    }
+  }
+  if (NULL != pos)
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Returning %lld credit transactions starting (inclusive) from 
%llu\n",
+                (long long) hc->ha.delta,
+                (unsigned long long) pos->row_id);
+  while ( (0 != hc->ha.delta) &&
+          (NULL != pos) )
+  {
+    json_t *trans;
+
+    if (T_CREDIT != pos->type)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Unexpected DEBIT transaction #%llu for account `%s'\n",
+                  (unsigned long long) pos->row_id,
+                  account);
+      if (0 > hc->ha.delta)
+        pos = pos->prev_in;
+      if (0 < hc->ha.delta)
+        pos = pos->next_in;
+      continue;
+    }
+    trans = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_uint64 ("row_id",
+                               pos->row_id),
+      GNUNET_JSON_pack_timestamp ("date",
+                                  pos->date),
+      TALER_JSON_pack_amount ("amount",
+                              &pos->amount),
+      GNUNET_JSON_pack_string ("debit_account",
+                               pos->debit_account->payto_uri),
+      GNUNET_JSON_pack_data_auto ("reserve_pub",
+                                  &pos->subject.credit.reserve_pub));
+    GNUNET_assert (NULL != trans);
+    GNUNET_assert (0 ==
+                   json_array_append_new (hc->history,
+                                          trans));
+    if (hc->ha.delta > 0)
+      hc->ha.delta--;
+    else
+      hc->ha.delta++;
+    if (0 > hc->ha.delta)
+      pos = pos->prev_in;
+    if (0 < hc->ha.delta)
+      pos = pos->next_in;
+  }
+  if ( (0 == json_array_size (hc->history)) &&
+       (! h->in_shutdown) &&
+       (GNUNET_TIME_absolute_is_future (hc->timeout)) &&
+       (0 < hc->ha.delta))
+  {
+    TALER_FAKEBANK_start_lp_ (h,
+                              connection,
+                              hc->acc,
+                              GNUNET_TIME_absolute_get_remaining (hc->timeout),
+                              LP_CREDIT,
+                              NULL);
+    GNUNET_assert (0 ==
+                   pthread_mutex_unlock (&h->big_lock));
+    return MHD_YES;
+  }
+  GNUNET_assert (0 ==
+                 pthread_mutex_unlock (&h->big_lock));
+finish:
+  if (0 == json_array_size (hc->history))
+  {
+    GNUNET_break (h->in_shutdown ||
+                  (! GNUNET_TIME_absolute_is_future (hc->timeout)));
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  {
+    json_t *h = hc->history;
+
+    hc->history = NULL;
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      GNUNET_JSON_pack_string (
+        "credit_account",
+        hc->payto_uri),
+      GNUNET_JSON_pack_array_steal (
+        "incoming_transactions",
+        h));
+  }
+}
diff --git a/src/bank-lib/fakebank_twg_history.h 
b/src/bank-lib/fakebank_twg_history.h
new file mode 100644
index 00000000..c49678ae
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_history.h
@@ -0,0 +1,67 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_history.c
+ * @brief routines to return account histories for the Taler Wire Gateway API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_HISTORY_H
+#define FAKEBANK_TWG_HISTORY_H
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /history/outgoing
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_debit_history_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls);
+
+
+/**
+ * Handle incoming HTTP request for /history/incoming
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account which account the request is about
+ * @param con_cls closure for request (NULL or &special_ptr)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_twg_get_credit_history_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  void **con_cls);
+
+
+#endif
diff --git a/src/bank-lib/fakebank_twg_transfer.c 
b/src/bank-lib/fakebank_twg_transfer.c
new file mode 100644
index 00000000..fef314a5
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_transfer.c
@@ -0,0 +1,178 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_transfer.c
+ * @brief implementation of the Taler Wire Gateway "/transfer" endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+#include "fakebank_common_transact.h"
+#include "fakebank_twg_transfer.h"
+
+
+/**
+ * Handle incoming HTTP request for /transfer.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account making the transfer
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_handle_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls)
+{
+  struct ConnectionContext *cc = *con_cls;
+  enum GNUNET_JSON_PostResult pr;
+  json_t *json;
+  uint64_t row_id;
+  struct GNUNET_TIME_Timestamp ts;
+
+  if (NULL == cc)
+  {
+    cc = GNUNET_new (struct ConnectionContext);
+    cc->ctx_cleaner = &GNUNET_JSON_post_parser_cleanup;
+    *con_cls = cc;
+  }
+  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
+                                connection,
+                                &cc->ctx,
+                                upload_data,
+                                upload_data_size,
+                                &json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_CONTINUE:
+    return MHD_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (0);
+    return MHD_NO;
+  case GNUNET_JSON_PR_SUCCESS:
+    break;
+  }
+  {
+    struct GNUNET_HashCode uuid;
+    struct TALER_WireTransferIdentifierRawP wtid;
+    const char *credit_account;
+    char *credit;
+    const char *base_url;
+    struct TALER_Amount amount;
+    enum GNUNET_GenericReturnValue ret;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("request_uid",
+                                   &uuid),
+      TALER_JSON_spec_amount ("amount",
+                              h->currency,
+                              &amount),
+      GNUNET_JSON_spec_string ("exchange_base_url",
+                               &base_url),
+      GNUNET_JSON_spec_fixed_auto ("wtid",
+                                   &wtid),
+      GNUNET_JSON_spec_string ("credit_account",
+                               &credit_account),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        (ret = TALER_MHD_parse_json_data (connection,
+                                          json,
+                                          spec)))
+    {
+      GNUNET_break_op (0);
+      json_decref (json);
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+    }
+    {
+      enum GNUNET_GenericReturnValue ret;
+
+      credit = TALER_xtalerbank_account_from_payto (credit_account);
+      if (NULL == credit)
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_BAD_REQUEST,
+          TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+          credit_account);
+      }
+      ret = TALER_FAKEBANK_make_transfer_ (h,
+                                           account,
+                                           credit,
+                                           &amount,
+                                           &wtid,
+                                           base_url,
+                                           &uuid,
+                                           &row_id,
+                                           &ts);
+      if (GNUNET_OK != ret)
+      {
+        MHD_RESULT res;
+        char *uids;
+
+        GNUNET_break (0);
+        uids = GNUNET_STRINGS_data_to_string_alloc (&uuid,
+                                                    sizeof (uuid));
+        json_decref (json);
+        res = TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_CONFLICT,
+                                          
TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED,
+                                          uids);
+        GNUNET_free (uids);
+        return res;
+      }
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Receiving incoming wire transfer: %s->%s, subject: %s, 
amount: %s, from %s\n",
+                  account,
+                  credit,
+                  TALER_B2S (&wtid),
+                  TALER_amount2s (&amount),
+                  base_url);
+      GNUNET_free (credit);
+    }
+  }
+  json_decref (json);
+
+  /* Finally build response object */
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_uint64 ("row_id",
+                             row_id),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                ts));
+}
diff --git a/src/bank-lib/fakebank_twg_transfer.h 
b/src/bank-lib/fakebank_twg_transfer.h
new file mode 100644
index 00000000..2019565b
--- /dev/null
+++ b/src/bank-lib/fakebank_twg_transfer.h
@@ -0,0 +1,55 @@
+/*
+  This file is part of TALER
+  (C) 2016-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 bank-lib/fakebank_twg_transfer.h
+ * @brief implementation of the Taler Wire Gateway "/transfer" endpoint
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#ifndef FAKEBANK_TWG_TRANSFER_H
+#define FAKEBANK_TWG_TRANSFER_H
+
+
+#include "taler_fakebank_lib.h"
+#include "taler_bank_service.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "fakebank.h"
+
+
+/**
+ * Handle incoming HTTP request for /transfer.
+ *
+ * @param h the fakebank handle
+ * @param connection the connection
+ * @param account account making the transfer
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct ConnectionContext *`)
+ * @return MHD result code
+ */
+MHD_RESULT
+TALER_FAKEBANK_handle_transfer_ (
+  struct TALER_FAKEBANK_Handle *h,
+  struct MHD_Connection *connection,
+  const char *account,
+  const char *upload_data,
+  size_t *upload_data_size,
+  void **con_cls);
+
+#endif

-- 
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]