From 36a8ecd4c47eaaab767d679564aa4e87166fa361 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 5 Jul 2022 14:25:30 +0200 Subject: -implemented bounded history for reserve status requests --- .../taler-exchange-httpd_reserves_status.c | 33 ++- src/exchangedb/plugin_exchangedb_postgres.c | 224 +++++++++++++++++++-- src/include/taler_exchangedb_plugin.h | 8 +- 3 files changed, 247 insertions(+), 18 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_status.c b/src/exchange/taler-exchange-httpd_reserves_status.c index 5b7becb94..ff8a65c23 100644 --- a/src/exchange/taler-exchange-httpd_reserves_status.c +++ b/src/exchange/taler-exchange-httpd_reserves_status.c @@ -58,6 +58,18 @@ struct ReserveStatusContext */ struct TALER_EXCHANGEDB_KycStatus kyc; + /** + * Sum of incoming transactions within the returned history. + * (currently not used). + */ + struct TALER_Amount balance_in; + + /** + * Sum of outgoing transactions within the returned history. + * (currently not used). + */ + struct TALER_Amount balance_out; + /** * Current reserve balance. */ @@ -135,10 +147,11 @@ reserve_status_transaction (void *cls, "inselect_wallet_status"); return qs; } - qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, - rsc->reserve_pub, - &rsc->balance, - &rsc->rh); + qs = TEH_plugin->get_reserve_status (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->balance_in, + &rsc->balance_out, + &rsc->rh); if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); @@ -148,6 +161,18 @@ reserve_status_transaction (void *cls, TALER_EC_GENERIC_DB_FETCH_FAILED, "get_reserve_status"); } + qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->balance); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_balance"); + } return qs; } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 63f075210..b9debfa44 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -815,6 +815,42 @@ prepare_statements (struct PostgresClosure *pg) " SELECT wire_source_h_payto FROM ri " "); ", 1), + /* Used in #postgres_get_reserve_status() to obtain inbound transactions + for a reserve */ + GNUNET_PQ_make_prepare ( + "reserves_in_get_transactions_truncated", + /* + "SELECT" + " wire_reference" + ",credit_val" + ",credit_frac" + ",execution_date" + ",payto_uri AS sender_account_details" + " FROM reserves_in" + " JOIN wire_targets" + " ON (wire_source_h_payto = wire_target_h_payto)" + " WHERE reserve_pub=$1" + " AND execution_date>=$2;", + */ + "WITH ri AS MATERIALIZED ( " + " SELECT * " + " FROM reserves_in " + " WHERE reserve_pub = $1 " + ") " + "SELECT " + " wire_reference " + " ,credit_val " + " ,credit_frac " + " ,execution_date " + " ,payto_uri AS sender_account_details " + "FROM wire_targets " + "JOIN ri " + " ON (wire_target_h_payto = wire_source_h_payto) " + "WHERE execution_date >= $2" + " AND wire_target_h_payto = ( " + " SELECT wire_source_h_payto FROM ri " + "); ", + 2), /* 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 @@ -1020,6 +1056,58 @@ prepare_statements (struct PostgresClosure *pg) "JOIN denominations denom " " ON (ro.denominations_serial = denom.denominations_serial); ", 1), + /* Used during #postgres_get_reserve_status() to + obtain all of the /reserve/withdraw operations that + have been performed on a given reserve. (i.e. to + demonstrate double-spending) */ + GNUNET_PQ_make_prepare ( + "get_reserves_out_truncated", + /* + "SELECT" + " ro.h_blind_ev" + ",denom.denom_pub_hash" + ",ro.denom_sig" + ",ro.reserve_sig" + ",ro.execution_date" + ",ro.amount_with_fee_val" + ",ro.amount_with_fee_frac" + ",denom.fee_withdraw_val" + ",denom.fee_withdraw_frac" + " FROM reserves res" + " JOIN reserves_out_by_reserve ror" + " ON (res.reserve_uuid = ror.reserve_uuid)" + " JOIN reserves_out ro" + " ON (ro.h_blind_ev = ror.h_blind_ev)" + " JOIN denominations denom" + " ON (ro.denominations_serial = denom.denominations_serial)" + " WHERE res.reserve_pub=$1" + " AND execution_date>=$2;", + */ + "WITH robr AS MATERIALIZED ( " + " SELECT h_blind_ev " + " FROM reserves_out_by_reserve " + " WHERE reserve_uuid= ( " + " SELECT reserve_uuid " + " FROM reserves " + " WHERE reserve_pub = $1 " + " ) " + ") SELECT " + " ro.h_blind_ev " + " ,denom.denom_pub_hash " + " ,ro.denom_sig " + " ,ro.reserve_sig " + " ,ro.execution_date " + " ,ro.amount_with_fee_val " + " ,ro.amount_with_fee_frac " + " ,denom.fee_withdraw_val " + " ,denom.fee_withdraw_frac " + "FROM robr " + "JOIN reserves_out ro " + " ON (ro.h_blind_ev = robr.h_blind_ev) " + "JOIN denominations denom " + " ON (ro.denominations_serial = denom.denominations_serial)" + " WHERE ro.execution_date>=$2;", + 2), /* Used in #postgres_select_withdrawals_above_serial_id() */ GNUNET_PQ_make_prepare ( @@ -2240,6 +2328,51 @@ prepare_statements (struct PostgresClosure *pg) " JOIN exchange_do_recoup_by_reserve($1) robr" " USING (denominations_serial);", 1), + /* Used in #postgres_get_reserve_status() to obtain recoup transactions + for a reserve - query optimization should be disabled i.e. + BEGIN; SET LOCAL join_collapse_limit=1; query; COMMIT; */ + GNUNET_PQ_make_prepare ( + "recoup_by_reserve_truncated", + /* + "SELECT" + " recoup.coin_pub" + ",recoup.coin_sig" + ",recoup.coin_blind" + ",recoup.amount_val" + ",recoup.amount_frac" + ",recoup.recoup_timestamp" + ",denominations.denom_pub_hash" + ",known_coins.denom_sig" + " FROM denominations" + " JOIN (known_coins" + " JOIN recoup " + " ON (recoup.coin_pub = known_coins.coin_pub))" + " ON (known_coins.denominations_serial = denominations.denominations_serial)" + " WHERE recoup_timestamp>=$2" + " AND recoup.coin_pub" + " IN (SELECT coin_pub" + " FROM recoup_by_reserve" + " JOIN (reserves_out" + " JOIN (reserves_out_by_reserve" + " JOIN reserves" + " ON (reserves.reserve_uuid = reserves_out_by_reserve.reserve_uuid))" + " ON (reserves_out_by_reserve.h_blind_ev = reserves_out.h_blind_ev))" + " ON (recoup_by_reserve.reserve_out_serial_id = reserves_out.reserve_out_serial_id)" + " WHERE reserves.reserve_pub=$1);", + */ + "SELECT robr.coin_pub " + " ,robr.coin_sig " + " ,robr.coin_blind " + " ,robr.amount_val " + " ,robr.amount_frac " + " ,robr.recoup_timestamp " + " ,denominations.denom_pub_hash " + " ,robr.denom_sig " + "FROM denominations " + " JOIN exchange_do_recoup_by_reserve($1) robr" + " USING (denominations_serial)" + " WHERE recoup_timestamp>=$2;", + 2), /* Used in #postgres_get_coin_transactions() to obtain recoup transactions affecting old coins of refreshed coins */ GNUNET_PQ_make_prepare ( @@ -2282,6 +2415,23 @@ prepare_statements (struct PostgresClosure *pg) " USING (wire_target_h_payto)" " WHERE reserve_pub=$1;", 1), + /* Used in #postgres_get_reserve_status() */ + GNUNET_PQ_make_prepare ( + "close_by_reserve_truncated", + "SELECT" + " amount_val" + ",amount_frac" + ",closing_fee_val" + ",closing_fee_frac" + ",execution_date" + ",payto_uri AS receiver_account" + ",wtid" + " FROM reserves_close" + " JOIN wire_targets" + " USING (wire_target_h_payto)" + " WHERE reserve_pub=$1" + " AND execution_date>=$2;", + 2), /* Used in #postgres_get_reserve_history() */ GNUNET_PQ_make_prepare ( "merge_by_reserve", @@ -2311,6 +2461,36 @@ prepare_statements (struct PostgresClosure *pg) " AND pr.finished" " AND NOT pr.refunded;", 1), + /* Used in #postgres_get_reserve_status() */ + GNUNET_PQ_make_prepare ( + "merge_by_reserve_truncated", + "SELECT" + " pr.amount_with_fee_val" + ",pr.amount_with_fee_frac" + ",pr.balance_val" + ",pr.balance_frac" + ",pr.purse_fee_val" + ",pr.purse_fee_frac" + ",pr.h_contract_terms" + ",pr.merge_pub" + ",am.reserve_sig" + ",pm.purse_pub" + ",pm.merge_timestamp" + ",pr.purse_expiration" + ",pr.age_limit" + ",pr.flags" + " FROM purse_merges pm" + " JOIN purse_requests pr" + " USING (purse_pub)" + " JOIN account_merges am" + " ON (am.purse_pub = pm.purse_pub AND" + " am.reserve_pub = pm.reserve_pub)" + " WHERE pm.reserve_pub=$1" + " AND pm.merge_timestamp >= $2" + " AND pm.partner_serial_id=0" /* must be local! */ + " AND pr.finished" + " AND NOT pr.refunded;", + 2), /* Used in #postgres_get_reserve_history() */ GNUNET_PQ_make_prepare ( "history_by_reserve", @@ -2322,6 +2502,18 @@ prepare_statements (struct PostgresClosure *pg) " FROM history_requests" " WHERE reserve_pub=$1;", 1), + /* Used in #postgres_get_reserve_status() */ + GNUNET_PQ_make_prepare ( + "history_by_reserve_truncated", + "SELECT" + " history_fee_val" + ",history_fee_frac" + ",request_timestamp" + ",reserve_sig" + " FROM history_requests" + " WHERE reserve_pub=$1" + " AND request_timestamp>=$2;", + 2), /* Used in #postgres_get_expired_reserves() */ GNUNET_PQ_make_prepare ( "get_expired_reserves", @@ -7031,14 +7223,18 @@ postgres_get_reserve_history (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] balance_in set to the total of inbound + * transactions in the returned history + * @param[out] balance_out set to the total of outbound + * transactions in the returned history * @param[out] rhp set to known transaction history (NULL if reserve is unknown) * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_get_reserve_status (void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, - struct TALER_Amount *balance, + struct TALER_Amount *balance_in, + struct TALER_Amount *balance_out, struct TALER_EXCHANGEDB_ReserveHistory **rhp) { struct PostgresClosure *pg = cls; @@ -7055,33 +7251,39 @@ postgres_get_reserve_status (void *cls, GNUNET_PQ_PostgresResultHandler cb; } work[] = { /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */ - { "reserves_in_get_transactions", + { "reserves_in_get_transactions_truncated", add_bank_to_exchange }, /** #TALER_EXCHANGEDB_RO_WITHDRAW_COIN */ - { "get_reserves_out", + { "get_reserves_out_truncated", &add_withdraw_coin }, /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */ - { "recoup_by_reserve", + { "recoup_by_reserve_truncated", &add_recoup }, /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */ - { "close_by_reserve", + { "close_by_reserve_truncated", &add_exchange_to_bank }, /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */ - { "merge_by_reserve", + { "merge_by_reserve_truncated", &add_p2p_merge }, /** #TALER_EXCHANGEDB_RO_HISTORY_REQUEST */ - { "history_by_reserve", + { "history_by_reserve_truncated", &add_history_requests }, /* List terminator */ { NULL, NULL } }; enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute timelimit; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_absolute_time (&timelimit), GNUNET_PQ_query_param_end }; + timelimit = GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_get (), + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS, + 5)); /* FIXME: actually implement reserve history truncation logic! */ rhc.reserve_pub = reserve_pub; rhc.rh = NULL; @@ -7119,10 +7321,8 @@ postgres_get_reserve_status (void *cls, } } *rhp = rhc.rh; - GNUNET_assert (0 <= - TALER_amount_subtract (balance, - &rhc.balance_in, - &rhc.balance_out)); + *balance_in = rhc.balance_in; + *balance_out = rhc.balance_out; return qs; } diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 4a871786f..82c46cdcb 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -3442,14 +3442,18 @@ struct TALER_EXCHANGEDB_Plugin * * @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] balance_in set to the total of inbound + * transactions in the returned history + * @param[out] balance_out set to the total of outbound + * transactions in the returned history * @param[out] rhp set to known transaction history (NULL if reserve is unknown) * @return transaction status */ enum GNUNET_DB_QueryStatus (*get_reserve_status)(void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, - struct TALER_Amount *balance, + struct TALER_Amount *balance_in, + struct TALER_Amount *balance_out, struct TALER_EXCHANGEDB_ReserveHistory **rhp); -- cgit v1.2.3