introduce stored procedure for coin balance check
This commit is contained in:
parent
889625a90f
commit
fba91c63d5
@ -164,8 +164,23 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_check_coin_balance (struct MHD_Connection *connection,
|
||||
/**
|
||||
* Called when we actually know that the balance (was) insufficient.
|
||||
* Re-does the check (slowly) to compute the full error message for
|
||||
* the client.
|
||||
*
|
||||
* @param connection HTTP connection to report hard errors on
|
||||
* @param coin_pub coin to analyze
|
||||
* @param coin_value total value of the original coin (by denomination)
|
||||
* @param op_cost cost of the current operation (for error reporting)
|
||||
* @param check_recoup should we include recoup transactions in the check
|
||||
* @param zombie_required additional requirement that the coin must
|
||||
* be a zombie coin, or also hard failure
|
||||
* @param[out] mhd_ret set to response status code, on hard error only
|
||||
* @return transaction status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
check_coin_balance (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
const struct TALER_Amount *op_cost,
|
||||
@ -273,13 +288,90 @@ TEH_check_coin_balance (struct MHD_Connection *connection,
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* we're good, coin has sufficient funds to be melted */
|
||||
/* This should not happen: The coin has sufficient funds
|
||||
after all!?!? */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_check_coin_balance (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
const struct TALER_Amount *op_cost,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
bool balance_ok = false;
|
||||
bool zombie_ok = false;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = TEH_plugin->do_check_coin_balance (TEH_plugin->cls,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
check_recoup,
|
||||
zombie_required,
|
||||
&balance_ok,
|
||||
&zombie_ok);
|
||||
switch (qs)
|
||||
{
|
||||
case GNUNET_DB_STATUS_HARD_ERROR:
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"check_coin_balance");
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_SOFT_ERROR:
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"check_coin_balance");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
|
||||
/* handled below */
|
||||
break;
|
||||
}
|
||||
if (! zombie_ok)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (balance_ok)
|
||||
return qs;
|
||||
/* balance is not OK, do expensive call to compute full error message */
|
||||
qs = check_coin_balance (connection,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
op_cost,
|
||||
check_recoup,
|
||||
zombie_required,
|
||||
mhd_ret);
|
||||
if (qs < 0)
|
||||
return qs; /* we expected to fail (same check as before!) */
|
||||
GNUNET_break (0); /* stored procedure and individual statements
|
||||
disagree, should be impossible! */
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
|
||||
"stored procedure disagrees with full coin transaction history fetch");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||
const char *name,
|
||||
|
@ -47,6 +47,9 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
* insufficient for all transactions associated with the
|
||||
* coin, return a hard error.
|
||||
*
|
||||
* We first do a "fast" check using a stored procedure, and
|
||||
* only obtain the "full" data on failure (for performance).
|
||||
*
|
||||
* @param connection HTTP connection to report hard errors on
|
||||
* @param coin_pub coin to analyze
|
||||
* @param coin_value total value of the original coin (by denomination)
|
||||
|
@ -899,6 +899,194 @@ COMMENT ON FUNCTION exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4)
|
||||
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION exchange_do_check_coin_balance(
|
||||
IN denom_val INT8, -- value of the denomination of the coin
|
||||
IN denom_frac INT4, -- value of the denomination of the coin
|
||||
IN in_coin_pub BYTEA, -- coin public key
|
||||
IN check_recoup BOOLEAN, -- do we need to check the recoup table?
|
||||
IN zombie_required BOOLEAN, -- do we need a zombie coin?
|
||||
OUT balance_ok BOOLEAN, -- balance satisfied?
|
||||
OUT zombie_ok BOOLEAN) -- zombie satisfied?
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
coin_uuid INT8; -- known_coin_id of coin_pub
|
||||
DECLARE
|
||||
tmp_val INT8; -- temporary result
|
||||
DECLARE
|
||||
tmp_frac INT8; -- temporary result
|
||||
DECLARE
|
||||
spent_val INT8; -- how much of coin was spent?
|
||||
DECLARE
|
||||
spent_frac INT8; -- how much of coin was spent?
|
||||
DECLARE
|
||||
unspent_val INT8; -- how much of coin was refunded?
|
||||
DECLARE
|
||||
unspent_frac INT8; -- how much of coin was refunded?
|
||||
BEGIN
|
||||
|
||||
-- Note: possible future optimization: get the coin_uuid from the previous
|
||||
-- 'ensure_coin_known' and pass that here instead of the coin_pub. Might help
|
||||
-- a tiny bit with performance.
|
||||
SELECT known_coin_id INTO coin_uuid
|
||||
FROM known_coins
|
||||
WHERE coin_pub=in_coin_pub;
|
||||
|
||||
IF NOT FOUND
|
||||
THEN
|
||||
-- coin unknown, should be impossible!
|
||||
balance_ok=FALSE;
|
||||
zombie_ok=FALSE;
|
||||
ASSERT false, 'coin unknown';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
|
||||
spent_val = 0;
|
||||
spent_frac = 0;
|
||||
unspent_val = denom_val;
|
||||
unspent_frac = denom_frac;
|
||||
|
||||
SELECT
|
||||
SUM(amount_with_fee_val) -- overflow here is not plausible
|
||||
,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM deposits
|
||||
WHERE known_coin_id=coin_uuid;
|
||||
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
spent_val = spent_val + tmp_val;
|
||||
spent_frac = spent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
SUM(amount_with_fee_val) -- overflow here is not plausible
|
||||
,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM refresh_commitments
|
||||
WHERE old_known_coin_id=coin_uuid;
|
||||
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
spent_val = spent_val + tmp_val;
|
||||
spent_frac = spent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
|
||||
SELECT
|
||||
SUM(rf.amount_with_fee_val) -- overflow here is not plausible
|
||||
,SUM(CAST(rf.amount_with_fee_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM deposits
|
||||
JOIN refunds rf
|
||||
USING (deposit_serial_id)
|
||||
WHERE
|
||||
known_coin_id=coin_uuid;
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
unspent_val = unspent_val + tmp_val;
|
||||
unspent_frac = unspent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
-- Note: even if 'check_recoup' is true, the tables below
|
||||
-- are in practice likely empty (as they only apply if
|
||||
-- the exchange (ever) had to revoke keys).
|
||||
IF check_recoup
|
||||
THEN
|
||||
|
||||
SELECT
|
||||
SUM(amount_val) -- overflow here is not plausible
|
||||
,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM recoup_refresh
|
||||
WHERE known_coin_id=coin_uuid;
|
||||
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
spent_val = spent_val + tmp_val;
|
||||
spent_frac = spent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
SUM(amount_val) -- overflow here is not plausible
|
||||
,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM recoup
|
||||
WHERE known_coin_id=coin_uuid;
|
||||
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
spent_val = spent_val + tmp_val;
|
||||
spent_frac = spent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
SUM(amount_val) -- overflow here is not plausible
|
||||
,SUM(CAST(amount_frac AS INT8)) -- compute using 64 bits
|
||||
INTO
|
||||
tmp_val
|
||||
,tmp_frac
|
||||
FROM recoup_refresh
|
||||
JOIN refresh_revealed_coins rrc
|
||||
USING (rrc_serial)
|
||||
JOIN refresh_commitments rfc
|
||||
ON (rrc.melt_serial_id = rfc.melt_serial_id)
|
||||
WHERE rfc.old_known_coin_id=coin_uuid;
|
||||
|
||||
IF tmp_val IS NOT NULL
|
||||
THEN
|
||||
unspent_val = unspent_val + tmp_val;
|
||||
unspent_frac = unspent_frac + tmp_frac;
|
||||
END IF;
|
||||
|
||||
IF ( (0 < tmp_val) OR (0 < tmp_frac) )
|
||||
THEN
|
||||
-- There was a transaction that justifies the zombie
|
||||
-- status, clear the flag
|
||||
zombie_required=FALSE;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
|
||||
-- normalize results
|
||||
spent_val = spent_val + spent_frac / 100000000;
|
||||
spent_frac = spent_frac % 100000000;
|
||||
unspent_val = unspent_val + unspent_frac / 100000000;
|
||||
unspent_frac = unspent_frac % 100000000;
|
||||
|
||||
-- Actually check if the coin balance is sufficient. Verbosely. ;-)
|
||||
IF (unspent_val > spent_val)
|
||||
THEN
|
||||
balance_ok=TRUE;
|
||||
ELSE
|
||||
IF (unspent_val = spent_val) AND (unspent_frac >= spent_frac)
|
||||
THEN
|
||||
balance_ok=TRUE;
|
||||
ELSE
|
||||
balance_ok=FALSE;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
zombie_ok = NOT zombie_required;
|
||||
|
||||
END $$;
|
||||
|
||||
COMMENT ON FUNCTION exchange_do_check_coin_balance(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
|
||||
IS 'Checks whether the coin has sufficient balance for all the operations associated with it';
|
||||
|
||||
|
||||
|
||||
|
||||
-- Complete transaction
|
||||
|
@ -596,6 +596,16 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
"lock_withdraw",
|
||||
"LOCK TABLE reserves_out;",
|
||||
0),
|
||||
/* Used in #postgres_do_check_coin_balance() to check
|
||||
a coin's balance */
|
||||
GNUNET_PQ_make_prepare (
|
||||
"call_check_coin_balance",
|
||||
"SELECT "
|
||||
" balance_ok"
|
||||
",zombie_ok"
|
||||
" FROM exchange_do_check_coin_balance"
|
||||
" ($1,$2,$3,$4,$5);",
|
||||
5),
|
||||
/* 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
|
||||
@ -4491,6 +4501,53 @@ postgres_get_withdraw_info (
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check coin balance is sufficient to satisfy balance
|
||||
* invariants.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param coin_pub coin to check
|
||||
* @param coin_value value of the coin's denomination (avoids internal lookup)
|
||||
* @param check_recoup include recoup and recoup_refresh tables in calculation
|
||||
* @param zombie_required additionally require coin to be a zombie coin
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @param[out] zombie_ok set to true if the zombie requirement was satisfied
|
||||
* @return query execution status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
postgres_do_check_coin_balance (
|
||||
void *cls,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
bool *balance_ok,
|
||||
bool *zombie_ok)
|
||||
{
|
||||
struct PostgresClosure *pg = cls;
|
||||
struct GNUNET_PQ_QueryParam params[] = {
|
||||
TALER_PQ_query_param_amount (coin_value),
|
||||
GNUNET_PQ_query_param_auto_from_type (coin_pub),
|
||||
GNUNET_PQ_query_param_bool (check_recoup),
|
||||
GNUNET_PQ_query_param_bool (zombie_required),
|
||||
GNUNET_PQ_query_param_end
|
||||
};
|
||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||
GNUNET_PQ_result_spec_bool ("balance_ok",
|
||||
balance_ok),
|
||||
GNUNET_PQ_result_spec_bool ("zombie_ok",
|
||||
zombie_ok),
|
||||
GNUNET_PQ_result_spec_end
|
||||
};
|
||||
|
||||
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
|
||||
"call_check_coin_balance",
|
||||
params,
|
||||
rs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform withdraw operation, checking for sufficient balance
|
||||
* and possibly persisting the withdrawal details.
|
||||
@ -11825,7 +11882,7 @@ 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->do_check_coin_balance = &postgres_do_check_coin_balance;
|
||||
plugin->do_withdraw = &postgres_do_withdraw;
|
||||
plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check;
|
||||
plugin->get_reserve_history = &postgres_get_reserve_history;
|
||||
|
@ -2503,18 +2503,27 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
|
||||
|
||||
/**
|
||||
* Store collectable coin under the corresponding hash of the blinded
|
||||
* message.
|
||||
* Check coin balance is sufficient to satisfy balance
|
||||
* invariants.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param collectable corresponding collectable coin (blind signature)
|
||||
* if a coin is found
|
||||
* @return statement execution status
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param coin_pub coin to check
|
||||
* @param coin_value value of the coin's denomination (avoids internal lookup)
|
||||
* @param check_recoup include recoup and recoup_refresh tables in calculation
|
||||
* @param zombie_required additionally require coin to be a zombie coin
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @param[out] zombie_ok set to true if the zombie requirement was satisfied
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_withdraw_infoXX)(
|
||||
(*do_check_coin_balance)(
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
bool *balance_ok,
|
||||
bool *zombie_ok);
|
||||
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user