From 67de20d26e7eed951528db6aaedaf163108f49a5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 5 Dec 2021 17:16:00 +0100 Subject: major rework of withdraw transaction to use stored procedure and (presumably) reduce serialization failures by avoiding SELECT before INSERT --- src/exchange/taler-exchange-httpd_keys.c | 2 + src/exchange/taler-exchange-httpd_reserves_get.c | 2 + src/exchange/taler-exchange-httpd_withdraw.c | 273 ++++++------------- src/exchangedb/.gitignore | 1 + src/exchangedb/drop0001.sql | 4 + src/exchangedb/exchange-0001.sql | 205 +++++++++++++++ src/exchangedb/plugin_exchangedb_postgres.c | 322 +++++++++++++---------- src/exchangedb/test_exchangedb.c | 24 +- src/include/taler_exchangedb_plugin.h | 51 +++- src/util/secmod_common.c | 2 +- 10 files changed, 550 insertions(+), 336 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 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 @@ -91,21 +91,6 @@ struct WithdrawContext */ struct TALER_WithdrawRequestPS wsrd; - /** - * Value of the coin plus withdraw fee. - */ - struct TALER_Amount amount_required; - - /** - * Hash of the denomination public key. - */ - struct TALER_DenominationHash denom_pub_hash; - - /** - * Signature over the request. - */ - struct TALER_ReserveSignatureP signature; - /** * Blinded planchet. */ @@ -126,39 +111,9 @@ struct WithdrawContext */ struct TALER_EXCHANGEDB_KycStatus kyc; - /** - * Set to true if the operation was denied due to - * failing @e kyc checks. - */ - bool kyc_denied; - }; -/** - * Function called with another amount that was - * already withdrawn. Accumulates all amounts in - * @a cls. - * - * @param[in,out] cls a `struct TALER_Amount` - * @param val value to add to @a cls - */ -static void -accumulate_withdraws (void *cls, - const struct TALER_Amount *val) -{ - struct TALER_Amount *acc = cls; - - if (GNUNET_OK != - TALER_amount_is_valid (acc)) - return; /* ignore */ - GNUNET_break (0 <= - TALER_amount_add (acc, - acc, - val)); -} - - /** * Function implementing withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction @@ -182,67 +137,34 @@ withdraw_transaction (void *cls, MHD_RESULT *mhd_ret) { struct WithdrawContext *wc = cls; - struct TALER_EXCHANGEDB_Reserve r; enum GNUNET_DB_QueryStatus qs; - struct TALER_BlindedDenominationSignature denom_sig; + bool found = false; + bool balance_ok = false; + uint64_t reserve_uuid; + struct GNUNET_TIME_Absolute now; - /* store away optimistic signature to protect - it from being overwritten by get_withdraw_info */ - denom_sig = wc->collectable.sig; - memset (&wc->collectable.sig, - 0, - sizeof (wc->collectable.sig)); - qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &wc->wsrd.h_coin_envelope, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "withdraw details"); - wc->collectable.sig = denom_sig; - return qs; - } + now = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&now); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Asked to withdraw from %s amount of %s\n", - TALER_B2S (&wc->wsrd.reserve_pub), - TALER_amount2s (&wc->amount_required)); - /* Don't sign again if we have already signed the coin */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - /* Toss out the optimistic signature, we got another one from the DB; - optimization trade-off loses in this case: we unnecessarily computed - a signature :-( */ - TALER_blinded_denom_sig_free (&denom_sig); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - /* We should never get more than one result, and we handled - the errors (negative case) above, so that leaves no results. */ - GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); - wc->collectable.sig = denom_sig; - - /* Check if balance is sufficient */ - r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to withdraw from reserve: %s\n", - TALER_B2S (&r.pub)); - qs = TEH_plugin->reserves_get (TEH_plugin->cls, - &r, - &wc->kyc); + wc->collectable.reserve_pub = wc->wsrd.reserve_pub; + wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; + qs = TEH_plugin->do_withdraw (TEH_plugin->cls, + &wc->collectable, + now, + &found, + &balance_ok, + &wc->kyc, + &reserve_uuid); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserves"); + "do_withdraw"); return qs; } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + if (! found) { *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, @@ -250,29 +172,29 @@ withdraw_transaction (void *cls, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - if (0 < TALER_amount_cmp (&wc->amount_required, - &r.balance)) + if (! balance_ok) { struct TALER_EXCHANGEDB_ReserveHistory *rh; + struct TALER_Amount balance; - /* The reserve does not have the required amount (actual - * amount + withdraw fee) */ -#if GNUNET_EXTRA_LOGGING + TEH_plugin->rollback (TEH_plugin->cls); + if (GNUNET_OK != + TEH_plugin->start (TEH_plugin->cls, + "get_reserve_history on insufficient balance")) { - char *amount_required; - char *r_balance; - - amount_required = TALER_amount_to_string (&wc->amount_required); - r_balance = TALER_amount_to_string (&r.balance); - TALER_LOG_DEBUG ("Asked %s over a reserve worth %s\n", - amount_required, - r_balance); - GNUNET_free (amount_required); - GNUNET_free (r_balance); + GNUNET_break (0); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; } -#endif + /* The reserve does not have the required amount (actual + * amount + withdraw fee) */ qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, &wc->wsrd.reserve_pub, + &balance, &rh); if (NULL == rh) { @@ -284,41 +206,41 @@ withdraw_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } *mhd_ret = reply_withdraw_insufficient_funds (connection, - &r.balance, + &balance, rh); TEH_plugin->free_reserve_history (TEH_plugin->cls, rh); return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC status is %s for %s\n", - wc->kyc.ok ? "ok" : "missing", - TALER_B2S (&r.pub)); - if ( (! wc->kyc.ok) && - (TEH_KYC_NONE != TEH_kyc_config.mode) && + if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && + (! wc->kyc.ok) && (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) ) { /* Wallet-to-wallet payments _always_ require KYC */ - wc->kyc_denied = true; - return qs; + *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("payment_target_uuid", + wc->kyc.payment_target_uuid)); + return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (! wc->kyc.ok) && - (TEH_KYC_NONE != TEH_kyc_config.mode) && + if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && + (! wc->kyc.ok) && (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) && (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) ) { /* Withdraws require KYC if above threshold */ - struct TALER_Amount acc; enum GNUNET_DB_QueryStatus qs2; + bool below_limit; - acc = wc->amount_required; - qs2 = TEH_plugin->select_withdraw_amounts_by_account ( + qs2 = TEH_plugin->do_withdraw_limit_check ( TEH_plugin->cls, - &wc->wsrd.reserve_pub, - TEH_kyc_config.withdraw_period, - &accumulate_withdraws, - &acc); + reserve_uuid, + GNUNET_TIME_absolute_subtract (now, + TEH_kyc_config.withdraw_period), + &TEH_kyc_config.withdraw_limit, + &below_limit); if (0 > qs2) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2); @@ -326,52 +248,18 @@ withdraw_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "withdraw details"); + "do_withdraw_limit_check"); return qs2; } - - if (GNUNET_OK != - TALER_amount_is_valid (&acc)) + if (! below_limit) { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("payment_target_uuid", + wc->kyc.payment_target_uuid)); return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Amount withdrawn so far is %s\n", - TALER_amount2s (&acc)); - if (1 == /* 1: acc > withdraw_limit */ - TALER_amount_cmp (&acc, - &TEH_kyc_config.withdraw_limit)) - { - wc->kyc_denied = true; - return qs; - } - } - - /* Balance is good, persist signature */ - wc->collectable.denom_pub_hash = wc->denom_pub_hash; - wc->collectable.amount_with_fee = wc->amount_required; - wc->collectable.reserve_pub = wc->wsrd.reserve_pub; - wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; - wc->collectable.reserve_sig = wc->signature; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Persisting withdraw from %s over %s\n", - TALER_B2S (&r.pub), - TALER_amount2s (&wc->amount_required)); - qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "withdraw details"); - return qs; } return qs; } @@ -432,9 +320,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, (void **) &wc.blinded_msg, &wc.blinded_msg_len), GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &wc.signature), + &wc.collectable.reserve_sig), GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &wc.denom_pub_hash), + &wc.collectable.denom_pub_hash), GNUNET_JSON_spec_end () }; enum TALER_ErrorCode ec; @@ -487,7 +375,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, return mret; } dk = TEH_keys_denomination_by_hash2 (ksh, - &wc.denom_pub_hash, + &wc.collectable.denom_pub_hash, NULL, NULL); if (NULL == dk) @@ -497,8 +385,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, &mret)) { GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_unknown_denom_pub_hash (rc->connection, - &wc.denom_pub_hash); + return TEH_RESPONSE_reply_unknown_denom_pub_hash ( + rc->connection, + &wc.collectable.denom_pub_hash); } GNUNET_JSON_parse_free (spec); return mret; @@ -519,7 +408,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, GNUNET_JSON_parse_free (spec); return TEH_RESPONSE_reply_expired_denom_pub_hash ( rc->connection, - &wc.denom_pub_hash, + &wc.collectable.denom_pub_hash, now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "WITHDRAW"); @@ -538,7 +427,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, GNUNET_JSON_parse_free (spec); return TEH_RESPONSE_reply_expired_denom_pub_hash ( rc->connection, - &wc.denom_pub_hash, + &wc.collectable.denom_pub_hash, now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, "WITHDRAW"); @@ -557,7 +446,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, GNUNET_JSON_parse_free (spec); return TEH_RESPONSE_reply_expired_denom_pub_hash ( rc->connection, - &wc.denom_pub_hash, + &wc.collectable.denom_pub_hash, now, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, "WITHDRAW"); @@ -569,7 +458,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, { if (0 > - TALER_amount_add (&wc.amount_required, + TALER_amount_add (&wc.collectable.amount_with_fee, &dk->meta.value, &dk->meta.fee_withdraw)) { @@ -580,7 +469,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, NULL); } TALER_amount_hton (&wc.wsrd.amount_with_fee, - &wc.amount_required); + &wc.collectable.amount_with_fee); } /* verify signature! */ @@ -589,15 +478,16 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, wc.wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); wc.wsrd.h_denomination_pub - = wc.denom_pub_hash; + = wc.collectable.denom_pub_hash; TALER_coin_ev_hash (wc.blinded_msg, wc.blinded_msg_len, &wc.wsrd.h_coin_envelope); if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &wc.wsrd, - &wc.signature.eddsa_signature, - &wc.wsrd.reserve_pub.eddsa_pub)) + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &wc.wsrd, + &wc.collectable.reserve_sig.eddsa_signature, + &wc.wsrd.reserve_pub.eddsa_pub)) { TALER_LOG_WARNING ( "Client supplied invalid signature for withdraw request\n"); @@ -611,7 +501,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, /* Sign before transaction! */ ec = TALER_EC_NONE; wc.collectable.sig - = TEH_keys_denomination_sign (&wc.denom_pub_hash, + = TEH_keys_denomination_sign (&wc.collectable.denom_pub_hash, wc.blinded_msg, wc.blinded_msg_len, &ec); @@ -625,7 +515,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, } /* run transaction and sign (if not optimistically signed before) */ - wc.kyc_denied = false; { MHD_RESULT mhd_ret; @@ -647,16 +536,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, /* Clean up and send back final response */ GNUNET_JSON_parse_free (spec); - if (wc.kyc_denied) - { - TALER_blinded_denom_sig_free (&wc.collectable.sig); - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("payment_target_uuid", - wc.kyc.payment_target_uuid)); - } - { MHD_RESULT ret; diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore index 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); } @@ -4587,11 +4616,21 @@ struct ReserveHistoryContext */ struct PostgresClosure *pg; + /** + * Sum of all credit transactions. + */ + struct TALER_Amount balance_in; + + /** + * Sum of all debit transactions. + */ + struct TALER_Amount balance_out; + /** * Set to #GNUNET_SYSERR on serious internal errors during * the callbacks. */ - int status; + enum GNUNET_GenericReturnValue status; }; @@ -4667,6 +4706,10 @@ add_bank_to_exchange (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_in, + &rhc->balance_in, + &bt->amount)); bt->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; @@ -4724,6 +4767,10 @@ add_withdraw_coin (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_out, + &rhc->balance_out, + &cbc->amount_with_fee)); cbc->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN; @@ -4784,6 +4831,10 @@ add_recoup (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_in, + &rhc->balance_in, + &recoup->value)); recoup->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; @@ -4840,6 +4891,10 @@ add_exchange_to_bank (void *cls, return; } } + GNUNET_assert (0 <= + TALER_amount_add (&rhc->balance_out, + &rhc->balance_out, + &closing->amount)); closing->reserve_pub = *rhc->reserve_pub; tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; @@ -4854,12 +4909,14 @@ add_exchange_to_bank (void *cls, * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param reserve_pub public key of the reserve + * @param[out] balance set to the reserve balance * @param[out] rhp set to known transaction history (NULL if reserve is unknown) * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_get_reserve_history (void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, struct TALER_EXCHANGEDB_ReserveHistory **rhp) { struct PostgresClosure *pg = cls; @@ -4902,6 +4959,12 @@ postgres_get_reserve_history (void *cls, rhc.rh_tail = NULL; rhc.pg = pg; rhc.status = GNUNET_OK; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + &rhc.balance_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pg->currency, + &rhc.balance_out)); qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */ for (unsigned int i = 0; NULL != work[i].cb; i++) { @@ -4927,6 +4990,10 @@ postgres_get_reserve_history (void *cls, } } *rhp = rhc.rh; + GNUNET_assert (0 <= + TALER_amount_subtract (balance, + &rhc.balance_in, + &rhc.balance_out)); return qs; } @@ -5308,13 +5375,12 @@ postgres_get_ready_deposit (void *cls, void *deposit_cb_cls) { struct PostgresClosure *pg = cls; - uint8_t kyc_override = (kyc_off) ? 1 : 0; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_uint64 (&start_shard_row), GNUNET_PQ_query_param_uint64 (&end_shard_row), - GNUNET_PQ_query_param_auto_from_type (&kyc_override), + GNUNET_PQ_query_param_bool (kyc_off), GNUNET_PQ_query_param_end }; struct TALER_Amount amount_with_fee; @@ -6609,7 +6675,6 @@ add_coin_deposit (void *cls, chc->have_deposit_or_melt = true; deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); { - uint8_t done = 0; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &deposit->amount_with_fee), @@ -6636,7 +6701,7 @@ add_coin_deposit (void *cls, GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &serial_id), GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + &deposit->done), GNUNET_PQ_result_spec_end }; @@ -6650,7 +6715,6 @@ add_coin_deposit (void *cls, chc->failed = true; return; } - deposit->done = (0 != done); } tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); tl->next = chc->head; @@ -7340,7 +7404,6 @@ postgres_lookup_transfer_by_deposit ( /* Check if transaction exists in deposits, so that we just do not have a WTID yet. In that case, return without wtid (by setting 'pending' true). */ - uint8_t ok8 = 0; struct GNUNET_PQ_ResultSpec rs2[] = { GNUNET_PQ_result_spec_auto_from_type ("wire_salt", &wire_salt), @@ -7349,7 +7412,7 @@ postgres_lookup_transfer_by_deposit ( GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", &kyc->payment_target_uuid), GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", - &ok8), + &kyc->ok), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", amount_with_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", @@ -7377,7 +7440,6 @@ postgres_lookup_transfer_by_deposit ( return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT; - kyc->ok = (0 != ok8); return qs; } } @@ -8164,7 +8226,7 @@ deposit_serial_helper_cb (void *cls, struct TALER_EXCHANGEDB_Deposit deposit; struct GNUNET_TIME_Absolute exchange_timestamp; struct TALER_DenominationPublicKey denom_pub; - uint8_t done = 0; + bool done; uint64_t rowid; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", @@ -8191,8 +8253,8 @@ deposit_serial_helper_cb (void *cls, &deposit.wire_salt), GNUNET_PQ_result_spec_string ("receiver_wire_account", &deposit.receiver_wire_account), - GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + GNUNET_PQ_result_spec_bool ("done", + &done), GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &rowid), GNUNET_PQ_result_spec_end @@ -8213,7 +8275,7 @@ deposit_serial_helper_cb (void *cls, exchange_timestamp, &deposit, &denom_pub, - (0 != done) ? true : false); + done); GNUNET_PQ_cleanup_result (rs); if (GNUNET_OK != ret) break; @@ -9783,8 +9845,8 @@ missing_wire_cb (void *cls, struct TALER_Amount amount; char *payto_uri; struct GNUNET_TIME_Absolute deadline; - uint8_t tiny; - uint8_t done; + bool tiny; + bool done; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", &rowid), @@ -9796,10 +9858,10 @@ missing_wire_cb (void *cls, &payto_uri), TALER_PQ_result_spec_absolute_time ("wire_deadline", &deadline), - GNUNET_PQ_result_spec_auto_from_type ("tiny", - &tiny), - GNUNET_PQ_result_spec_auto_from_type ("done", - &done), + GNUNET_PQ_result_spec_bool ("tiny", + &tiny), + GNUNET_PQ_result_spec_bool ("done", + &done), GNUNET_PQ_result_spec_end }; @@ -9923,22 +9985,18 @@ postgres_lookup_auditor_status ( GNUNET_PQ_query_param_auto_from_type (auditor_pub), GNUNET_PQ_query_param_end }; - uint8_t enabled8 = 0; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("auditor_url", auditor_url), - GNUNET_PQ_result_spec_auto_from_type ("is_active", - &enabled8), + GNUNET_PQ_result_spec_bool ("is_active", + enabled), GNUNET_PQ_result_spec_end }; - enum GNUNET_DB_QueryStatus qs; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_auditor_status", - params, - rs); - *enabled = (0 != enabled8); - return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_auditor_status", + params, + rs); } @@ -9996,12 +10054,11 @@ postgres_update_auditor (void *cls, bool enabled) { struct PostgresClosure *pg = cls; - uint8_t enabled8 = enabled ? 1 : 0; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (auditor_pub), GNUNET_PQ_query_param_string (auditor_url), GNUNET_PQ_query_param_string (auditor_name), - GNUNET_PQ_query_param_auto_from_type (&enabled8), + GNUNET_PQ_query_param_bool (enabled), GNUNET_PQ_query_param_absolute_time (&change_date), GNUNET_PQ_query_param_end }; @@ -10091,10 +10148,9 @@ postgres_update_wire (void *cls, bool enabled) { struct PostgresClosure *pg = cls; - uint8_t enabled8 = enabled ? 1 : 0; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (payto_uri), - GNUNET_PQ_query_param_auto_from_type (&enabled8), + GNUNET_PQ_query_param_bool (enabled), GNUNET_PQ_query_param_absolute_time (&change_date), GNUNET_PQ_query_param_end }; @@ -11767,7 +11823,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->get_latest_reserve_in_reference = &postgres_get_latest_reserve_in_reference; plugin->get_withdraw_info = &postgres_get_withdraw_info; - plugin->insert_withdraw_info = &postgres_insert_withdraw_info; + // plugin->insert_withdraw_info = &postgres_insert_withdraw_info; + plugin->do_withdraw = &postgres_do_withdraw; + plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check; plugin->get_reserve_history = &postgres_get_reserve_history; plugin->select_withdraw_amounts_by_account = &postgres_select_withdraw_amounts_by_account; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 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", -- cgit v1.2.3