gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: work on #9039


From: gnunet
Subject: [taler-exchange] branch master updated: work on #9039
Date: Sun, 01 Sep 2024 15:33:02 +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 39e51a95b work on #9039
39e51a95b is described below

commit 39e51a95be1539467efc146bf72e88631414ef9d
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Sep 1 15:32:58 2024 +0200

    work on #9039
---
 src/exchange/taler-exchange-httpd.c                |   1 +
 src/exchange/taler-exchange-httpd_batch-deposit.c  | 896 +++++++++++++++------
 src/exchange/taler-exchange-httpd_batch-deposit.h  |   8 +
 src/exchange/taler-exchange-httpd_config.h         |   2 +-
 src/exchangedb/Makefile.am                         |   1 +
 ...c => pg_select_deposit_amounts_for_kyc_check.c} |  34 +-
 .../pg_select_deposit_amounts_for_kyc_check.h      |  51 ++
 .../pg_select_merge_amounts_for_kyc_check.c        |   1 -
 src/exchangedb/plugin_exchangedb_postgres.c        |   3 +
 src/include/taler_exchangedb_plugin.h              |  22 +
 src/kyclogic/kyclogic_api.c                        |   6 +
 src/lib/exchange_api_handle.c                      |  21 +-
 12 files changed, 781 insertions(+), 265 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index 36c567ebd..52e8ef6af 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -2585,6 +2585,7 @@ do_shutdown (void *cls)
 
   my_mhd = TALER_MHD_daemon_stop ();
   TEH_resume_keys_requests (true);
+  TEH_batch_deposit_cleanup ();
   TEH_age_withdraw_cleanup ();
   TEH_batch_withdraw_cleanup ();
   TEH_reserves_close_cleanup ();
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c 
b/src/exchange/taler-exchange-httpd_batch-deposit.c
index 84f27dd94..b18acd8aa 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.c
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2023 Taler Systems SA
+  Copyright (C) 2014-2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
@@ -31,6 +31,7 @@
 #include "taler_extensions_policy.h"
 #include "taler_json_lib.h"
 #include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
 #include "taler-exchange-httpd_batch-deposit.h"
 #include "taler-exchange-httpd_responses.h"
 #include "taler_exchangedb_lib.h"
@@ -44,34 +45,34 @@ struct BatchDepositContext
 {
 
   /**
-   * Array with the individual coin deposit fees.
+   * Kept in a DLL.
    */
-  struct TALER_Amount *deposit_fees;
+  struct BatchDepositContext *next;
 
   /**
-   * Our timestamp (when we received the request).
-   * Possibly updated by the transaction if the
-   * request is idempotent (was repeated).
+   * Kept in a DLL.
    */
-  struct GNUNET_TIME_Timestamp exchange_timestamp;
+  struct BatchDepositContext *prev;
 
   /**
-   * Details about the batch deposit operation.
+   * The request we are working on.
    */
-  struct TALER_EXCHANGEDB_BatchDeposit bd;
+  struct TEH_RequestContext *rc;
 
+  /**
+   * Handle for the legitimization check.
+   */
+  struct TEH_LegitimizationCheckHandle *lch;
 
   /**
-   * Total amount that is accumulated with this deposit,
-   * without fee.
+   * Array with the individual coin deposit fees.
    */
-  struct TALER_Amount accumulated_total_without_fee;
+  struct TALER_Amount *deposit_fees;
 
   /**
-   * True, if no policy was present in the request. Then
-   * @e policy_json is NULL and @e h_policy will be all zero.
+   * Information about deposited coins.
    */
-  bool has_no_policy;
+  struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
 
   /**
    * Additional details for policy extension relevant for this
@@ -79,12 +80,16 @@ struct BatchDepositContext
    */
   json_t *policy_json;
 
+
   /**
-   * If @e policy_json was present, the corresponding policy extension
-   * calculates these details.  These will be persisted in the policy_details
-   * table.
+   * Response to return, if set.
    */
-  struct TALER_PolicyDetails policy_details;
+  struct MHD_Response *response;
+
+  /**
+   * KYC status of the reserve used for the operation.
+   */
+  struct TALER_EXCHANGEDB_KycStatus kyc;
 
   /**
    * Hash over @e policy_details, might be all zero
@@ -102,9 +107,107 @@ struct BatchDepositContext
    */
   uint64_t policy_details_serial_id;
 
+  /**
+   * Our timestamp (when we received the request).
+   * Possibly updated by the transaction if the
+   * request is idempotent (was repeated).
+   */
+  struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+  /**
+   * Total amount that is accumulated with this deposit,
+   * without fee.
+   */
+  struct TALER_Amount accumulated_total_without_fee;
+
+  /**
+   * Details about the batch deposit operation.
+   */
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
+
+  /**
+   * If @e policy_json was present, the corresponding policy extension
+   * calculates these details.  These will be persisted in the policy_details
+   * table.
+   */
+  struct TALER_PolicyDetails policy_details;
+
+  /**
+   * HTTP status to return with @e response, or 0.
+   */
+  unsigned int http_status;
+
+  /**
+   * Our current state in the state machine.
+   */
+  enum
+  {
+    BDC_PHASE_INIT = 0,
+    BDC_PHASE_PARSE = 1,
+    BDC_PHASE_POLICY = 2,
+    BDC_PHASE_KYC = 3,
+    BDC_PHASE_TRANSACT = 4,
+    BDC_PHASE_REPLY_SUCCESS = 5,
+    BDC_PHASE_SUSPENDED,
+    BDC_PHASE_CHECK_KYC_RESULT,
+    BDC_PHASE_GENERATE_REPLY_FAILURE,
+    BDC_PHASE_RETURN_YES,
+    BDC_PHASE_RETURN_NO,
+  } phase;
+
+  /**
+   * True, if no policy was present in the request. Then
+   * @e policy_json is NULL and @e h_policy will be all zero.
+   */
+  bool has_no_policy;
 };
 
 
+/**
+ * Head of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_head;
+
+/**
+ * Tail of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_tail;
+
+
+void
+TEH_batch_deposit_cleanup ()
+{
+  struct BatchDepositContext *bdc;
+
+  while (NULL != (bdc = bdc_head))
+  {
+    GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase);
+    bdc->phase = BDC_PHASE_RETURN_NO;
+    MHD_resume_connection (bdc->rc->connection);
+    GNUNET_CONTAINER_DLL_remove (bdc_head,
+                                 bdc_tail,
+                                 bdc);
+  }
+}
+
+
+/**
+ * Terminate the main loop by returning the final
+ * result.
+ *
+ * @param[in,out] bdc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct BatchDepositContext *bdc,
+             MHD_RESULT mres)
+{
+  bdc->phase = (MHD_YES == mres)
+    ? BDC_PHASE_RETURN_YES
+    : BDC_PHASE_RETURN_NO;
+}
+
+
 /**
  * Send confirmation of batch deposit success to client.  This function will
  * create a signed message affirming the given information and return it to
@@ -112,53 +215,53 @@ struct BatchDepositContext
  * (residual) value for the specified transaction and that it will execute the
  * requested batch deposit operation with the given wiring details.
  *
- * @param connection connection to the client
- * @param dc information about the batch deposit
- * @return MHD result code
+ * @param[in,out] bdc information about the batch deposit
  */
-static MHD_RESULT
-reply_batch_deposit_success (
-  struct MHD_Connection *connection,
-  const struct BatchDepositContext *dc)
+static void
+bdc_phase_reply_success (
+  struct BatchDepositContext *bdc)
 {
-  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
   const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
   enum TALER_ErrorCode ec;
   struct TALER_ExchangePublicKeyP pub;
   struct TALER_ExchangeSignatureP sig;
 
-  for (unsigned int i = 0; i<bd->num_cdis; i++)
+  for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
     csigs[i] = &bd->cdis[i].csig;
   if (TALER_EC_NONE !=
       (ec = TALER_exchange_online_deposit_confirmation_sign (
          &TEH_keys_exchange_sign_,
          &bd->h_contract_terms,
-         &dc->h_wire,
-         dc->has_no_policy ? NULL : &dc->h_policy,
-         dc->exchange_timestamp,
+         &bdc->h_wire,
+         bdc->has_no_policy ? NULL : &bdc->h_policy,
+         bdc->exchange_timestamp,
          bd->wire_deadline,
          bd->refund_deadline,
-         &dc->accumulated_total_without_fee,
+         &bdc->accumulated_total_without_fee,
          bd->num_cdis,
          csigs,
-         &dc->bd.merchant_pub,
+         &bdc->bd.merchant_pub,
          &pub,
          &sig)))
   {
     GNUNET_break (0);
-    return TALER_MHD_reply_with_ec (connection,
-                                    ec,
-                                    NULL);
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_ec (bdc->rc->connection,
+                                          ec,
+                                          NULL));
+    return;
   }
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_timestamp ("exchange_timestamp",
-                                dc->exchange_timestamp),
-    GNUNET_JSON_pack_data_auto ("exchange_pub",
-                                &pub),
-    GNUNET_JSON_pack_data_auto ("exchange_sig",
-                                &sig));
+  finish_loop (bdc,
+               TALER_MHD_REPLY_JSON_PACK (
+                 bdc->rc->connection,
+                 MHD_HTTP_OK,
+                 GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+                                             bdc->exchange_timestamp),
+                 GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                             &pub),
+                 GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                             &sig)));
 }
 
 
@@ -180,8 +283,8 @@ batch_deposit_transaction (void *cls,
                            struct MHD_Connection *connection,
                            MHD_RESULT *mhd_ret)
 {
-  struct BatchDepositContext *dc = cls;
-  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+  struct BatchDepositContext *bdc = cls;
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
   enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
   uint32_t bad_balance_coin_index = UINT32_MAX;
   bool balance_ok;
@@ -189,26 +292,26 @@ batch_deposit_transaction (void *cls,
 
   /* If the deposit has a policy associated to it, persist it.  This will
    * insert or update the record. */
-  if (! dc->has_no_policy)
+  if (! bdc->has_no_policy)
   {
     qs = TEH_plugin->persist_policy_details (
       TEH_plugin->cls,
-      &dc->policy_details,
-      &dc->bd.policy_details_serial_id,
-      &dc->accumulated_total_without_fee,
-      &dc->policy_details.fulfillment_state);
+      &bdc->policy_details,
+      &bdc->bd.policy_details_serial_id,
+      &bdc->accumulated_total_without_fee,
+      &bdc->policy_details.fulfillment_state);
     if (qs < 0)
       return qs;
 
-    dc->bd.policy_blocked =
-      dc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
+    bdc->bd.policy_blocked =
+      bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
   }
 
   /* FIXME: replace by batch insert! */
-  for (unsigned int i = 0; i<bd->num_cdis; i++)
+  for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
   {
     const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
-      = &bd->cdis[i];
+      = &bdc->cdis[i];
     uint64_t known_coin_id;
 
     qs = TEH_make_coin_known (&cdi->coin,
@@ -226,7 +329,7 @@ batch_deposit_transaction (void *cls,
   qs = TEH_plugin->do_deposit (
     TEH_plugin->cls,
     bd,
-    &dc->exchange_timestamp,
+    &bdc->exchange_timestamp,
     &balance_ok,
     &bad_balance_coin_index,
     &in_conflict);
@@ -236,10 +339,11 @@ batch_deposit_transaction (void *cls,
       return qs;
     TALER_LOG_WARNING (
       "Failed to store /batch-deposit information in database\n");
-    *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           TALER_EC_GENERIC_DB_STORE_FAILED,
-                                           "batch-deposit");
+    *mhd_ret = TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_GENERIC_DB_STORE_FAILED,
+      "batch-deposit");
     return qs;
   }
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -261,10 +365,11 @@ batch_deposit_transaction (void *cls,
     {
       TALER_LOG_WARNING (
         "Failed to retrieve conflicting contract details from database\n");
-      *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_STORE_FAILED,
-                                             "batch-deposit");
+      *mhd_ret = TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_INTERNAL_SERVER_ERROR,
+        TALER_EC_GENERIC_DB_STORE_FAILED,
+        "batch-deposit");
       return qs;
     }
 
@@ -277,16 +382,16 @@ batch_deposit_transaction (void *cls,
   }
   if (! balance_ok)
   {
-    GNUNET_assert (bad_balance_coin_index < bd->num_cdis);
+    GNUNET_assert (bad_balance_coin_index < bdc->bd.num_cdis);
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "returning history of conflicting coin (%s)\n",
-                TALER_B2S (&bd->cdis[bad_balance_coin_index].coin.coin_pub));
+                TALER_B2S (&bdc->cdis[bad_balance_coin_index].coin.coin_pub));
     *mhd_ret
       = TEH_RESPONSE_reply_coin_insufficient_funds (
           connection,
           TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
-          &bd->cdis[bad_balance_coin_index].coin.denom_pub_hash,
-          &bd->cdis[bad_balance_coin_index].coin.coin_pub);
+          &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+          &bdc->cdis[bad_balance_coin_index].coin.coin_pub);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
   TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
@@ -294,13 +399,289 @@ batch_deposit_transaction (void *cls,
 }
 
 
+/**
+ * Run database transaction.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_transact (struct BatchDepositContext *bdc)
+{
+  MHD_RESULT mhd_ret;
+
+  if (GNUNET_SYSERR ==
+      TEH_plugin->preflight (TEH_plugin->cls))
+  {
+    GNUNET_break (0);
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_GENERIC_DB_START_FAILED,
+                   "preflight failure"));
+    return;
+  }
+
+  if (GNUNET_OK !=
+      TEH_DB_run_transaction (bdc->rc->connection,
+                              "execute batch deposit",
+                              TEH_MT_REQUEST_BATCH_DEPOSIT,
+                              &mhd_ret,
+                              &batch_deposit_transaction,
+                              bdc))
+  {
+    finish_loop (bdc,
+                 mhd_ret);
+    return;
+  }
+  bdc->phase++;
+}
+
+
+/**
+ * Check if the @a bdc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param bdc parsed request data
+ * @return true if the request is idempotent with an existing request
+ *    false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+check_request_idempotent (
+  struct BatchDepositContext *bdc)
+{
+#if FIXME_PLACEHOLDER
+  const struct TEH_RequestContext *rc = bdc->rc;
+
+  for (unsigned int i = 0; i<bwc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &bwc->planchets[i];
+    enum GNUNET_DB_QueryStatus qs;
+    struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+
+    qs = TEH_plugin->get_withdraw_info (
+      TEH_plugin->cls,
+      &pc->collectable.h_coin_envelope,
+      &collectable);
+    if (0 > qs)
+    {
+      /* FIXME: soft error not handled correctly! */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      finish_loop (bwc,
+                   TALER_MHD_reply_with_error (
+                     rc->connection,
+                     MHD_HTTP_INTERNAL_SERVER_ERROR,
+                     TALER_EC_GENERIC_DB_FETCH_FAILED,
+                     "get_withdraw_info"));
+      return true;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return false;
+    pc->collectable = collectable;
+  }
+  /* generate idempotent reply */
+  TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
+  bwc->phase = BDC_PHASE_GENERATE_REPLY_SUCCESS;
+  return true;
+#else
+  GNUNET_break (0); // NOT IMPLEMENTED
+  return false;
+#endif
+}
+
+
+/**
+ * Check the KYC result.
+ *
+ * @param bdc storage for request processing
+ */
+static void
+bdc_phase_check_kyc_result (struct BatchDepositContext *bdc)
+{
+  /* return final positive response */
+  if (! bdc->kyc.ok)
+  {
+    if (check_request_idempotent (bdc))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Request is idempotent!\n");
+      return;
+    }
+    /* KYC required */
+    finish_loop (bdc,
+                 TEH_RESPONSE_reply_kyc_required (
+                   bdc->rc->connection,
+                   &bdc->bd.wire_target_h_payto,
+                   &bdc->kyc));
+    return;
+  }
+  bdc->phase = BDC_PHASE_TRANSACT;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+deposit_legi_cb (
+  void *cls,
+  const struct TEH_LegitimizationCheckResult *lcr)
+{
+  struct BatchDepositContext *bdc = cls;
+
+  bdc->lch = NULL;
+  GNUNET_assert (BDC_PHASE_SUSPENDED ==
+                 bdc->phase);
+  MHD_resume_connection (bdc->rc->connection);
+  GNUNET_CONTAINER_DLL_remove (bdc_head,
+                               bdc_tail,
+                               bdc);
+  TALER_MHD_daemon_trigger ();
+  if (NULL != lcr->response)
+  {
+    bdc->response = lcr->response;
+    bdc->http_status = lcr->http_status;
+    bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE;
+    return;
+  }
+  bdc->kyc = lcr->kyc;
+  bdc->phase = BDC_PHASE_CHECK_KYC_RESULT;
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant transaction amounts for a
+ * particular time range. Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and account to iterate
+ *        over events for
+ * @param limit maximum time-range for which events should be fetched
+ *        (timestamp in the past)
+ * @param cb function to call on each event found, events must be returned
+ *        in reverse chronological order
+ * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposit_amount_cb (
+  void *cls,
+  struct GNUNET_TIME_Absolute limit,
+  TALER_EXCHANGEDB_KycAmountCallback cb,
+  void *cb_cls)
+{
+  struct BatchDepositContext *bdc = cls;
+  enum GNUNET_GenericReturnValue ret;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Signaling amount %s for KYC check during deposit\n",
+              TALER_amount2s (&bdc->accumulated_total_without_fee));
+  ret = cb (cb_cls,
+            &bdc->accumulated_total_without_fee,
+            bdc->exchange_timestamp.abs_time);
+  GNUNET_break (GNUNET_SYSERR != ret);
+  if (GNUNET_OK != ret)
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  qs = TEH_plugin->select_deposit_amounts_for_kyc_check (
+    TEH_plugin->cls,
+    &bdc->bd.wire_target_h_payto,
+    limit,
+    cb,
+    cb_cls);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Got %d additional transactions for this deposit and limit 
%llu\n",
+              qs,
+              (unsigned long long) limit.abs_value_us);
+  GNUNET_break (qs >= 0);
+  return qs;
+}
+
+
+/**
+ * Run KYC check.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_kyc (struct BatchDepositContext *bdc)
+{
+  if (GNUNET_YES != TEH_enable_kyc)
+  {
+    bdc->phase++;
+    return;
+  }
+  /* FIXME: this fails to check that the
+     merchant_pub used in this request
+     matches the registered public key */
+  bdc->lch = TEH_legitimization_check (
+    &bdc->rc->async_scope_id,
+    TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
+    bdc->bd.receiver_wire_account,
+    &bdc->bd.wire_target_h_payto,
+    NULL,
+    &deposit_amount_cb,
+    bdc,
+    &deposit_legi_cb,
+    bdc);
+  GNUNET_assert (NULL != bdc->lch);
+  GNUNET_CONTAINER_DLL_insert (bdc_head,
+                               bdc_tail,
+                               bdc);
+  MHD_suspend_connection (bdc->rc->connection);
+  bdc->phase = BDC_PHASE_SUSPENDED;
+}
+
+
+/**
+ * Handle policy.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_policy (struct BatchDepositContext *bdc)
+{
+  const char *error_hint = NULL;
+
+  if (bdc->has_no_policy)
+  {
+    bdc->phase++;
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_extensions_create_policy_details (
+        TEH_currency,
+        bdc->policy_json,
+        &bdc->policy_details,
+        &error_hint))
+  {
+    GNUNET_break_op (0);
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
+                   error_hint));
+    return;
+  }
+
+  TALER_deposit_policy_hash (bdc->policy_json,
+                             &bdc->h_policy);
+  bdc->phase++;
+}
+
+
 /**
  * Parse per-coin deposit information from @a jcoin
  * into @a deposit. Fill in generic information from
  * @a ctx.
  *
- * @param connection connection we are handling
- * @param dc information about the overall batch
+ * @param bdc information about the overall batch
  * @param jcoin coin data to parse
  * @param[out] cdi where to store the result
  * @param[out] deposit_fee where to write the deposit fee
@@ -308,13 +689,12 @@ batch_deposit_transaction (void *cls,
  *         #GNUNET_SYSERR on failure and no error could be returned
  */
 static enum GNUNET_GenericReturnValue
-parse_coin (struct MHD_Connection *connection,
-            const struct BatchDepositContext *dc,
+parse_coin (const struct BatchDepositContext *bdc,
             json_t *jcoin,
             struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
             struct TALER_Amount *deposit_fee)
 {
-  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
   struct GNUNET_JSON_Specification spec[] = {
     TALER_JSON_spec_amount ("contribution",
                             TEH_currency,
@@ -336,7 +716,7 @@ parse_coin (struct MHD_Connection *connection,
   enum GNUNET_GenericReturnValue res;
 
   if (GNUNET_OK !=
-      (res = TALER_MHD_parse_json_data (connection,
+      (res = TALER_MHD_parse_json_data (bdc->rc->connection,
                                         jcoin,
                                         spec)))
     return res;
@@ -345,9 +725,10 @@ parse_coin (struct MHD_Connection *connection,
     struct TEH_DenominationKey *dk;
     MHD_RESULT mret;
 
-    dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
-                                        connection,
-                                        &mret);
+    dk = TEH_keys_denomination_by_hash (
+      &cdi->coin.denom_pub_hash,
+      bdc->rc->connection,
+      &mret);
     if (NULL == dk)
     {
       GNUNET_JSON_parse_free (spec);
@@ -361,10 +742,11 @@ parse_coin (struct MHD_Connection *connection,
       GNUNET_break_op (0);
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
-              TALER_MHD_reply_with_error (connection,
-                                          MHD_HTTP_BAD_REQUEST,
-                                          
TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
-                                          NULL))
+              TALER_MHD_reply_with_error (
+                bdc->rc->connection,
+                MHD_HTTP_BAD_REQUEST,
+                TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+                NULL))
         ? GNUNET_NO
         : GNUNET_SYSERR;
     }
@@ -374,7 +756,7 @@ parse_coin (struct MHD_Connection *connection,
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
-                connection,
+                bdc->rc->connection,
                 &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
                 "DEPOSIT"))
@@ -387,7 +769,7 @@ parse_coin (struct MHD_Connection *connection,
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
-                connection,
+                bdc->rc->connection,
                 &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
                 "DEPOSIT"))
@@ -400,7 +782,7 @@ parse_coin (struct MHD_Connection *connection,
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
-                connection,
+                bdc->rc->connection,
                 &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
                 "DEPOSIT"))
@@ -413,10 +795,11 @@ parse_coin (struct MHD_Connection *connection,
       /* denomination cipher and denomination signature cipher not the same */
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
-              TALER_MHD_reply_with_error (connection,
-                                          MHD_HTTP_BAD_REQUEST,
-                                          
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
-                                          NULL))
+              TALER_MHD_reply_with_error (
+                bdc->rc->connection,
+                MHD_HTTP_BAD_REQUEST,
+                TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+                NULL))
         ? GNUNET_NO
         : GNUNET_SYSERR;
     }
@@ -441,10 +824,11 @@ parse_coin (struct MHD_Connection *connection,
       TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
       GNUNET_JSON_parse_free (spec);
       return (MHD_YES ==
-              TALER_MHD_reply_with_error (connection,
-                                          MHD_HTTP_FORBIDDEN,
-                                          
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
-                                          NULL))
+              TALER_MHD_reply_with_error (
+                bdc->rc->connection,
+                MHD_HTTP_FORBIDDEN,
+                TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+                NULL))
         ? GNUNET_NO
         : GNUNET_SYSERR;
     }
@@ -455,10 +839,11 @@ parse_coin (struct MHD_Connection *connection,
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
     return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        
TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
-                                        NULL))
+            TALER_MHD_reply_with_error (
+              bdc->rc->connection,
+              MHD_HTTP_BAD_REQUEST,
+              TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+              NULL))
         ? GNUNET_NO
         : GNUNET_SYSERR;
   }
@@ -468,13 +853,13 @@ parse_coin (struct MHD_Connection *connection,
       TALER_wallet_deposit_verify (
         &cdi->amount_with_fee,
         deposit_fee,
-        &dc->h_wire,
+        &bdc->h_wire,
         &bd->h_contract_terms,
         &bd->wallet_data_hash,
         cdi->coin.no_age_commitment
         ? NULL
         : &cdi->coin.h_age_commitment,
-        NULL != dc->policy_json ? &dc->h_policy : NULL,
+        NULL != bdc->policy_json ? &bdc->h_policy : NULL,
         &cdi->coin.denom_pub_hash,
         bd->wallet_timestamp,
         &bd->merchant_pub,
@@ -485,10 +870,11 @@ parse_coin (struct MHD_Connection *connection,
     TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
     GNUNET_JSON_parse_free (spec);
     return (MHD_YES ==
-            TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_FORBIDDEN,
-                                        
TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
-                                        TALER_B2S (&cdi->coin.coin_pub)))
+            TALER_MHD_reply_with_error (
+              bdc->rc->connection,
+              MHD_HTTP_FORBIDDEN,
+              TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
+              TALER_B2S (&cdi->coin.coin_pub)))
       ? GNUNET_NO
       : GNUNET_SYSERR;
   }
@@ -496,15 +882,19 @@ parse_coin (struct MHD_Connection *connection,
 }
 
 
-MHD_RESULT
-TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
-                           const json_t *root,
-                           const char *const args[])
+/**
+ * Run processing phase that parses the request.
+ *
+ * @param[in,out] bdc request context
+ * @param root JSON object that was POSTed
+ */
+static void
+bdc_phase_parse (struct BatchDepositContext *bdc,
+                 const json_t *root)
 {
-  struct MHD_Connection *connection = rc->connection;
-  struct BatchDepositContext dc = { 0 };
-  struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
+  struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
   const json_t *coins;
+  const json_t *policy_json;
   bool no_refund_deadline = true;
   struct GNUNET_JSON_Specification spec[] = {
     TALER_JSON_spec_payto_uri ("merchant_payto_uri",
@@ -522,9 +912,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
     GNUNET_JSON_spec_array_const ("coins",
                                   &coins),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_json ("policy",
-                             &dc.policy_json),
-      &dc.has_no_policy),
+      GNUNET_JSON_spec_object_const ("policy",
+                                     &policy_json),
+      &bdc->has_no_policy),
     GNUNET_JSON_spec_timestamp ("timestamp",
                                 &bd->wallet_timestamp),
     GNUNET_JSON_spec_mark_optional (
@@ -536,24 +926,31 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) args;
   {
     enum GNUNET_GenericReturnValue res;
 
-    res = TALER_MHD_parse_json_data (connection,
+    res = TALER_MHD_parse_json_data (bdc->rc->connection,
                                      root,
                                      spec);
     if (GNUNET_SYSERR == res)
     {
+      /* hard failure */
       GNUNET_break (0);
-      return MHD_NO; /* hard failure */
+      finish_loop (bdc,
+                   MHD_NO);
+      return;
     }
     if (GNUNET_NO == res)
     {
+      /* failure */
       GNUNET_break_op (0);
-      return MHD_YES; /* failure */
+      finish_loop (bdc,
+                   MHD_YES);
+      return;
     }
   }
+  bdc->policy_json
+    = json_incref ((json_t *) policy_json);
 
   /* validate merchant's wire details (as far as we can) */
   {
@@ -566,12 +963,14 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
 
       GNUNET_break_op (0);
       GNUNET_JSON_parse_free (spec);
-      ret = TALER_MHD_reply_with_error (connection,
+      ret = TALER_MHD_reply_with_error (bdc->rc->connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         emsg);
       GNUNET_free (emsg);
-      return ret;
+      finish_loop (bdc,
+                   ret);
+      return;
     }
   }
   if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
@@ -580,156 +979,183 @@ TEH_handler_batch_deposit (struct TEH_RequestContext 
*rc,
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
-                                       NULL);
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
+                   NULL));
+    return;
   }
   if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
-                                       NULL);
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
+                   NULL));
+    return;
   }
   TALER_payto_hash (bd->receiver_wire_account,
                     &bd->wire_target_h_payto);
   TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
                                       &bd->wire_salt,
-                                      &dc.h_wire);
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (TEH_currency,
-                                        &dc.accumulated_total_without_fee));
+                                      &bdc->h_wire);
 
-  /* handle policy, if present */
-  if (! dc.has_no_policy)
-  {
-    const char *error_hint = NULL;
-
-    if (GNUNET_OK !=
-        TALER_extensions_create_policy_details (
-          TEH_currency,
-          dc.policy_json,
-          &dc.policy_details,
-          &error_hint))
-    {
-      GNUNET_break_op (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
-                                         error_hint);
-    }
-
-    TALER_deposit_policy_hash (dc.policy_json,
-                               &dc.h_policy);
-  }
 
   bd->num_cdis = json_array_size (coins);
   if (0 == bd->num_cdis)
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "coins");
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                   "coins"));
+    return;
   }
   if (TALER_MAX_FRESH_COINS < bd->num_cdis)
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "coins");
+    finish_loop (bdc,
+                 TALER_MHD_reply_with_error (
+                   bdc->rc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                   "coins"));
+    return;
   }
 
+  bdc->cdis
+    = GNUNET_new_array (bd->num_cdis,
+                        struct TALER_EXCHANGEDB_CoinDepositInformation);
+  bdc->deposit_fees
+    = GNUNET_new_array (bd->num_cdis,
+                        struct TALER_Amount);
+  bd->cdis = bdc->cdis;
+  for (unsigned i = 0; i<bd->num_cdis; i++)
   {
-    struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
-      GNUNET_NZL (bd->num_cdis)];
-    struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
+    struct TALER_Amount amount_without_fee;
     enum GNUNET_GenericReturnValue res;
-    unsigned int i;
 
-    bd->cdis = cdis;
-    dc.deposit_fees = deposit_fees;
-    for (i = 0; i<bd->num_cdis; i++)
-    {
-      struct TALER_Amount amount_without_fee;
-
-      res = parse_coin (connection,
-                        &dc,
-                        json_array_get (coins,
-                                        i),
-                        &cdis[i],
-                        &deposit_fees[i]);
-      if (GNUNET_OK != res)
-        break;
-      GNUNET_assert (0 <=
-                     TALER_amount_subtract (
-                       &amount_without_fee,
-                       &cdis[i].amount_with_fee,
-                       &deposit_fees[i]));
-
-      GNUNET_assert (0 <=
-                     TALER_amount_add (
-                       &dc.accumulated_total_without_fee,
-                       &dc.accumulated_total_without_fee,
-                       &amount_without_fee));
-    }
+    res = parse_coin (bdc,
+                      json_array_get (coins,
+                                      i),
+                      &bdc->cdis[i],
+                      &bdc->deposit_fees[i]);
     if (GNUNET_OK != res)
     {
-      for (unsigned int j = 0; j<i; j++)
-        TALER_denom_sig_free (&cdis[j].coin.denom_sig);
-      GNUNET_JSON_parse_free (spec);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+      finish_loop (bdc,
+                   (GNUNET_NO == res)
+                   ? MHD_YES
+                   : MHD_NO);
+      return;
     }
+    GNUNET_assert (0 <=
+                   TALER_amount_subtract (
+                     &amount_without_fee,
+                     &bdc->cdis[i].amount_with_fee,
+                     &bdc->deposit_fees[i]));
 
-    dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
-    if (GNUNET_SYSERR ==
-        TEH_plugin->preflight (TEH_plugin->cls))
-    {
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_START_FAILED,
-                                         "preflight failure");
-    }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (
+                     &bdc->accumulated_total_without_fee,
+                     &bdc->accumulated_total_without_fee,
+                     &amount_without_fee));
+  }
 
-    /* execute transaction */
-    {
-      MHD_RESULT mhd_ret;
-
-      if (GNUNET_OK !=
-          TEH_DB_run_transaction (connection,
-                                  "execute batch deposit",
-                                  TEH_MT_REQUEST_BATCH_DEPOSIT,
-                                  &mhd_ret,
-                                  &batch_deposit_transaction,
-                                  &dc))
-      {
-        for (unsigned int j = 0; j<bd->num_cdis; j++)
-          TALER_denom_sig_free (&cdis[j].coin.denom_sig);
-        GNUNET_JSON_parse_free (spec);
-        return mhd_ret;
-      }
-    }
+  GNUNET_JSON_parse_free (spec);
+  bdc->phase++;
+}
 
-    /* generate regular response */
-    {
-      MHD_RESULT mhd_ret;
 
-      mhd_ret = reply_batch_deposit_success (connection,
-                                             &dc);
-      for (unsigned int j = 0; j<bd->num_cdis; j++)
-        TALER_denom_sig_free (&cdis[j].coin.denom_sig);
-      GNUNET_JSON_parse_free (spec);
-      return mhd_ret;
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+bdc_cleaner (struct TEH_RequestContext *rc)
+{
+  struct BatchDepositContext *bdc = rc->rh_ctx;
+
+  if (NULL != bdc->lch)
+  {
+    TEH_legitimization_check_cancel (bdc->lch);
+    bdc->lch = NULL;
+  }
+  for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
+    TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig);
+  GNUNET_free (bdc->cdis);
+  GNUNET_free (bdc->deposit_fees);
+  json_decref (bdc->policy_json);
+  GNUNET_free (bdc);
+}
+
+
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+                           const json_t *root,
+                           const char *const args[])
+{
+  struct BatchDepositContext *bdc = rc->rh_ctx;
+
+  (void) args;
+  if (NULL == bdc)
+  {
+    bdc = GNUNET_new (struct BatchDepositContext);
+    bdc->rc = rc;
+    rc->rh_ctx = bdc;
+    rc->rh_cleaner = &bdc_cleaner;
+    bdc->phase = BDC_PHASE_PARSE;
+    bdc->exchange_timestamp = GNUNET_TIME_timestamp_get ();
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          
&bdc->accumulated_total_without_fee));
+  }
+  while (1)
+  {
+    switch (bdc->phase)
+    {
+    case BDC_PHASE_INIT:
+      GNUNET_break (0);
+      bdc->phase = BDC_PHASE_RETURN_NO;
+      break;
+    case BDC_PHASE_PARSE:
+      bdc_phase_parse (bdc,
+                       root);
+      break;
+    case BDC_PHASE_POLICY:
+      bdc_phase_policy (bdc);
+      break;
+    case BDC_PHASE_KYC:
+      bdc_phase_kyc (bdc);
+      break;
+    case BDC_PHASE_TRANSACT:
+      bdc_phase_transact (bdc);
+      break;
+    case BDC_PHASE_REPLY_SUCCESS:
+      bdc_phase_reply_success (bdc);
+      break;
+    case BDC_PHASE_SUSPENDED:
+      return MHD_YES;
+    case BDC_PHASE_CHECK_KYC_RESULT:
+      bdc_phase_check_kyc_result (bdc);
+      break;
+    case BDC_PHASE_GENERATE_REPLY_FAILURE:
+      return MHD_queue_response (bdc->rc->connection,
+                                 bdc->http_status,
+                                 bdc->response);
+    case BDC_PHASE_RETURN_YES:
+      return MHD_YES;
+    case BDC_PHASE_RETURN_NO:
+      return MHD_NO;
     }
   }
 }
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.h 
b/src/exchange/taler-exchange-httpd_batch-deposit.h
index 187fb9f20..b5b7b0a32 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.h
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.h
@@ -28,6 +28,14 @@
 #include "taler-exchange-httpd.h"
 
 
+/**
+ * Resumes all suspended batch deposit requests
+ * during cleanup.
+ */
+void
+TEH_batch_deposit_cleanup (void);
+
+
 /**
  * Handle a "/batch-deposit" request.  Parses the JSON, and, if
  * successful, passes the JSON data to #deposit_transaction() to
diff --git a/src/exchange/taler-exchange-httpd_config.h 
b/src/exchange/taler-exchange-httpd_config.h
index 036409778..486a8b83b 100644
--- a/src/exchange/taler-exchange-httpd_config.h
+++ b/src/exchange/taler-exchange-httpd_config.h
@@ -41,7 +41,7 @@
  *
  * Returned via both /config and /keys endpoints.
  */
-#define EXCHANGE_PROTOCOL_VERSION "20:0:3"
+#define EXCHANGE_PROTOCOL_VERSION "21:0:4"
 
 
 /**
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index ab9e666ca..a81c2d722 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -124,6 +124,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_select_withdraw_amounts_for_kyc_check.h 
pg_select_withdraw_amounts_for_kyc_check.c \
   pg_select_merge_amounts_for_kyc_check.h 
pg_select_merge_amounts_for_kyc_check.c \
   pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \
+  pg_select_deposit_amounts_for_kyc_check.h 
pg_select_deposit_amounts_for_kyc_check.c \
   pg_lookup_aml_history.h pg_lookup_aml_history.c \
   pg_lookup_kyc_history.h pg_lookup_kyc_history.c \
   pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c 
b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c
similarity index 80%
copy from src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
copy to src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c
index 417d78ec7..c34fd1da5 100644
--- a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
+++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2024 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
@@ -14,18 +14,17 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_merge_amounts_for_kyc_check.c
- * @brief Implementation of the select_merge_amounts_for_kyc_check function 
for Postgres
+ * @file exchangedb/pg_select_deposit_amounts_for_kyc_check.c
+ * @brief Implementation of the select_deposit_amounts_for_kyc_check function 
for Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include "taler_error_codes.h"
 #include "taler_dbevents.h"
 #include "taler_pq_lib.h"
-#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_select_deposit_amounts_for_kyc_check.h"
 #include "pg_helper.h"
 
-
 /**
  * Closure for #get_kyc_amounts_cb().
  */
@@ -110,7 +109,7 @@ get_kyc_amounts_cb (void *cls,
 
 
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_merge_amounts_for_kyc_check (
+TEH_PG_select_deposit_amounts_for_kyc_check (
   void *cls,
   const struct TALER_PaytoHashP *h_payto,
   struct GNUNET_TIME_Absolute time_limit,
@@ -131,23 +130,20 @@ TEH_PG_select_merge_amounts_for_kyc_check (
   };
   enum GNUNET_DB_QueryStatus qs;
 
-
   PREPARE (pg,
-           "select_kyc_relevant_merge_events",
+           "select_kyc_relevant_deposit_events",
            "SELECT"
-           " amount_with_fee AS amount"
-           ",merge_timestamp AS date"
-           " FROM account_merges"
-           " JOIN purse_merges USING (purse_pub)"
-           " JOIN purse_requests USING (purse_pub)"
-           " JOIN purse_decision USING (purse_pub)"
-           " WHERE wallet_h_payto=$1"
-           "   AND merge_timestamp >= $2"
-           "   AND NOT refunded"
-           " ORDER BY merge_timestamp DESC");
+           " cd.amount_with_fee AS amount"
+           ",bd.exchange_timestamp AS date"
+           " FROM batch_deposits bd"
+           " JOIN coin_deposits cd"
+           "   USING (batch_deposit_serial_id)"
+           " WHERE wire_target_h_payto=$1"
+           "   AND bd.exchange_timestamp >= $2"
+           " ORDER BY bd.exchange_timestamp DESC");
   qs = GNUNET_PQ_eval_prepared_multi_select (
     pg->conn,
-    "select_kyc_relevant_merge_events",
+    "select_kyc_relevant_deposit_events",
     params,
     &get_kyc_amounts_cb,
     &ctx);
diff --git a/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h 
b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h
new file mode 100644
index 000000000..7e4ab31b2
--- /dev/null
+++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h
@@ -0,0 +1,51 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2024 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 exchangedb/pg_select_deposit_amounts_for_kyc_check.h
+ * @brief implementation of the select_deposit_amounts_for_kyc_check function 
for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on deposited amounts after @a time_limit which are relevant for 
a
+ * KYC trigger for a merchant identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount,
+ *    in reverse chronological order (or until @a kac aborts
+ *    by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an 
error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_deposit_amounts_for_kyc_check (
+  void *cls,
+  const struct TALER_PaytoHashP *h_payto,
+  struct GNUNET_TIME_Absolute time_limit,
+  TALER_EXCHANGEDB_KycAmountCallback kac,
+  void *kac_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c 
b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
index 417d78ec7..8df91a398 100644
--- a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
@@ -131,7 +131,6 @@ TEH_PG_select_merge_amounts_for_kyc_check (
   };
   enum GNUNET_DB_QueryStatus qs;
 
-
   PREPARE (pg,
            "select_kyc_relevant_merge_events",
            "SELECT"
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index c13a7d193..3915e3a46 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -55,6 +55,7 @@
 #include "pg_lookup_records_by_table.h"
 #include "pg_lookup_kyc_status_by_token.h"
 #include "pg_lookup_serial_by_table.h"
+#include "pg_select_deposit_amounts_for_kyc_check.h"
 #include "pg_lookup_pending_legitimization.h"
 #include "pg_lookup_completed_legitimization.h"
 #include "pg_lookup_active_legitimization.h"
@@ -741,6 +742,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_get_wire_fees;
   plugin->select_aml_decisions
     = &TEH_PG_select_aml_decisions;
+  plugin->select_deposit_amounts_for_kyc_check
+    = &TEH_PG_select_deposit_amounts_for_kyc_check;
   plugin->insert_signkey_revocation
     = &TEH_PG_insert_signkey_revocation;
   plugin->select_aml_attributes
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index f4b5d8556..278994287 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -7163,6 +7163,28 @@ struct TALER_EXCHANGEDB_Plugin
     void *kac_cls);
 
 
+  /**
+   * Call @a kac on deposited amounts after @a time_limit which are relevant 
for a
+   * KYC trigger for a merchant identified by @a h_payto.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param time_limit oldest transaction that could be relevant
+   * @param kac function to call for each applicable amount,
+   *    in reverse chronological order (or until @a kac aborts
+   *    by returning anything except #GNUNET_OK).
+   * @param kac_cls closure for @a kac
+   * @return transaction status code, @a kac aborting with #GNUNET_NO is not 
an error
+   */
+  enum GNUNET_DB_QueryStatus
+    (*select_deposit_amounts_for_kyc_check)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    struct GNUNET_TIME_Absolute time_limit,
+    TALER_EXCHANGEDB_KycAmountCallback kac,
+    void *kac_cls);
+
+
   /**
    * Store automated legitimization outcome.
    *
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 7ea6b2b82..5f5b45f92 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -2777,6 +2777,12 @@ TALER_KYCLOGIC_kyc_test_required (
                                       rule->timeframe);
   }
 
+  if (! have_threshold)
+  {
+    *triggered_rule = NULL;
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  }
+
   {
     struct GNUNET_TIME_Absolute now
       = GNUNET_TIME_absolute_get ();
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 5890fa75e..329e2c2e6 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -40,12 +40,12 @@
  * Which version of the Taler protocol is implemented
  * by this library?  Used to determine compatibility.
  */
-#define EXCHANGE_PROTOCOL_CURRENT 19
+#define EXCHANGE_PROTOCOL_CURRENT 21
 
 /**
  * How many versions are we backwards compatible with?
  */
-#define EXCHANGE_PROTOCOL_AGE 2
+#define EXCHANGE_PROTOCOL_AGE 4
 
 /**
  * Set to 1 for extra debug logging.
@@ -975,7 +975,7 @@ decode_keys_json (const json_t *resp_obj,
       EXITIF (1);
     }
     {
-      const json_t *hard_limits;
+      const json_t *hard_limits = NULL;
       struct GNUNET_JSON_Specification sspec[] = {
         TALER_JSON_spec_currency_specification (
           "currency_specification",
@@ -989,9 +989,11 @@ decode_keys_json (const json_t *resp_obj,
           "stefan_log",
           currency,
           &key_data->stefan_log),
-        GNUNET_JSON_spec_array_const (
-          "hard_limits",
-          &hard_limits),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_array_const (
+            "hard_limits",
+            &hard_limits),
+          NULL),
         GNUNET_JSON_spec_double (
           "stefan_lin",
           &key_data->stefan_lin),
@@ -1010,9 +1012,10 @@ decode_keys_json (const json_t *resp_obj,
                     eline);
         EXITIF (1);
       }
-      if (GNUNET_OK !=
-          parse_hard_limits (hard_limits,
-                             key_data))
+      if ( (NULL != hard_limits) &&
+           (GNUNET_OK !=
+            parse_hard_limits (hard_limits,
+                               key_data)) )
       {
         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                     "Parsing hard limits of /keys failed\n");

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