gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: major rework of withdraw transac


From: gnunet
Subject: [taler-exchange] branch master updated: major rework of withdraw transaction to use stored procedure and (presumably) reduce serialization failures by avoiding SELECT before INSERT
Date: Sun, 05 Dec 2021 17:16:04 +0100

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 67de20d2 major rework of withdraw transaction to use stored procedure 
and (presumably) reduce serialization failures by avoiding SELECT before INSERT
67de20d2 is described below

commit 67de20d26e7eed951528db6aaedaf163108f49a5
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Dec 5 17:16:00 2021 +0100

    major rework of withdraw transaction to use stored procedure and 
(presumably) reduce serialization failures by avoiding SELECT before INSERT
---
 src/exchange/taler-exchange-httpd_keys.c         |   2 +
 src/exchange/taler-exchange-httpd_reserves_get.c |   2 +
 src/exchange/taler-exchange-httpd_withdraw.c     | 273 ++++++-------------
 src/exchangedb/.gitignore                        |   1 +
 src/exchangedb/drop0001.sql                      |   4 +
 src/exchangedb/exchange-0001.sql                 | 205 +++++++++++++++
 src/exchangedb/plugin_exchangedb_postgres.c      | 322 +++++++++++++----------
 src/exchangedb/test_exchangedb.c                 |  24 +-
 src/include/taler_exchangedb_plugin.h            |  51 +++-
 src/util/secmod_common.c                         |   2 +-
 10 files changed, 550 insertions(+), 336 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_keys.c 
b/src/exchange/taler-exchange-httpd_keys.c
index 86366eaf..af9274c1 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -2110,6 +2110,8 @@ TEH_keys_denomination_by_hash2 (
                                           &h_denom_pub->hash);
   if (NULL == dk)
   {
+    if (NULL == conn)
+      return NULL;
     *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
                                                        h_denom_pub);
     return NULL;
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c 
b/src/exchange/taler-exchange-httpd_reserves_get.c
index 89a7dd49..3b835421 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.c
+++ b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -235,11 +235,13 @@ reserve_history_transaction (void *cls,
                              MHD_RESULT *mhd_ret)
 {
   struct ReserveHistoryContext *rsc = cls;
+  struct TALER_Amount balance;
 
   (void) connection;
   (void) mhd_ret;
   return TEH_plugin->get_reserve_history (TEH_plugin->cls,
                                           &rsc->reserve_pub,
+                                          &balance,
                                           &rsc->rh);
 }
 
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c 
b/src/exchange/taler-exchange-httpd_withdraw.c
index a347156d..8540fca4 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -91,21 +91,6 @@ struct WithdrawContext
    */
   struct TALER_WithdrawRequestPS wsrd;
 
-  /**
-   * Value of the coin plus withdraw fee.
-   */
-  struct TALER_Amount amount_required;
-
-  /**
-   * Hash of the denomination public key.
-   */
-  struct TALER_DenominationHash denom_pub_hash;
-
-  /**
-   * Signature over the request.
-   */
-  struct TALER_ReserveSignatureP signature;
-
   /**
    * Blinded planchet.
    */
@@ -126,39 +111,9 @@ struct WithdrawContext
    */
   struct TALER_EXCHANGEDB_KycStatus kyc;
 
-  /**
-   * Set to true if the operation was denied due to
-   * failing @e kyc checks.
-   */
-  bool kyc_denied;
-
 };
 
 
-/**
- * Function called with another amount that was
- * already withdrawn. Accumulates all amounts in
- * @a cls.
- *
- * @param[in,out] cls a `struct TALER_Amount`
- * @param val value to add to @a cls
- */
-static void
-accumulate_withdraws (void *cls,
-                      const struct TALER_Amount *val)
-{
-  struct TALER_Amount *acc = cls;
-
-  if (GNUNET_OK !=
-      TALER_amount_is_valid (acc))
-    return; /* ignore */
-  GNUNET_break (0 <=
-                TALER_amount_add (acc,
-                                  acc,
-                                  val));
-}
-
-
 /**
  * Function implementing withdraw transaction.  Runs the
  * transaction logic; IF it returns a non-error code, the transaction
@@ -182,67 +137,34 @@ withdraw_transaction (void *cls,
                       MHD_RESULT *mhd_ret)
 {
   struct WithdrawContext *wc = cls;
-  struct TALER_EXCHANGEDB_Reserve r;
   enum GNUNET_DB_QueryStatus qs;
-  struct TALER_BlindedDenominationSignature denom_sig;
+  bool found = false;
+  bool balance_ok = false;
+  uint64_t reserve_uuid;
+  struct GNUNET_TIME_Absolute now;
 
-  /* store away optimistic signature to protect
-     it from being overwritten by get_withdraw_info */
-  denom_sig = wc->collectable.sig;
-  memset (&wc->collectable.sig,
-          0,
-          sizeof (wc->collectable.sig));
-  qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
-                                      &wc->wsrd.h_coin_envelope,
-                                      &wc->collectable);
-  if (0 > qs)
-  {
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-      *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                             "withdraw details");
-    wc->collectable.sig = denom_sig;
-    return qs;
-  }
+  now = GNUNET_TIME_absolute_get ();
+  (void) GNUNET_TIME_round_abs (&now);
 
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Asked to withdraw from %s amount of %s\n",
-              TALER_B2S (&wc->wsrd.reserve_pub),
-              TALER_amount2s (&wc->amount_required));
-  /* Don't sign again if we have already signed the coin */
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-  {
-    /* Toss out the optimistic signature, we got another one from the DB;
-       optimization trade-off loses in this case: we unnecessarily computed
-       a signature :-( */
-    TALER_blinded_denom_sig_free (&denom_sig);
-    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-  }
-  /* We should never get more than one result, and we handled
-     the errors (negative case) above, so that leaves no results. */
-  GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
-  wc->collectable.sig = denom_sig;
-
-  /* Check if balance is sufficient */
-  r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in 
reserves_get (if successful) */
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Trying to withdraw from reserve: %s\n",
-              TALER_B2S (&r.pub));
-  qs = TEH_plugin->reserves_get (TEH_plugin->cls,
-                                 &r,
-                                 &wc->kyc);
+  wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
+  wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
+  qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
+                                &wc->collectable,
+                                now,
+                                &found,
+                                &balance_ok,
+                                &wc->kyc,
+                                &reserve_uuid);
   if (0 > qs)
   {
     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
       *mhd_ret = TALER_MHD_reply_with_error (connection,
                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                             "reserves");
+                                             "do_withdraw");
     return qs;
   }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  if (! found)
   {
     *mhd_ret = TALER_MHD_reply_with_error (connection,
                                            MHD_HTTP_NOT_FOUND,
@@ -250,29 +172,29 @@ withdraw_transaction (void *cls,
                                            NULL);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
-  if (0 < TALER_amount_cmp (&wc->amount_required,
-                            &r.balance))
+  if (! balance_ok)
   {
     struct TALER_EXCHANGEDB_ReserveHistory *rh;
+    struct TALER_Amount balance;
 
-    /* The reserve does not have the required amount (actual
-     * amount + withdraw fee) */
-#if GNUNET_EXTRA_LOGGING
+    TEH_plugin->rollback (TEH_plugin->cls);
+    if (GNUNET_OK !=
+        TEH_plugin->start (TEH_plugin->cls,
+                           "get_reserve_history on insufficient balance"))
     {
-      char *amount_required;
-      char *r_balance;
-
-      amount_required = TALER_amount_to_string (&wc->amount_required);
-      r_balance = TALER_amount_to_string (&r.balance);
-      TALER_LOG_DEBUG ("Asked %s over a reserve worth %s\n",
-                       amount_required,
-                       r_balance);
-      GNUNET_free (amount_required);
-      GNUNET_free (r_balance);
+      GNUNET_break (0);
+      if (NULL != mhd_ret)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_START_FAILED,
+                                               NULL);
+      return GNUNET_DB_STATUS_HARD_ERROR;
     }
-#endif
+    /* The reserve does not have the required amount (actual
+     * amount + withdraw fee) */
     qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
                                           &wc->wsrd.reserve_pub,
+                                          &balance,
                                           &rh);
     if (NULL == rh)
     {
@@ -284,41 +206,41 @@ withdraw_transaction (void *cls,
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
     *mhd_ret = reply_withdraw_insufficient_funds (connection,
-                                                  &r.balance,
+                                                  &balance,
                                                   rh);
     TEH_plugin->free_reserve_history (TEH_plugin->cls,
                                       rh);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
 
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "KYC status is %s for %s\n",
-              wc->kyc.ok ? "ok" : "missing",
-              TALER_B2S (&r.pub));
-  if ( (! wc->kyc.ok) &&
-       (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+  if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+       (! wc->kyc.ok) &&
        (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
   {
     /* Wallet-to-wallet payments _always_ require KYC */
-    wc->kyc_denied = true;
-    return qs;
+    *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_ACCEPTED,
+      GNUNET_JSON_pack_uint64 ("payment_target_uuid",
+                               wc->kyc.payment_target_uuid));
+    return GNUNET_DB_STATUS_HARD_ERROR;
   }
-  if ( (! wc->kyc.ok) &&
-       (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+  if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+       (! wc->kyc.ok) &&
        (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
        (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
   {
     /* Withdraws require KYC if above threshold */
-    struct TALER_Amount acc;
     enum GNUNET_DB_QueryStatus qs2;
+    bool below_limit;
 
-    acc = wc->amount_required;
-    qs2 = TEH_plugin->select_withdraw_amounts_by_account (
+    qs2 = TEH_plugin->do_withdraw_limit_check (
       TEH_plugin->cls,
-      &wc->wsrd.reserve_pub,
-      TEH_kyc_config.withdraw_period,
-      &accumulate_withdraws,
-      &acc);
+      reserve_uuid,
+      GNUNET_TIME_absolute_subtract (now,
+                                     TEH_kyc_config.withdraw_period),
+      &TEH_kyc_config.withdraw_limit,
+      &below_limit);
     if (0 > qs2)
     {
       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2);
@@ -326,52 +248,18 @@ withdraw_transaction (void *cls,
         *mhd_ret = TALER_MHD_reply_with_error (connection,
                                                MHD_HTTP_INTERNAL_SERVER_ERROR,
                                                
TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                               "withdraw details");
+                                               "do_withdraw_limit_check");
       return qs2;
     }
-
-    if (GNUNET_OK !=
-        TALER_amount_is_valid (&acc))
+    if (! below_limit)
     {
-      GNUNET_break (0);
-      *mhd_ret = TALER_MHD_reply_with_ec (connection,
-                                          
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                          NULL);
+      *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+        connection,
+        MHD_HTTP_ACCEPTED,
+        GNUNET_JSON_pack_uint64 ("payment_target_uuid",
+                                 wc->kyc.payment_target_uuid));
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Amount withdrawn so far is %s\n",
-                TALER_amount2s (&acc));
-    if (1 == /* 1: acc > withdraw_limit */
-        TALER_amount_cmp (&acc,
-                          &TEH_kyc_config.withdraw_limit))
-    {
-      wc->kyc_denied = true;
-      return qs;
-    }
-  }
-
-  /* Balance is good, persist signature */
-  wc->collectable.denom_pub_hash = wc->denom_pub_hash;
-  wc->collectable.amount_with_fee = wc->amount_required;
-  wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
-  wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
-  wc->collectable.reserve_sig = wc->signature;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Persisting withdraw from %s over %s\n",
-              TALER_B2S (&r.pub),
-              TALER_amount2s (&wc->amount_required));
-  qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls,
-                                         &wc->collectable);
-  if (0 > qs)
-  {
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-      *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_STORE_FAILED,
-                                             "withdraw details");
-    return qs;
   }
   return qs;
 }
@@ -432,9 +320,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
                               (void **) &wc.blinded_msg,
                               &wc.blinded_msg_len),
     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &wc.signature),
+                                 &wc.collectable.reserve_sig),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
-                                 &wc.denom_pub_hash),
+                                 &wc.collectable.denom_pub_hash),
     GNUNET_JSON_spec_end ()
   };
   enum TALER_ErrorCode ec;
@@ -487,7 +375,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
       return mret;
     }
     dk = TEH_keys_denomination_by_hash2 (ksh,
-                                         &wc.denom_pub_hash,
+                                         &wc.collectable.denom_pub_hash,
                                          NULL,
                                          NULL);
     if (NULL == dk)
@@ -497,8 +385,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
                                       &mret))
       {
         GNUNET_JSON_parse_free (spec);
-        return TEH_RESPONSE_reply_unknown_denom_pub_hash (rc->connection,
-                                                          &wc.denom_pub_hash);
+        return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+          rc->connection,
+          &wc.collectable.denom_pub_hash);
       }
       GNUNET_JSON_parse_free (spec);
       return mret;
@@ -519,7 +408,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
         GNUNET_JSON_parse_free (spec);
         return TEH_RESPONSE_reply_expired_denom_pub_hash (
           rc->connection,
-          &wc.denom_pub_hash,
+          &wc.collectable.denom_pub_hash,
           now,
           TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
           "WITHDRAW");
@@ -538,7 +427,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
       GNUNET_JSON_parse_free (spec);
       return TEH_RESPONSE_reply_expired_denom_pub_hash (
         rc->connection,
-        &wc.denom_pub_hash,
+        &wc.collectable.denom_pub_hash,
         now,
         TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
         "WITHDRAW");
@@ -557,7 +446,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
         GNUNET_JSON_parse_free (spec);
         return TEH_RESPONSE_reply_expired_denom_pub_hash (
           rc->connection,
-          &wc.denom_pub_hash,
+          &wc.collectable.denom_pub_hash,
           now,
           TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
           "WITHDRAW");
@@ -569,7 +458,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
 
   {
     if (0 >
-        TALER_amount_add (&wc.amount_required,
+        TALER_amount_add (&wc.collectable.amount_with_fee,
                           &dk->meta.value,
                           &dk->meta.fee_withdraw))
     {
@@ -580,7 +469,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
                                          NULL);
     }
     TALER_amount_hton (&wc.wsrd.amount_with_fee,
-                       &wc.amount_required);
+                       &wc.collectable.amount_with_fee);
   }
 
   /* verify signature! */
@@ -589,15 +478,16 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
   wc.wsrd.purpose.purpose
     = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
   wc.wsrd.h_denomination_pub
-    = wc.denom_pub_hash;
+    = wc.collectable.denom_pub_hash;
   TALER_coin_ev_hash (wc.blinded_msg,
                       wc.blinded_msg_len,
                       &wc.wsrd.h_coin_envelope);
   if (GNUNET_OK !=
-      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
-                                  &wc.wsrd,
-                                  &wc.signature.eddsa_signature,
-                                  &wc.wsrd.reserve_pub.eddsa_pub))
+      GNUNET_CRYPTO_eddsa_verify (
+        TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+        &wc.wsrd,
+        &wc.collectable.reserve_sig.eddsa_signature,
+        &wc.wsrd.reserve_pub.eddsa_pub))
   {
     TALER_LOG_WARNING (
       "Client supplied invalid signature for withdraw request\n");
@@ -611,7 +501,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
   /* Sign before transaction! */
   ec = TALER_EC_NONE;
   wc.collectable.sig
-    = TEH_keys_denomination_sign (&wc.denom_pub_hash,
+    = TEH_keys_denomination_sign (&wc.collectable.denom_pub_hash,
                                   wc.blinded_msg,
                                   wc.blinded_msg_len,
                                   &ec);
@@ -625,7 +515,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
   }
 
   /* run transaction and sign (if not optimistically signed before) */
-  wc.kyc_denied = false;
   {
     MHD_RESULT mhd_ret;
 
@@ -647,16 +536,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
   /* Clean up and send back final response */
   GNUNET_JSON_parse_free (spec);
 
-  if (wc.kyc_denied)
-  {
-    TALER_blinded_denom_sig_free (&wc.collectable.sig);
-    return TALER_MHD_REPLY_JSON_PACK (
-      rc->connection,
-      MHD_HTTP_ACCEPTED,
-      GNUNET_JSON_pack_uint64 ("payment_target_uuid",
-                               wc.kyc.payment_target_uuid));
-  }
-
   {
     MHD_RESULT ret;
 
diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore
index 830cf10c..aea9a74e 100644
--- a/src/exchangedb/.gitignore
+++ b/src/exchangedb/.gitignore
@@ -4,3 +4,4 @@ test-exchangedb-fees
 test-exchangedb-postgres
 test-exchangedb-signkeys
 test-perf-taler-exchangedb
+bench-db-postgres
diff --git a/src/exchangedb/drop0001.sql b/src/exchangedb/drop0001.sql
index 52079e52..3dcbb81f 100644
--- a/src/exchangedb/drop0001.sql
+++ b/src/exchangedb/drop0001.sql
@@ -54,6 +54,10 @@ DROP TABLE IF EXISTS reserves CASCADE;
 DROP TABLE IF EXISTS denomination_revocations CASCADE;
 DROP TABLE IF EXISTS denominations CASCADE;
 
+DROP FUNCTION IF EXISTS 
exchange_do_withdraw(bigint,integer,bytea,bytea,bytea,bytea,bytea,bigint,bigint)
 ;
+
+DROP FUNCTION IF EXISTS 
exchange_do_withdraw_limit_check(bigint,bigint,bigint,int) ;
+
 
 -- And we're out of here...
 COMMIT;
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 7acd6724..80ad9527 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -680,6 +680,211 @@ CREATE INDEX IF NOT EXISTS revolving_work_shards_index
   );
 
 
+-- Stored procedures
+
+
+DROP FUNCTION IF EXISTS 
exchange_do_withdraw(bigint,integer,bytea,bytea,bytea,bytea,bytea,bigint,bigint)
 ;
+
+CREATE OR REPLACE FUNCTION exchange_do_withdraw(
+  IN amount_val INT8,
+  IN amount_frac INT4,
+  IN h_denom_pub BYTEA,
+  IN rpub BYTEA,
+  IN reserve_sig BYTEA,
+  IN h_coin_envelope BYTEA,
+  IN denom_sig BYTEA,
+  IN now INT8,
+  IN min_reserve_gc INT8,
+  OUT reserve_found BOOLEAN,
+  OUT balance_ok BOOLEAN,
+  OUT kycok BOOLEAN,
+  OUT ruuid INT8,
+  OUT account_uuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  reserve_gc INT8;
+DECLARE
+  denom_serial INT8;
+DECLARE
+  reserve_val INT8;
+DECLARE
+  reserve_frac INT4;
+BEGIN
+
+SELECT denominations_serial INTO denom_serial
+  FROM denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+  -- denomination unknown, should be impossible!
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  kycok=FALSE;
+  ruuid=0;
+  account_uuid=0;
+  ASSERT false, 'denomination unknown';
+  RETURN;
+END IF;
+
+SELECT
+   reserves.reserve_uuid
+  ,current_balance_val
+  ,current_balance_frac
+  ,expiration_date
+  ,gc_date
+ INTO
+   ruuid
+  ,reserve_val
+  ,reserve_frac
+  ,reserve_gc
+  FROM reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+  -- reserve unknown
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  kycok=FALSE;
+  account_uuid=0;
+  RETURN;
+END IF;
+
+-- We optimistically insert, and then on conflict declare
+-- the query successful due to idempotency.
+INSERT INTO reserves_out
+  (h_blind_ev
+  ,denominations_serial
+  ,denom_sig
+  ,reserve_uuid
+  ,reserve_sig
+  ,execution_date
+  ,amount_with_fee_val
+  ,amount_with_fee_frac)
+VALUES
+  (h_coin_envelope
+  ,denom_serial
+  ,denom_sig
+  ,ruuid
+  ,reserve_sig
+  ,now
+  ,amount_val
+  ,amount_frac)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+  -- idempotent query, all constraints must be satisfied
+  reserve_found=TRUE;
+  balance_ok=TRUE;
+  kycok=TRUE;
+  account_uuid=0;
+  RETURN;
+END IF;
+
+-- Check reserve balance is sufficient.
+IF (reserve_val > amount_val)
+THEN
+  IF (reserve_frac > amount_frac)
+  THEN
+    reserve_val=reserve_val - amount_val;
+    reserve_frac=reserve_frac - amount_frac;
+  ELSE
+    reserve_val=reserve_val - amount_val - 1;
+    reserve_frac=reserve_frac + 100000000 - amount_frac;
+  END IF;
+ELSE
+  IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac)
+  THEN
+    reserve_val=0;
+    reserve_frac=reserve_frac - amount_frac;
+  ELSE
+    reserve_found=TRUE;
+    balance_ok=FALSE;
+    kycok=FALSE; -- we do not really know or care
+    account_uuid=0;
+    RETURN;
+  END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc);
+
+-- Update reserve balance.
+UPDATE reserves SET
+  gc_date=min_reserve_gc
+ ,current_balance_val=reserve_val
+ ,current_balance_frac=reserve_frac
+WHERE
+  reserves.reserve_uuid=ruuid;
+
+reserve_found=TRUE;
+balance_ok=TRUE;
+
+-- Obtain KYC status based on the last wire transfer into
+-- this reserve. FIXME: likely not adequate for reserves that got P2P 
transfers!
+SELECT
+   kyc_ok
+  ,wire_source_serial_id
+  INTO
+   kycok
+  ,account_uuid
+  FROM reserves_in
+  JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id)
+ WHERE reserve_uuid=ruuid
+ LIMIT 1; -- limit 1 should not be required (without p2p transfers)
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_withdraw(INT8, INT4, BYTEA, BYTEA, BYTEA, 
BYTEA, BYTEA, INT8, INT8)
+  IS 'Checks whether the reserve has sufficient balance for a withdraw 
operation (or the request is repeated and was previously approved) and if so 
updates the database with the result';
+
+
+
+DROP FUNCTION IF EXISTS 
exchange_do_withdraw_limit_check(bigint,bigint,bigint,int) ;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_withdraw_limit_check(
+  IN ruuid INT8,
+  IN start_time INT8,
+  IN upper_limit_val INT8,
+  IN upper_limit_frac INT4,
+  OUT below_limit BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  total_val INT8;
+DECLARE
+  total_frac INT8; -- INT4 could overflow during accumulation!
+BEGIN
+
+SELECT
+   SUM(amount_with_fee_val) -- overflow here is not plausible
+  ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
+  INTO
+   total_val
+  ,total_frac
+  FROM reserves_out
+ WHERE reserves_out.reserve_uuid=ruuid
+   AND execution_date > start_time;
+
+-- normalize result
+total_val = total_val + total_frac / 100000000;
+total_frac = total_frac % 100000000;
+
+-- compare to threshold
+below_limit = (total_val < upper_limit_val) OR
+            ( (total_val = upper_limit_val) AND
+              (total_frac <= upper_limit_frac) );
+END $$;
+
+COMMENT ON FUNCTION exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4)
+  IS 'Check whether the withdrawals from the given reserve since the given 
time are below the given threshold';
+
+
+
 
 
 -- Complete transaction
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 004516c5..9bdac759 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -596,6 +596,34 @@ prepare_statements (struct PostgresClosure *pg)
       "lock_withdraw",
       "LOCK TABLE reserves_out;",
       0),
+    /* Used in #postgres_do_withdraw() to store
+       the signature of a blinded coin with the blinded coin's
+       details before returning it during /reserve/withdraw. We store
+       the coin's denomination information (public key, signature)
+       and the blinded message as well as the reserve that the coin
+       is being withdrawn from and the signature of the message
+       authorizing the withdrawal. */
+    GNUNET_PQ_make_prepare (
+      "call_withdraw",
+      "SELECT "
+      " reserve_found"
+      ",balance_ok"
+      ",kycok AS kyc_ok"
+      ",ruuid AS reserve_uuid"
+      ",account_uuid AS payment_target_uuid"
+      " FROM exchange_do_withdraw"
+      " ($1,$2,$3,$4,$5,$6,$7,$8,$9);",
+      9),
+    /* Used in #postgres_do_withdraw_limit_check() to check
+       if the withdrawals remain below the limit under which
+       KYC is not required. */
+    GNUNET_PQ_make_prepare (
+      "call_withdraw_limit_check",
+      "SELECT "
+      " below_limit"
+      " FROM exchange_do_withdraw_limit_check"
+      " ($1,$2,$3,$4);",
+      4),
     /* Used in #postgres_insert_withdraw_info() to store
        the signature of a blinded coin with the blinded coin's
        details before returning it during /reserve/withdraw. We store
@@ -3378,12 +3406,12 @@ dominations_cb_helper (void *cls,
     struct TALER_DenominationPublicKey denom_pub;
     struct TALER_MasterSignatureP master_sig;
     struct TALER_DenominationHash h_denom_pub;
-    uint8_t revoked;
+    bool revoked;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_auto_from_type ("master_sig",
                                             &master_sig),
-      GNUNET_PQ_result_spec_auto_from_type ("revoked",
-                                            &revoked),
+      GNUNET_PQ_result_spec_bool ("revoked",
+                                  &revoked),
       TALER_PQ_result_spec_absolute_time ("valid_from",
                                           &meta.start),
       TALER_PQ_result_spec_absolute_time ("expire_withdraw",
@@ -3422,7 +3450,7 @@ dominations_cb_helper (void *cls,
              &h_denom_pub,
              &meta,
              &master_sig,
-             (0 != revoked));
+             revoked);
     GNUNET_PQ_cleanup_result (rs);
   }
 }
@@ -3777,7 +3805,6 @@ postgres_reserves_get (void *cls,
     GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
     GNUNET_PQ_query_param_end
   };
-  uint8_t ok8;
   struct GNUNET_PQ_ResultSpec rs[] = {
     TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
                                  &reserve->balance),
@@ -3787,19 +3814,16 @@ postgres_reserves_get (void *cls,
                                         &reserve->gc),
     GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
                                   &kyc->payment_target_uuid),
-    GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
-                                          &ok8),
+    GNUNET_PQ_result_spec_bool ("kyc_ok",
+                                &kyc->ok),
     GNUNET_PQ_result_spec_end
   };
-  enum GNUNET_DB_QueryStatus qs;
 
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "reserves_get_with_kyc",
-                                                 params,
-                                                 rs);
   kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
-  kyc->ok = (0 != ok8);
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "reserves_get_with_kyc",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -3874,23 +3898,19 @@ postgres_get_kyc_status (void *cls,
     GNUNET_PQ_query_param_string (payto_uri),
     GNUNET_PQ_query_param_end
   };
-  uint8_t ok8;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
                                   &kyc->payment_target_uuid),
     GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
-                                          &ok8),
+                                          &kyc->ok),
     GNUNET_PQ_result_spec_end
   };
-  enum GNUNET_DB_QueryStatus qs;
 
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "get_kyc_status",
-                                                 params,
-                                                 rs);
   kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
-  kyc->ok = (0 != ok8);
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_kyc_status",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -3914,24 +3934,20 @@ postgres_select_kyc_status (void *cls,
     GNUNET_PQ_query_param_uint64 (&payment_target_uuid),
     GNUNET_PQ_query_param_end
   };
-  uint8_t ok8;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_auto_from_type ("h_payto",
                                           h_payto),
     GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
-                                          &ok8),
+                                          &kyc->ok),
     GNUNET_PQ_result_spec_end
   };
-  enum GNUNET_DB_QueryStatus qs;
 
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "select_kyc_status",
-                                                 params,
-                                                 rs);
   kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
-  kyc->ok = (0 != ok8);
   kyc->payment_target_uuid = payment_target_uuid;
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "select_kyc_status",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -3962,12 +3978,11 @@ inselect_account_kyc_status (
       GNUNET_PQ_query_param_auto_from_type (&h_payto),
       GNUNET_PQ_query_param_end
     };
-    uint8_t ok8 = 0;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id",
                                     &kyc->payment_target_uuid),
-      GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
-                                            &ok8),
+      GNUNET_PQ_result_spec_bool ("kyc_ok",
+                                  &kyc->ok),
       GNUNET_PQ_result_spec_end
     };
 
@@ -3998,10 +4013,6 @@ inselect_account_kyc_status (
         return GNUNET_DB_STATUS_SOFT_ERROR;
       kyc->ok = false;
     }
-    else
-    {
-      kyc->ok = (0 != ok8);
-    }
   }
   kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
   return qs;
@@ -4478,86 +4489,104 @@ postgres_get_withdraw_info (
 
 
 /**
- * Store collectable bit coin under the corresponding
- * hash of the blinded message.
+ * Perform withdraw operation, checking for sufficient balance
+ * and possibly persisting the withdrawal details.
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param collectable corresponding collectable coin (blind signature)
  *                    if a coin is found
+ * @param now current time (rounded)
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] kyc_ok set to true if the kyc status of the reserve is satisfied
+ * @param[out] reserve_uuid set to the UUID of the reserve
  * @return query execution status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_insert_withdraw_info (
+postgres_do_withdraw (
   void *cls,
-  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  struct GNUNET_TIME_Absolute now,
+  bool *found,
+  bool *balance_ok,
+  struct TALER_EXCHANGEDB_KycStatus *kyc,
+  uint64_t *reserve_uuid)
 {
   struct PostgresClosure *pg = cls;
-  struct TALER_EXCHANGEDB_Reserve reserve;
-  struct GNUNET_TIME_Absolute now;
   struct GNUNET_TIME_Absolute gc;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+    TALER_PQ_query_param_amount (&collectable->amount_with_fee),
     GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
-    TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
     GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
     GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+    TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
     TALER_PQ_query_param_absolute_time (&now),
-    TALER_PQ_query_param_amount (&collectable->amount_with_fee),
+    TALER_PQ_query_param_absolute_time (&gc),
     GNUNET_PQ_query_param_end
   };
-  enum GNUNET_DB_QueryStatus qs;
-
-  now = GNUNET_TIME_absolute_get ();
-  (void) GNUNET_TIME_round_abs (&now);
-  qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                           "insert_withdraw_info",
-                                           params);
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
-  {
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    return qs;
-  }
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("reserve_found",
+                                found),
+    GNUNET_PQ_result_spec_bool ("balance_ok",
+                                balance_ok),
+    GNUNET_PQ_result_spec_bool ("kyc_ok",
+                                &kyc->ok),
+    GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
+                                  reserve_uuid),
+    GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
+                                  &kyc->payment_target_uuid),
+    GNUNET_PQ_result_spec_end
+  };
 
-  /* update reserve balance */
-  reserve.pub = collectable->reserve_pub;
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      (qs = reserves_get_internal (pg,
-                                   &reserve)))
-  {
-    /* Should have been checked before we got here... */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-      qs = GNUNET_DB_STATUS_HARD_ERROR;
-    return qs;
-  }
-  if (0 >
-      TALER_amount_subtract (&reserve.balance,
-                             &reserve.balance,
-                             &collectable->amount_with_fee))
-  {
-    /* The reserve history was checked to make sure there is enough of a 
balance
-       left before we tried this; however, concurrent operations may have 
changed
-       the situation by now, causing us to fail here. As reserves can no longer
-       be topped up, retrying should not help either.  */
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Withdrawal from reserve `%s' refused due to insufficient 
balance.\n",
-                TALER_B2S (&collectable->reserve_pub));
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
   gc = GNUNET_TIME_absolute_add (now,
                                  pg->legal_reserve_expiration_time);
-  reserve.gc = GNUNET_TIME_absolute_max (gc,
-                                         reserve.gc);
-  (void) GNUNET_TIME_round_abs (&reserve.gc);
-  qs = reserves_update (pg,
-                        &reserve);
-  GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    GNUNET_break (0);
-    qs = GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  return qs;
+  (void) GNUNET_TIME_round_abs (&gc);
+  kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_withdraw",
+                                                   params,
+                                                   rs);
+
+}
+
+
+/**
+ * Check that reserve remains below threshold for KYC
+ * checks after withdraw operation.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_uuid reserve to check
+ * @param withdraw_start starting point to accumulate from
+ * @param upper_limit maximum amount allowed
+ * @param[out] below_limit set to true if the limit was not exceeded
+ * @return query execution status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_do_withdraw_limit_check (
+  void *cls,
+  uint64_t reserve_uuid,
+  struct GNUNET_TIME_Absolute withdraw_start,
+  const struct TALER_Amount *upper_limit,
+  bool *below_limit)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&reserve_uuid),
+    TALER_PQ_query_param_absolute_time (&withdraw_start),
+    TALER_PQ_query_param_amount (upper_limit),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("below_limit",
+                                below_limit),
+    GNUNET_PQ_result_spec_end
+  };
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_withdraw_limit_check",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -4587,11 +4616,21 @@ struct ReserveHistoryContext
    */
   struct PostgresClosure *pg;
 
+  /**
+   * Sum of all credit transactions.
+   */
+  struct TALER_Amount balance_in;
+
+  /**
+   * Sum of all debit transactions.
+   */
+  struct TALER_Amount balance_out;
+
   /**
    * Set to #GNUNET_SYSERR on serious internal errors during
    * the callbacks.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -4667,6 +4706,10 @@ add_bank_to_exchange (void *cls,
         return;
       }
     }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&rhc->balance_in,
+                                     &rhc->balance_in,
+                                     &bt->amount));
     bt->reserve_pub = *rhc->reserve_pub;
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
@@ -4724,6 +4767,10 @@ add_withdraw_coin (void *cls,
         return;
       }
     }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&rhc->balance_out,
+                                     &rhc->balance_out,
+                                     &cbc->amount_with_fee));
     cbc->reserve_pub = *rhc->reserve_pub;
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
@@ -4784,6 +4831,10 @@ add_recoup (void *cls,
         return;
       }
     }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&rhc->balance_in,
+                                     &rhc->balance_in,
+                                     &recoup->value));
     recoup->reserve_pub = *rhc->reserve_pub;
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
@@ -4840,6 +4891,10 @@ add_exchange_to_bank (void *cls,
         return;
       }
     }
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&rhc->balance_out,
+                                     &rhc->balance_out,
+                                     &closing->amount));
     closing->reserve_pub = *rhc->reserve_pub;
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
@@ -4854,12 +4909,14 @@ add_exchange_to_bank (void *cls,
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param reserve_pub public key of the reserve
+ * @param[out] balance set to the reserve balance
  * @param[out] rhp set to known transaction history (NULL if reserve is 
unknown)
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
 postgres_get_reserve_history (void *cls,
                               const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                              struct TALER_Amount *balance,
                               struct TALER_EXCHANGEDB_ReserveHistory **rhp)
 {
   struct PostgresClosure *pg = cls;
@@ -4902,6 +4959,12 @@ postgres_get_reserve_history (void *cls,
   rhc.rh_tail = NULL;
   rhc.pg = pg;
   rhc.status = GNUNET_OK;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pg->currency,
+                                        &rhc.balance_in));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (pg->currency,
+                                        &rhc.balance_out));
   qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */
   for (unsigned int i = 0; NULL != work[i].cb; i++)
   {
@@ -4927,6 +4990,10 @@ postgres_get_reserve_history (void *cls,
     }
   }
   *rhp = rhc.rh;
+  GNUNET_assert (0 <=
+                 TALER_amount_subtract (balance,
+                                        &rhc.balance_in,
+                                        &rhc.balance_out));
   return qs;
 }
 
@@ -5308,13 +5375,12 @@ postgres_get_ready_deposit (void *cls,
                             void *deposit_cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  uint8_t kyc_override = (kyc_off) ? 1 : 0;
   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
   struct GNUNET_PQ_QueryParam params[] = {
     TALER_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_uint64 (&start_shard_row),
     GNUNET_PQ_query_param_uint64 (&end_shard_row),
-    GNUNET_PQ_query_param_auto_from_type (&kyc_override),
+    GNUNET_PQ_query_param_bool (kyc_off),
     GNUNET_PQ_query_param_end
   };
   struct TALER_Amount amount_with_fee;
@@ -6609,7 +6675,6 @@ add_coin_deposit (void *cls,
     chc->have_deposit_or_melt = true;
     deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
     {
-      uint8_t done = 0;
       struct GNUNET_PQ_ResultSpec rs[] = {
         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                      &deposit->amount_with_fee),
@@ -6636,7 +6701,7 @@ add_coin_deposit (void *cls,
         GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
                                       &serial_id),
         GNUNET_PQ_result_spec_auto_from_type ("done",
-                                              &done),
+                                              &deposit->done),
         GNUNET_PQ_result_spec_end
       };
 
@@ -6650,7 +6715,6 @@ add_coin_deposit (void *cls,
         chc->failed = true;
         return;
       }
-      deposit->done = (0 != done);
     }
     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     tl->next = chc->head;
@@ -7340,7 +7404,6 @@ postgres_lookup_transfer_by_deposit (
     /* Check if transaction exists in deposits, so that we just
        do not have a WTID yet. In that case, return without wtid
        (by setting 'pending' true). */
-    uint8_t ok8 = 0;
     struct GNUNET_PQ_ResultSpec rs2[] = {
       GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
                                             &wire_salt),
@@ -7349,7 +7412,7 @@ postgres_lookup_transfer_by_deposit (
       GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
                                     &kyc->payment_target_uuid),
       GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
-                                            &ok8),
+                                            &kyc->ok),
       TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                    amount_with_fee),
       TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
@@ -7377,7 +7440,6 @@ postgres_lookup_transfer_by_deposit (
         return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     }
     kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
-    kyc->ok = (0 != ok8);
     return qs;
   }
 }
@@ -8164,7 +8226,7 @@ deposit_serial_helper_cb (void *cls,
     struct TALER_EXCHANGEDB_Deposit deposit;
     struct GNUNET_TIME_Absolute exchange_timestamp;
     struct TALER_DenominationPublicKey denom_pub;
-    uint8_t done = 0;
+    bool done;
     uint64_t rowid;
     struct GNUNET_PQ_ResultSpec rs[] = {
       TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
@@ -8191,8 +8253,8 @@ deposit_serial_helper_cb (void *cls,
                                             &deposit.wire_salt),
       GNUNET_PQ_result_spec_string ("receiver_wire_account",
                                     &deposit.receiver_wire_account),
-      GNUNET_PQ_result_spec_auto_from_type ("done",
-                                            &done),
+      GNUNET_PQ_result_spec_bool ("done",
+                                  &done),
       GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
                                     &rowid),
       GNUNET_PQ_result_spec_end
@@ -8213,7 +8275,7 @@ deposit_serial_helper_cb (void *cls,
                    exchange_timestamp,
                    &deposit,
                    &denom_pub,
-                   (0 != done) ? true : false);
+                   done);
     GNUNET_PQ_cleanup_result (rs);
     if (GNUNET_OK != ret)
       break;
@@ -9783,8 +9845,8 @@ missing_wire_cb (void *cls,
     struct TALER_Amount amount;
     char *payto_uri;
     struct GNUNET_TIME_Absolute deadline;
-    uint8_t tiny;
-    uint8_t done;
+    bool tiny;
+    bool done;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
                                     &rowid),
@@ -9796,10 +9858,10 @@ missing_wire_cb (void *cls,
                                     &payto_uri),
       TALER_PQ_result_spec_absolute_time ("wire_deadline",
                                           &deadline),
-      GNUNET_PQ_result_spec_auto_from_type ("tiny",
-                                            &tiny),
-      GNUNET_PQ_result_spec_auto_from_type ("done",
-                                            &done),
+      GNUNET_PQ_result_spec_bool ("tiny",
+                                  &tiny),
+      GNUNET_PQ_result_spec_bool ("done",
+                                  &done),
       GNUNET_PQ_result_spec_end
     };
 
@@ -9923,22 +9985,18 @@ postgres_lookup_auditor_status (
     GNUNET_PQ_query_param_auto_from_type (auditor_pub),
     GNUNET_PQ_query_param_end
   };
-  uint8_t enabled8 = 0;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_string ("auditor_url",
                                   auditor_url),
-    GNUNET_PQ_result_spec_auto_from_type ("is_active",
-                                          &enabled8),
+    GNUNET_PQ_result_spec_bool ("is_active",
+                                enabled),
     GNUNET_PQ_result_spec_end
   };
-  enum GNUNET_DB_QueryStatus qs;
 
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "lookup_auditor_status",
-                                                 params,
-                                                 rs);
-  *enabled = (0 != enabled8);
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_auditor_status",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -9996,12 +10054,11 @@ postgres_update_auditor (void *cls,
                          bool enabled)
 {
   struct PostgresClosure *pg = cls;
-  uint8_t enabled8 = enabled ? 1 : 0;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (auditor_pub),
     GNUNET_PQ_query_param_string (auditor_url),
     GNUNET_PQ_query_param_string (auditor_name),
-    GNUNET_PQ_query_param_auto_from_type (&enabled8),
+    GNUNET_PQ_query_param_bool (enabled),
     GNUNET_PQ_query_param_absolute_time (&change_date),
     GNUNET_PQ_query_param_end
   };
@@ -10091,10 +10148,9 @@ postgres_update_wire (void *cls,
                       bool enabled)
 {
   struct PostgresClosure *pg = cls;
-  uint8_t enabled8 = enabled ? 1 : 0;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (payto_uri),
-    GNUNET_PQ_query_param_auto_from_type (&enabled8),
+    GNUNET_PQ_query_param_bool (enabled),
     GNUNET_PQ_query_param_absolute_time (&change_date),
     GNUNET_PQ_query_param_end
   };
@@ -11767,7 +11823,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
   plugin->get_latest_reserve_in_reference =
     &postgres_get_latest_reserve_in_reference;
   plugin->get_withdraw_info = &postgres_get_withdraw_info;
-  plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
+  // plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
+  plugin->do_withdraw = &postgres_do_withdraw;
+  plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check;
   plugin->get_reserve_history = &postgres_get_reserve_history;
   plugin->select_withdraw_amounts_by_account
     = &postgres_select_withdraw_amounts_by_account;
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 6807c242..12cff490 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1659,10 +1659,26 @@ run (void *cls)
   cbc.reserve_pub = reserve_pub;
   cbc.amount_with_fee = value;
   GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (CURRENCY, &cbc.withdraw_fee));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->insert_withdraw_info (plugin->cls,
-                                        &cbc));
+                 TALER_amount_set_zero (CURRENCY,
+                                        &cbc.withdraw_fee));
+  {
+    bool found;
+    bool balance_ok;
+    struct TALER_EXCHANGEDB_KycStatus kyc;
+    uint64_t ruuid;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_withdraw (plugin->cls,
+                                 &cbc,
+                                 now,
+                                 &found,
+                                 &balance_ok,
+                                 &kyc,
+                                 &ruuid));
+    GNUNET_assert (found);
+    GNUNET_assert (balance_ok);
+    GNUNET_assert (! kyc.ok);
+  }
   FAILIF (GNUNET_OK !=
           check_reserve (&reserve_pub,
                          value.value,
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 9a1dc78b..fd2f3dc4 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -496,7 +496,7 @@ struct TALER_EXCHANGEDB_ClosingTransfer
   struct TALER_ReservePublicKeyP reserve_pub;
 
   /**
-   * Amount that was transferred to the exchange.
+   * Amount that was transferred from the exchange.
    */
   struct TALER_Amount amount;
 
@@ -2512,23 +2512,70 @@ struct TALER_EXCHANGEDB_Plugin
    * @return statement execution status
    */
   enum GNUNET_DB_QueryStatus
-  (*insert_withdraw_info)(
+  (*insert_withdraw_infoXX)(
     void *cls,
     const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
 
 
+  /**
+   * Perform withdraw operation, checking for sufficient balance
+   * and possibly persisting the withdrawal details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param collectable corresponding collectable coin (blind signature)
+   *                    if a coin is found
+   * @param now current time (rounded)
+   * @param[out] found set to true if the reserve was found
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] kyc set to the KYC status of the reserve
+   * @param[out] reserve_uuid set to the UUID of the reserve
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_withdraw)(
+    void *cls,
+    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+    struct GNUNET_TIME_Absolute now,
+    bool *found,
+    bool *balance_ok,
+    struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
+    uint64_t *reserve_uuid);
+
+
+  /**
+   * Check that reserve remains below threshold for KYC
+   * checks after withdraw operation.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param reserve_uuid reserve to check
+   * @param withdraw_start starting point to accumulate from
+   * @param upper_limit maximum amount allowed
+   * @param[out] below_limit set to true if the limit was not exceeded
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_withdraw_limit_check)(
+    void *cls,
+    uint64_t reserve_uuid,
+    struct GNUNET_TIME_Absolute withdraw_start,
+    const struct TALER_Amount *upper_limit,
+    bool *below_limit);
+
+
   /**
    * Get all of the transaction history associated with the specified
    * reserve.
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param reserve_pub public key of the reserve
+   * @param[out] balance set to the reserve balance
    * @param[out] rhp set to known transaction history (NULL if reserve is 
unknown)
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
   (*get_reserve_history)(void *cls,
                          const struct TALER_ReservePublicKeyP *reserve_pub,
+                         struct TALER_Amount *balance,
                          struct TALER_EXCHANGEDB_ReserveHistory **rhp);
 
 
diff --git a/src/util/secmod_common.c b/src/util/secmod_common.c
index 0a83bfb6..6f342386 100644
--- a/src/util/secmod_common.c
+++ b/src/util/secmod_common.c
@@ -78,7 +78,7 @@ TES_transmit_raw (int sock,
                   size_t end,
                   const void *pos)
 {
-  ssize_t off = 0;
+  size_t off = 0;
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Sending message of length %u\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]