major rework of withdraw transaction to use stored procedure and (presumably) reduce serialization failures by avoiding SELECT before INSERT

This commit is contained in:
Christian Grothoff 2021-12-05 17:16:00 +01:00
parent c0d2af8a49
commit 67de20d26e
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
10 changed files with 550 additions and 336 deletions

View File

@ -2110,6 +2110,8 @@ TEH_keys_denomination_by_hash2 (
&h_denom_pub->hash); &h_denom_pub->hash);
if (NULL == dk) if (NULL == dk)
{ {
if (NULL == conn)
return NULL;
*mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn, *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
h_denom_pub); h_denom_pub);
return NULL; return NULL;

View File

@ -235,11 +235,13 @@ reserve_history_transaction (void *cls,
MHD_RESULT *mhd_ret) MHD_RESULT *mhd_ret)
{ {
struct ReserveHistoryContext *rsc = cls; struct ReserveHistoryContext *rsc = cls;
struct TALER_Amount balance;
(void) connection; (void) connection;
(void) mhd_ret; (void) mhd_ret;
return TEH_plugin->get_reserve_history (TEH_plugin->cls, return TEH_plugin->get_reserve_history (TEH_plugin->cls,
&rsc->reserve_pub, &rsc->reserve_pub,
&balance,
&rsc->rh); &rsc->rh);
} }

View File

@ -91,21 +91,6 @@ struct WithdrawContext
*/ */
struct TALER_WithdrawRequestPS wsrd; 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. * Blinded planchet.
*/ */
@ -126,39 +111,9 @@ struct WithdrawContext
*/ */
struct TALER_EXCHANGEDB_KycStatus kyc; 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 * Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction * transaction logic; IF it returns a non-error code, the transaction
@ -182,67 +137,34 @@ withdraw_transaction (void *cls,
MHD_RESULT *mhd_ret) MHD_RESULT *mhd_ret)
{ {
struct WithdrawContext *wc = cls; struct WithdrawContext *wc = cls;
struct TALER_EXCHANGEDB_Reserve r;
enum GNUNET_DB_QueryStatus qs; 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 now = GNUNET_TIME_absolute_get ();
it from being overwritten by get_withdraw_info */ (void) GNUNET_TIME_round_abs (&now);
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;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO, wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
"Asked to withdraw from %s amount of %s\n", wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
TALER_B2S (&wc->wsrd.reserve_pub), qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
TALER_amount2s (&wc->amount_required)); &wc->collectable,
/* Don't sign again if we have already signed the coin */ now,
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &found,
{ &balance_ok,
/* Toss out the optimistic signature, we got another one from the DB; &wc->kyc,
optimization trade-off loses in this case: we unnecessarily computed &reserve_uuid);
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);
if (0 > qs) if (0 > qs)
{ {
if (GNUNET_DB_STATUS_HARD_ERROR == qs) if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection, *mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED,
"reserves"); "do_withdraw");
return qs; return qs;
} }
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) if (! found)
{ {
*mhd_ret = TALER_MHD_reply_with_error (connection, *mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND, MHD_HTTP_NOT_FOUND,
@ -250,29 +172,29 @@ withdraw_transaction (void *cls,
NULL); NULL);
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
if (0 < TALER_amount_cmp (&wc->amount_required, if (! balance_ok)
&r.balance))
{ {
struct TALER_EXCHANGEDB_ReserveHistory *rh; struct TALER_EXCHANGEDB_ReserveHistory *rh;
struct TALER_Amount balance;
TEH_plugin->rollback (TEH_plugin->cls);
if (GNUNET_OK !=
TEH_plugin->start (TEH_plugin->cls,
"get_reserve_history on insufficient 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;
}
/* The reserve does not have the required amount (actual /* The reserve does not have the required amount (actual
* amount + withdraw fee) */ * amount + withdraw fee) */
#if GNUNET_EXTRA_LOGGING
{
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);
}
#endif
qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
&wc->wsrd.reserve_pub, &wc->wsrd.reserve_pub,
&balance,
&rh); &rh);
if (NULL == rh) if (NULL == rh)
{ {
@ -284,41 +206,41 @@ withdraw_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
*mhd_ret = reply_withdraw_insufficient_funds (connection, *mhd_ret = reply_withdraw_insufficient_funds (connection,
&r.balance, &balance,
rh); rh);
TEH_plugin->free_reserve_history (TEH_plugin->cls, TEH_plugin->free_reserve_history (TEH_plugin->cls,
rh); rh);
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
GNUNET_log (GNUNET_ERROR_TYPE_INFO, if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
"KYC status is %s for %s\n", (! wc->kyc.ok) &&
wc->kyc.ok ? "ok" : "missing",
TALER_B2S (&r.pub));
if ( (! wc->kyc.ok) &&
(TEH_KYC_NONE != TEH_kyc_config.mode) &&
(TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) ) (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
{ {
/* Wallet-to-wallet payments _always_ require KYC */ /* Wallet-to-wallet payments _always_ require KYC */
wc->kyc_denied = true; *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
return qs; 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) && if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(TEH_KYC_NONE != TEH_kyc_config.mode) && (! wc->kyc.ok) &&
(TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) && (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
(! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) ) (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
{ {
/* Withdraws require KYC if above threshold */ /* Withdraws require KYC if above threshold */
struct TALER_Amount acc;
enum GNUNET_DB_QueryStatus qs2; enum GNUNET_DB_QueryStatus qs2;
bool below_limit;
acc = wc->amount_required; qs2 = TEH_plugin->do_withdraw_limit_check (
qs2 = TEH_plugin->select_withdraw_amounts_by_account (
TEH_plugin->cls, TEH_plugin->cls,
&wc->wsrd.reserve_pub, reserve_uuid,
TEH_kyc_config.withdraw_period, GNUNET_TIME_absolute_subtract (now,
&accumulate_withdraws, TEH_kyc_config.withdraw_period),
&acc); &TEH_kyc_config.withdraw_limit,
&below_limit);
if (0 > qs2) if (0 > qs2)
{ {
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED,
"withdraw details"); "do_withdraw_limit_check");
return qs2; return qs2;
} }
if (! below_limit)
if (GNUNET_OK !=
TALER_amount_is_valid (&acc))
{ {
GNUNET_break (0); *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
*mhd_ret = TALER_MHD_reply_with_ec (connection, connection,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, MHD_HTTP_ACCEPTED,
NULL); GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
return GNUNET_DB_STATUS_HARD_ERROR; 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; return qs;
} }
@ -432,9 +320,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
(void **) &wc.blinded_msg, (void **) &wc.blinded_msg,
&wc.blinded_msg_len), &wc.blinded_msg_len),
GNUNET_JSON_spec_fixed_auto ("reserve_sig", GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&wc.signature), &wc.collectable.reserve_sig),
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&wc.denom_pub_hash), &wc.collectable.denom_pub_hash),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
enum TALER_ErrorCode ec; enum TALER_ErrorCode ec;
@ -487,7 +375,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
return mret; return mret;
} }
dk = TEH_keys_denomination_by_hash2 (ksh, dk = TEH_keys_denomination_by_hash2 (ksh,
&wc.denom_pub_hash, &wc.collectable.denom_pub_hash,
NULL, NULL,
NULL); NULL);
if (NULL == dk) if (NULL == dk)
@ -497,8 +385,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
&mret)) &mret))
{ {
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TEH_RESPONSE_reply_unknown_denom_pub_hash (rc->connection, return TEH_RESPONSE_reply_unknown_denom_pub_hash (
&wc.denom_pub_hash); rc->connection,
&wc.collectable.denom_pub_hash);
} }
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return mret; return mret;
@ -519,7 +408,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TEH_RESPONSE_reply_expired_denom_pub_hash ( return TEH_RESPONSE_reply_expired_denom_pub_hash (
rc->connection, rc->connection,
&wc.denom_pub_hash, &wc.collectable.denom_pub_hash,
now, now,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
"WITHDRAW"); "WITHDRAW");
@ -538,7 +427,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TEH_RESPONSE_reply_expired_denom_pub_hash ( return TEH_RESPONSE_reply_expired_denom_pub_hash (
rc->connection, rc->connection,
&wc.denom_pub_hash, &wc.collectable.denom_pub_hash,
now, now,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
"WITHDRAW"); "WITHDRAW");
@ -557,7 +446,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TEH_RESPONSE_reply_expired_denom_pub_hash ( return TEH_RESPONSE_reply_expired_denom_pub_hash (
rc->connection, rc->connection,
&wc.denom_pub_hash, &wc.collectable.denom_pub_hash,
now, now,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
"WITHDRAW"); "WITHDRAW");
@ -569,7 +458,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
{ {
if (0 > if (0 >
TALER_amount_add (&wc.amount_required, TALER_amount_add (&wc.collectable.amount_with_fee,
&dk->meta.value, &dk->meta.value,
&dk->meta.fee_withdraw)) &dk->meta.fee_withdraw))
{ {
@ -580,7 +469,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
NULL); NULL);
} }
TALER_amount_hton (&wc.wsrd.amount_with_fee, TALER_amount_hton (&wc.wsrd.amount_with_fee,
&wc.amount_required); &wc.collectable.amount_with_fee);
} }
/* verify signature! */ /* verify signature! */
@ -589,15 +478,16 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
wc.wsrd.purpose.purpose wc.wsrd.purpose.purpose
= htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
wc.wsrd.h_denomination_pub wc.wsrd.h_denomination_pub
= wc.denom_pub_hash; = wc.collectable.denom_pub_hash;
TALER_coin_ev_hash (wc.blinded_msg, TALER_coin_ev_hash (wc.blinded_msg,
wc.blinded_msg_len, wc.blinded_msg_len,
&wc.wsrd.h_coin_envelope); &wc.wsrd.h_coin_envelope);
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, GNUNET_CRYPTO_eddsa_verify (
&wc.wsrd, TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
&wc.signature.eddsa_signature, &wc.wsrd,
&wc.wsrd.reserve_pub.eddsa_pub)) &wc.collectable.reserve_sig.eddsa_signature,
&wc.wsrd.reserve_pub.eddsa_pub))
{ {
TALER_LOG_WARNING ( TALER_LOG_WARNING (
"Client supplied invalid signature for withdraw request\n"); "Client supplied invalid signature for withdraw request\n");
@ -611,7 +501,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
/* Sign before transaction! */ /* Sign before transaction! */
ec = TALER_EC_NONE; ec = TALER_EC_NONE;
wc.collectable.sig 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,
wc.blinded_msg_len, wc.blinded_msg_len,
&ec); &ec);
@ -625,7 +515,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
} }
/* run transaction and sign (if not optimistically signed before) */ /* run transaction and sign (if not optimistically signed before) */
wc.kyc_denied = false;
{ {
MHD_RESULT mhd_ret; MHD_RESULT mhd_ret;
@ -647,16 +536,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
/* Clean up and send back final response */ /* Clean up and send back final response */
GNUNET_JSON_parse_free (spec); 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; MHD_RESULT ret;

View File

@ -4,3 +4,4 @@ test-exchangedb-fees
test-exchangedb-postgres test-exchangedb-postgres
test-exchangedb-signkeys test-exchangedb-signkeys
test-perf-taler-exchangedb test-perf-taler-exchangedb
bench-db-postgres

View File

@ -54,6 +54,10 @@ DROP TABLE IF EXISTS reserves CASCADE;
DROP TABLE IF EXISTS denomination_revocations CASCADE; DROP TABLE IF EXISTS denomination_revocations CASCADE;
DROP TABLE IF EXISTS denominations 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... -- And we're out of here...
COMMIT; COMMIT;

View File

@ -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 -- Complete transaction

View File

@ -596,6 +596,34 @@ prepare_statements (struct PostgresClosure *pg)
"lock_withdraw", "lock_withdraw",
"LOCK TABLE reserves_out;", "LOCK TABLE reserves_out;",
0), 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 /* Used in #postgres_insert_withdraw_info() to store
the signature of a blinded coin with the blinded coin's the signature of a blinded coin with the blinded coin's
details before returning it during /reserve/withdraw. We store 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_DenominationPublicKey denom_pub;
struct TALER_MasterSignatureP master_sig; struct TALER_MasterSignatureP master_sig;
struct TALER_DenominationHash h_denom_pub; struct TALER_DenominationHash h_denom_pub;
uint8_t revoked; bool revoked;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("master_sig", GNUNET_PQ_result_spec_auto_from_type ("master_sig",
&master_sig), &master_sig),
GNUNET_PQ_result_spec_auto_from_type ("revoked", GNUNET_PQ_result_spec_bool ("revoked",
&revoked), &revoked),
TALER_PQ_result_spec_absolute_time ("valid_from", TALER_PQ_result_spec_absolute_time ("valid_from",
&meta.start), &meta.start),
TALER_PQ_result_spec_absolute_time ("expire_withdraw", TALER_PQ_result_spec_absolute_time ("expire_withdraw",
@ -3422,7 +3450,7 @@ dominations_cb_helper (void *cls,
&h_denom_pub, &h_denom_pub,
&meta, &meta,
&master_sig, &master_sig,
(0 != revoked)); revoked);
GNUNET_PQ_cleanup_result (rs); 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_auto_from_type (&reserve->pub),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
uint8_t ok8;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance", TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
&reserve->balance), &reserve->balance),
@ -3787,19 +3814,16 @@ postgres_reserves_get (void *cls,
&reserve->gc), &reserve->gc),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid), &kyc->payment_target_uuid),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", GNUNET_PQ_result_spec_bool ("kyc_ok",
&ok8), &kyc->ok),
GNUNET_PQ_result_spec_end 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->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
kyc->ok = (0 != ok8); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
return qs; "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_string (payto_uri),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
uint8_t ok8;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid), &kyc->payment_target_uuid),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&ok8), &kyc->ok),
GNUNET_PQ_result_spec_end 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->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
kyc->ok = (0 != ok8); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
return qs; "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_uint64 (&payment_target_uuid),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
uint8_t ok8;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_payto", GNUNET_PQ_result_spec_auto_from_type ("h_payto",
h_payto), h_payto),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&ok8), &kyc->ok),
GNUNET_PQ_result_spec_end 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->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
kyc->ok = (0 != ok8);
kyc->payment_target_uuid = payment_target_uuid; 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_auto_from_type (&h_payto),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
uint8_t ok8 = 0;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id", GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id",
&kyc->payment_target_uuid), &kyc->payment_target_uuid),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", GNUNET_PQ_result_spec_bool ("kyc_ok",
&ok8), &kyc->ok),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
@ -3998,10 +4013,6 @@ inselect_account_kyc_status (
return GNUNET_DB_STATUS_SOFT_ERROR; return GNUNET_DB_STATUS_SOFT_ERROR;
kyc->ok = false; kyc->ok = false;
} }
else
{
kyc->ok = (0 != ok8);
}
} }
kyc->type = TALER_EXCHANGEDB_KYC_BALANCE; kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
return qs; return qs;
@ -4478,86 +4489,104 @@ postgres_get_withdraw_info (
/** /**
* Store collectable bit coin under the corresponding * Perform withdraw operation, checking for sufficient balance
* hash of the blinded message. * and possibly persisting the withdrawal details.
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @param cls the `struct PostgresClosure` with the plugin-specific state
* @param collectable corresponding collectable coin (blind signature) * @param collectable corresponding collectable coin (blind signature)
* if a coin is found * 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 * @return query execution status
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
postgres_insert_withdraw_info ( postgres_do_withdraw (
void *cls, 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 PostgresClosure *pg = cls;
struct TALER_EXCHANGEDB_Reserve reserve;
struct GNUNET_TIME_Absolute now;
struct GNUNET_TIME_Absolute gc; struct GNUNET_TIME_Absolute gc;
struct GNUNET_PQ_QueryParam params[] = { 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), 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_pub),
GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig), 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_absolute_time (&now),
TALER_PQ_query_param_amount (&collectable->amount_with_fee), TALER_PQ_query_param_absolute_time (&gc),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
enum GNUNET_DB_QueryStatus 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
};
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;
}
/* 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, gc = GNUNET_TIME_absolute_add (now,
pg->legal_reserve_expiration_time); pg->legal_reserve_expiration_time);
reserve.gc = GNUNET_TIME_absolute_max (gc, (void) GNUNET_TIME_round_abs (&gc);
reserve.gc); kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
(void) GNUNET_TIME_round_abs (&reserve.gc); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
qs = reserves_update (pg, "call_withdraw",
&reserve); params,
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); rs);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{ }
GNUNET_break (0);
qs = GNUNET_DB_STATUS_HARD_ERROR;
} /**
return qs; * 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; 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 * Set to #GNUNET_SYSERR on serious internal errors during
* the callbacks. * the callbacks.
*/ */
int status; enum GNUNET_GenericReturnValue status;
}; };
@ -4667,6 +4706,10 @@ add_bank_to_exchange (void *cls,
return; return;
} }
} }
GNUNET_assert (0 <=
TALER_amount_add (&rhc->balance_in,
&rhc->balance_in,
&bt->amount));
bt->reserve_pub = *rhc->reserve_pub; bt->reserve_pub = *rhc->reserve_pub;
tail = append_rh (rhc); tail = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
@ -4724,6 +4767,10 @@ add_withdraw_coin (void *cls,
return; return;
} }
} }
GNUNET_assert (0 <=
TALER_amount_add (&rhc->balance_out,
&rhc->balance_out,
&cbc->amount_with_fee));
cbc->reserve_pub = *rhc->reserve_pub; cbc->reserve_pub = *rhc->reserve_pub;
tail = append_rh (rhc); tail = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN; tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
@ -4784,6 +4831,10 @@ add_recoup (void *cls,
return; return;
} }
} }
GNUNET_assert (0 <=
TALER_amount_add (&rhc->balance_in,
&rhc->balance_in,
&recoup->value));
recoup->reserve_pub = *rhc->reserve_pub; recoup->reserve_pub = *rhc->reserve_pub;
tail = append_rh (rhc); tail = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
@ -4840,6 +4891,10 @@ add_exchange_to_bank (void *cls,
return; return;
} }
} }
GNUNET_assert (0 <=
TALER_amount_add (&rhc->balance_out,
&rhc->balance_out,
&closing->amount));
closing->reserve_pub = *rhc->reserve_pub; closing->reserve_pub = *rhc->reserve_pub;
tail = append_rh (rhc); tail = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; 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 cls the `struct PostgresClosure` with the plugin-specific state
* @param reserve_pub public key of the reserve * @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) * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
* @return transaction status * @return transaction status
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
postgres_get_reserve_history (void *cls, postgres_get_reserve_history (void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_ReservePublicKeyP *reserve_pub,
struct TALER_Amount *balance,
struct TALER_EXCHANGEDB_ReserveHistory **rhp) struct TALER_EXCHANGEDB_ReserveHistory **rhp)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
@ -4902,6 +4959,12 @@ postgres_get_reserve_history (void *cls,
rhc.rh_tail = NULL; rhc.rh_tail = NULL;
rhc.pg = pg; rhc.pg = pg;
rhc.status = GNUNET_OK; 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 */ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */
for (unsigned int i = 0; NULL != work[i].cb; i++) for (unsigned int i = 0; NULL != work[i].cb; i++)
{ {
@ -4927,6 +4990,10 @@ postgres_get_reserve_history (void *cls,
} }
} }
*rhp = rhc.rh; *rhp = rhc.rh;
GNUNET_assert (0 <=
TALER_amount_subtract (balance,
&rhc.balance_in,
&rhc.balance_out));
return qs; return qs;
} }
@ -5308,13 +5375,12 @@ postgres_get_ready_deposit (void *cls,
void *deposit_cb_cls) void *deposit_cb_cls)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
uint8_t kyc_override = (kyc_off) ? 1 : 0;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
TALER_PQ_query_param_absolute_time (&now), TALER_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_uint64 (&start_shard_row), GNUNET_PQ_query_param_uint64 (&start_shard_row),
GNUNET_PQ_query_param_uint64 (&end_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 GNUNET_PQ_query_param_end
}; };
struct TALER_Amount amount_with_fee; struct TALER_Amount amount_with_fee;
@ -6609,7 +6675,6 @@ add_coin_deposit (void *cls,
chc->have_deposit_or_melt = true; chc->have_deposit_or_melt = true;
deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
{ {
uint8_t done = 0;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
&deposit->amount_with_fee), &deposit->amount_with_fee),
@ -6636,7 +6701,7 @@ add_coin_deposit (void *cls,
GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
&serial_id), &serial_id),
GNUNET_PQ_result_spec_auto_from_type ("done", GNUNET_PQ_result_spec_auto_from_type ("done",
&done), &deposit->done),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
@ -6650,7 +6715,6 @@ add_coin_deposit (void *cls,
chc->failed = true; chc->failed = true;
return; return;
} }
deposit->done = (0 != done);
} }
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
tl->next = chc->head; tl->next = chc->head;
@ -7340,7 +7404,6 @@ postgres_lookup_transfer_by_deposit (
/* Check if transaction exists in deposits, so that we just /* Check if transaction exists in deposits, so that we just
do not have a WTID yet. In that case, return without wtid do not have a WTID yet. In that case, return without wtid
(by setting 'pending' true). */ (by setting 'pending' true). */
uint8_t ok8 = 0;
struct GNUNET_PQ_ResultSpec rs2[] = { struct GNUNET_PQ_ResultSpec rs2[] = {
GNUNET_PQ_result_spec_auto_from_type ("wire_salt", GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
&wire_salt), &wire_salt),
@ -7349,7 +7412,7 @@ postgres_lookup_transfer_by_deposit (
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid), &kyc->payment_target_uuid),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&ok8), &kyc->ok),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
amount_with_fee), amount_with_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
@ -7377,7 +7440,6 @@ postgres_lookup_transfer_by_deposit (
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
} }
kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT; kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
kyc->ok = (0 != ok8);
return qs; return qs;
} }
} }
@ -8164,7 +8226,7 @@ deposit_serial_helper_cb (void *cls,
struct TALER_EXCHANGEDB_Deposit deposit; struct TALER_EXCHANGEDB_Deposit deposit;
struct GNUNET_TIME_Absolute exchange_timestamp; struct GNUNET_TIME_Absolute exchange_timestamp;
struct TALER_DenominationPublicKey denom_pub; struct TALER_DenominationPublicKey denom_pub;
uint8_t done = 0; bool done;
uint64_t rowid; uint64_t rowid;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
@ -8191,8 +8253,8 @@ deposit_serial_helper_cb (void *cls,
&deposit.wire_salt), &deposit.wire_salt),
GNUNET_PQ_result_spec_string ("receiver_wire_account", GNUNET_PQ_result_spec_string ("receiver_wire_account",
&deposit.receiver_wire_account), &deposit.receiver_wire_account),
GNUNET_PQ_result_spec_auto_from_type ("done", GNUNET_PQ_result_spec_bool ("done",
&done), &done),
GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
&rowid), &rowid),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
@ -8213,7 +8275,7 @@ deposit_serial_helper_cb (void *cls,
exchange_timestamp, exchange_timestamp,
&deposit, &deposit,
&denom_pub, &denom_pub,
(0 != done) ? true : false); done);
GNUNET_PQ_cleanup_result (rs); GNUNET_PQ_cleanup_result (rs);
if (GNUNET_OK != ret) if (GNUNET_OK != ret)
break; break;
@ -9783,8 +9845,8 @@ missing_wire_cb (void *cls,
struct TALER_Amount amount; struct TALER_Amount amount;
char *payto_uri; char *payto_uri;
struct GNUNET_TIME_Absolute deadline; struct GNUNET_TIME_Absolute deadline;
uint8_t tiny; bool tiny;
uint8_t done; bool done;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
&rowid), &rowid),
@ -9796,10 +9858,10 @@ missing_wire_cb (void *cls,
&payto_uri), &payto_uri),
TALER_PQ_result_spec_absolute_time ("wire_deadline", TALER_PQ_result_spec_absolute_time ("wire_deadline",
&deadline), &deadline),
GNUNET_PQ_result_spec_auto_from_type ("tiny", GNUNET_PQ_result_spec_bool ("tiny",
&tiny), &tiny),
GNUNET_PQ_result_spec_auto_from_type ("done", GNUNET_PQ_result_spec_bool ("done",
&done), &done),
GNUNET_PQ_result_spec_end 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_auto_from_type (auditor_pub),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
uint8_t enabled8 = 0;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("auditor_url", GNUNET_PQ_result_spec_string ("auditor_url",
auditor_url), auditor_url),
GNUNET_PQ_result_spec_auto_from_type ("is_active", GNUNET_PQ_result_spec_bool ("is_active",
&enabled8), enabled),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_auditor_status", "lookup_auditor_status",
params, params,
rs); rs);
*enabled = (0 != enabled8);
return qs;
} }
@ -9996,12 +10054,11 @@ postgres_update_auditor (void *cls,
bool enabled) bool enabled)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
uint8_t enabled8 = enabled ? 1 : 0;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (auditor_pub), GNUNET_PQ_query_param_auto_from_type (auditor_pub),
GNUNET_PQ_query_param_string (auditor_url), GNUNET_PQ_query_param_string (auditor_url),
GNUNET_PQ_query_param_string (auditor_name), 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_absolute_time (&change_date),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
@ -10091,10 +10148,9 @@ postgres_update_wire (void *cls,
bool enabled) bool enabled)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
uint8_t enabled8 = enabled ? 1 : 0;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (payto_uri), 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_absolute_time (&change_date),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
@ -11767,7 +11823,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->get_latest_reserve_in_reference = plugin->get_latest_reserve_in_reference =
&postgres_get_latest_reserve_in_reference; &postgres_get_latest_reserve_in_reference;
plugin->get_withdraw_info = &postgres_get_withdraw_info; 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->get_reserve_history = &postgres_get_reserve_history;
plugin->select_withdraw_amounts_by_account plugin->select_withdraw_amounts_by_account
= &postgres_select_withdraw_amounts_by_account; = &postgres_select_withdraw_amounts_by_account;

View File

@ -1659,10 +1659,26 @@ run (void *cls)
cbc.reserve_pub = reserve_pub; cbc.reserve_pub = reserve_pub;
cbc.amount_with_fee = value; cbc.amount_with_fee = value;
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (CURRENCY, &cbc.withdraw_fee)); TALER_amount_set_zero (CURRENCY,
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != &cbc.withdraw_fee));
plugin->insert_withdraw_info (plugin->cls, {
&cbc)); 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 != FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub, check_reserve (&reserve_pub,
value.value, value.value,

View File

@ -496,7 +496,7 @@ struct TALER_EXCHANGEDB_ClosingTransfer
struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReservePublicKeyP reserve_pub;
/** /**
* Amount that was transferred to the exchange. * Amount that was transferred from the exchange.
*/ */
struct TALER_Amount amount; struct TALER_Amount amount;
@ -2512,23 +2512,70 @@ struct TALER_EXCHANGEDB_Plugin
* @return statement execution status * @return statement execution status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*insert_withdraw_info)( (*insert_withdraw_infoXX)(
void *cls, void *cls,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable); 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 * Get all of the transaction history associated with the specified
* reserve. * reserve.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param reserve_pub public key of the reserve * @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) * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
* @return transaction status * @return transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*get_reserve_history)(void *cls, (*get_reserve_history)(void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_ReservePublicKeyP *reserve_pub,
struct TALER_Amount *balance,
struct TALER_EXCHANGEDB_ReserveHistory **rhp); struct TALER_EXCHANGEDB_ReserveHistory **rhp);

View File

@ -78,7 +78,7 @@ TES_transmit_raw (int sock,
size_t end, size_t end,
const void *pos) const void *pos)
{ {
ssize_t off = 0; size_t off = 0;
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Sending message of length %u\n", "Sending message of length %u\n",