aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-12-05 17:16:00 +0100
committerChristian Grothoff <christian@grothoff.org>2021-12-05 17:16:00 +0100
commit67de20d26e7eed951528db6aaedaf163108f49a5 (patch)
tree900f533ef8cd5ce0217fab1ff20b61d228ae753b /src
parentc0d2af8a49a35e4face7e758aad670de94682633 (diff)
downloadexchange-67de20d26e7eed951528db6aaedaf163108f49a5.tar.xz
major rework of withdraw transaction to use stored procedure and (presumably) reduce serialization failures by avoiding SELECT before INSERT
Diffstat (limited to 'src')
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c2
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.c2
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c273
-rw-r--r--src/exchangedb/.gitignore1
-rw-r--r--src/exchangedb/drop0001.sql4
-rw-r--r--src/exchangedb/exchange-0001.sql205
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c322
-rw-r--r--src/exchangedb/test_exchangedb.c24
-rw-r--r--src/include/taler_exchangedb_plugin.h51
-rw-r--r--src/util/secmod_common.c2
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 86366eaf6..af9274c13 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 89a7dd498..3b8354215 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 a347156d9..8540fca4b 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -92,21 +92,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.
*/
char *blinded_msg;
@@ -126,40 +111,10 @@ 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
* logic MUST NOT queue a MHD response. IF it returns an hard error,
@@ -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 830cf10cc..aea9a74e4 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 52079e52c..3dcbb81fa 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 7acd67244..80ad95273 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 004516c51..9bdac759e 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);
}
@@ -4588,10 +4617,20 @@ 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 6807c2425..12cff4907 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 9a1dc78b6..fd2f3dc42 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 0a83bfb6c..6f3423869 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",