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_CoinSpendPublicKeyP *coin_pub,
|
||||||
const struct TALER_Amount *coin_value,
|
const struct TALER_Amount *coin_value,
|
||||||
const struct TALER_Amount *op_cost,
|
const struct TALER_Amount *op_cost,
|
||||||
@ -273,13 +288,90 @@ TEH_check_coin_balance (struct MHD_Connection *connection,
|
|||||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
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,
|
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||||
tl);
|
tl);
|
||||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
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
|
enum GNUNET_GenericReturnValue
|
||||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||||
const char *name,
|
const char *name,
|
||||||
|
@ -47,6 +47,9 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
|||||||
* insufficient for all transactions associated with the
|
* insufficient for all transactions associated with the
|
||||||
* coin, return a hard error.
|
* 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 connection HTTP connection to report hard errors on
|
||||||
* @param coin_pub coin to analyze
|
* @param coin_pub coin to analyze
|
||||||
* @param coin_value total value of the original coin (by denomination)
|
* @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
|
-- Complete transaction
|
||||||
|
@ -596,6 +596,16 @@ prepare_statements (struct PostgresClosure *pg)
|
|||||||
"lock_withdraw",
|
"lock_withdraw",
|
||||||
"LOCK TABLE reserves_out;",
|
"LOCK TABLE reserves_out;",
|
||||||
0),
|
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
|
/* Used in #postgres_do_withdraw() 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
|
||||||
@ -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
|
* Perform withdraw operation, checking for sufficient balance
|
||||||
* and possibly persisting the withdrawal details.
|
* and possibly persisting the withdrawal details.
|
||||||
@ -11825,7 +11882,7 @@ 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->do_check_coin_balance = &postgres_do_check_coin_balance;
|
||||||
plugin->do_withdraw = &postgres_do_withdraw;
|
plugin->do_withdraw = &postgres_do_withdraw;
|
||||||
plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check;
|
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;
|
||||||
|
@ -2503,18 +2503,27 @@ struct TALER_EXCHANGEDB_Plugin
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store collectable coin under the corresponding hash of the blinded
|
* Check coin balance is sufficient to satisfy balance
|
||||||
* message.
|
* invariants.
|
||||||
*
|
*
|
||||||
* @param cls the @e cls of this struct with the plugin-specific state
|
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||||
* @param collectable corresponding collectable coin (blind signature)
|
* @param coin_pub coin to check
|
||||||
* if a coin is found
|
* @param coin_value value of the coin's denomination (avoids internal lookup)
|
||||||
* @return statement execution status
|
* @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
|
enum GNUNET_DB_QueryStatus
|
||||||
(*insert_withdraw_infoXX)(
|
(*do_check_coin_balance)(
|
||||||
void *cls,
|
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