From 1ee69f6f1d93349a3e576a86c1d93d23ccec28ce Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 13 Oct 2022 22:43:22 +0200 Subject: -towards returning reserve open deposits in coin history --- src/exchangedb/Makefile.am | 2 + src/exchangedb/pg_get_coin_transactions.c | 866 ++++++++++++++++++++++++++++ src/exchangedb/pg_get_coin_transactions.h | 44 ++ src/exchangedb/plugin_exchangedb_common.c | 29 +- src/exchangedb/plugin_exchangedb_common.h | 51 ++ src/exchangedb/plugin_exchangedb_postgres.c | 807 +------------------------- src/include/taler_exchangedb_plugin.h | 38 +- 7 files changed, 1023 insertions(+), 814 deletions(-) create mode 100644 src/exchangedb/pg_get_coin_transactions.c create mode 100644 src/exchangedb/pg_get_coin_transactions.h create mode 100644 src/exchangedb/plugin_exchangedb_common.h diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index e4094cd71..059c0136a 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -68,8 +68,10 @@ plugin_LTLIBRARIES = \ endif libtaler_plugin_exchangedb_postgres_la_SOURCES = \ + plugin_exchangedb_common.c plugin_exchangedb_common.h \ plugin_exchangedb_postgres.c pg_helper.h \ pg_do_reserve_open.c pg_do_reserve_open.h \ + pg_get_coin_transactions.c pg_get_coin_transactions.h \ pg_get_expired_reserves.c pg_get_expired_reserves.h \ pg_get_unfinished_close_requests.c pg_get_unfinished_close_requests.h \ pg_insert_close_request.c pg_insert_close_request.h \ diff --git a/src/exchangedb/pg_get_coin_transactions.c b/src/exchangedb/pg_get_coin_transactions.c new file mode 100644 index 000000000..54dce7f6f --- /dev/null +++ b/src/exchangedb/pg_get_coin_transactions.c @@ -0,0 +1,866 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file pg_get_coin_transactions.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_get_coin_transactions.h" +#include "pg_helper.h" +#include "plugin_exchangedb_common.h" + + +/** + * Closure for callbacks called from #postgres_get_coin_transactions() + */ +struct CoinHistoryContext +{ + /** + * Head of the coin's history list. + */ + struct TALER_EXCHANGEDB_TransactionList *head; + + /** + * Public key of the coin we are building the history for. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pub; + + /** + * Closure for all callbacks of this database plugin. + */ + void *db_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Set to 'true' if the transaction failed. + */ + bool failed; + + /** + * Set to 'true' if we found a deposit or melt (for invariant check). + */ + bool have_deposit_or_melt; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_deposit (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; i < num_results; i++) + { + struct TALER_EXCHANGEDB_DepositListEntry *deposit; + struct TALER_EXCHANGEDB_TransactionList *tl; + uint64_t serial_id; + + chc->have_deposit_or_melt = true; + deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); + { + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &deposit->amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", + &deposit->deposit_fee), + GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", + &deposit->h_denom_pub), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + &deposit->h_age_commitment), + &deposit->no_age_commitment), + GNUNET_PQ_result_spec_timestamp ("wallet_timestamp", + &deposit->timestamp), + GNUNET_PQ_result_spec_timestamp ("refund_deadline", + &deposit->refund_deadline), + GNUNET_PQ_result_spec_timestamp ("wire_deadline", + &deposit->wire_deadline), + GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", + &deposit->merchant_pub), + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &deposit->h_contract_terms), + GNUNET_PQ_result_spec_auto_from_type ("wire_salt", + &deposit->wire_salt), + GNUNET_PQ_result_spec_string ("payto_uri", + &deposit->receiver_wire_account), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &deposit->csig), + GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", + &serial_id), + GNUNET_PQ_result_spec_auto_from_type ("done", + &deposit->done), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (deposit); + chc->failed = true; + return; + } + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_DEPOSIT; + tl->details.deposit = deposit; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_purse_deposit (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; i < num_results; i++) + { + struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit; + struct TALER_EXCHANGEDB_TransactionList *tl; + uint64_t serial_id; + + chc->have_deposit_or_melt = true; + deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry); + { + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &deposit->amount), + TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", + &deposit->deposit_fee), + GNUNET_PQ_result_spec_auto_from_type ("purse_pub", + &deposit->purse_pub), + GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id", + &serial_id), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("partner_base_url", + &deposit->exchange_base_url), + NULL), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &deposit->coin_sig), + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + &deposit->h_age_commitment), + GNUNET_PQ_result_spec_bool ("refunded", + &deposit->refunded), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (deposit); + chc->failed = true; + return; + } + deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment); + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT; + tl->details.purse_deposit = deposit; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_melt (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; ihave_deposit_or_melt = true; + melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry); + { + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("rc", + &melt->rc), + /* oldcoin_index not needed */ + GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", + &melt->h_denom_pub), + GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", + &melt->coin_sig), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &melt->amount_with_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh", + &melt->melt_fee), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + &melt->h_age_commitment), + &melt->no_age_commitment), + GNUNET_PQ_result_spec_uint64 ("melt_serial_id", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (melt); + chc->failed = true; + return; + } + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_MELT; + tl->details.melt = melt; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_refund (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; imerchant_pub), + GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", + &refund->merchant_sig), + GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + &refund->h_contract_terms), + GNUNET_PQ_result_spec_uint64 ("rtransaction_id", + &refund->rtransaction_id), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &refund->refund_amount), + TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", + &refund->refund_fee), + GNUNET_PQ_result_spec_uint64 ("refund_serial_id", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (refund); + chc->failed = true; + return; + } + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_REFUND; + tl->details.refund = refund; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_old_coin_recoup (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; icoin.coin_pub), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &recoup->coin_sig), + GNUNET_PQ_result_spec_auto_from_type ("coin_blind", + &recoup->coin_blind), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &recoup->value), + GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", + &recoup->timestamp), + GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", + &recoup->coin.denom_pub_hash), + TALER_PQ_result_spec_denom_sig ("denom_sig", + &recoup->coin.denom_sig), + GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (recoup); + chc->failed = true; + return; + } + recoup->old_coin_pub = *chc->coin_pub; + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP; + tl->details.old_coin_recoup = recoup; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_recoup (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; ireserve_pub), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &recoup->coin_sig), + GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", + &recoup->h_denom_pub), + GNUNET_PQ_result_spec_auto_from_type ("coin_blind", + &recoup->coin_blind), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &recoup->value), + GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", + &recoup->timestamp), + GNUNET_PQ_result_spec_uint64 ("recoup_uuid", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (recoup); + chc->failed = true; + return; + } + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_RECOUP; + tl->details.recoup = recoup; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_recoup_refresh (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; iold_coin_pub), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &recoup->coin_sig), + GNUNET_PQ_result_spec_auto_from_type ("coin_blind", + &recoup->coin_blind), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &recoup->value), + GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", + &recoup->timestamp), + GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", + &recoup->coin.denom_pub_hash), + TALER_PQ_result_spec_denom_sig ("denom_sig", + &recoup->coin.denom_sig), + GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (recoup); + chc->failed = true; + return; + } + recoup->coin.coin_pub = *chc->coin_pub; + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH; + tl->details.recoup_refresh = recoup; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct CoinHistoryContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +add_coin_reserve_open (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct CoinHistoryContext *chc = cls; + struct PostgresClosure *pg = chc->pg; + + for (unsigned int i = 0; ireserve_sig), + GNUNET_PQ_result_spec_auto_from_type ("coin_sig", + &role->coin_sig), + TALER_PQ_RESULT_SPEC_AMOUNT ("contribution", + &role->coin_contribution), + GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid", + &serial_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + GNUNET_free (role); + chc->failed = true; + return; + } + } + tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); + tl->next = chc->head; + tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN; + tl->details.reserve_open = role; + tl->serial_id = serial_id; + chc->head = tl; + } +} + + +/** + * Work we need to do. + */ +struct Work +{ + /** + * SQL prepared statement name. + */ + const char *statement; + + /** + * Function to call to handle the result(s). + */ + GNUNET_PQ_PostgresResultHandler cb; +}; + + +enum GNUNET_DB_QueryStatus +TEH_PG_get_coin_transactions ( + void *cls, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_EXCHANGEDB_TransactionList **tlp) +{ + struct PostgresClosure *pg = cls; + static const struct Work work[] = { + /** #TALER_EXCHANGEDB_TT_DEPOSIT */ + { "get_deposit_with_coin_pub", + &add_coin_deposit }, + /** #TALER_EXCHANGEDB_TT_MELT */ + { "get_refresh_session_by_coin", + &add_coin_melt }, + /** #TALER_EXCHANGEDB_TT_PURSE_DEPOSIT */ + { "get_purse_deposit_by_coin_pub", + &add_coin_purse_deposit }, + /** #TALER_EXCHANGEDB_TT_REFUND */ + { "get_refunds_by_coin", + &add_coin_refund }, + /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */ + { "recoup_by_old_coin", + &add_old_coin_recoup }, + /** #TALER_EXCHANGEDB_TT_RECOUP */ + { "recoup_by_coin", + &add_coin_recoup }, + /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */ + { "recoup_by_refreshed_coin", + &add_coin_recoup_refresh }, + /** #TALER_EXCHANGEDB_TT_RESERVE_OPEN */ + { "reserve_open_by_coin", + &add_coin_reserve_open }, + { NULL, NULL } + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + struct CoinHistoryContext chc = { + .head = NULL, + .coin_pub = coin_pub, + .pg = pg, + .db_cls = cls + }; + + PREPARE (pg, + "get_deposit_with_coin_pub", + "SELECT" + " dep.amount_with_fee_val" + ",dep.amount_with_fee_frac" + ",denoms.fee_deposit_val" + ",denoms.fee_deposit_frac" + ",denoms.denom_pub_hash" + ",kc.age_commitment_hash" + ",dep.wallet_timestamp" + ",dep.refund_deadline" + ",dep.wire_deadline" + ",dep.merchant_pub" + ",dep.h_contract_terms" + ",dep.wire_salt" + ",wt.payto_uri" + ",dep.coin_sig" + ",dep.deposit_serial_id" + ",dep.done" + " FROM deposits dep" + " JOIN wire_targets wt" + " USING (wire_target_h_payto)" + " JOIN known_coins kc" + " ON (kc.coin_pub = dep.coin_pub)" + " JOIN denominations denoms" + " USING (denominations_serial)" + " WHERE dep.coin_pub=$1;"); + PREPARE (pg, + "get_refresh_session_by_coin", + "SELECT" + " rc" + ",old_coin_sig" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",denoms.denom_pub_hash" + ",denoms.fee_refresh_val" + ",denoms.fee_refresh_frac" + ",kc.age_commitment_hash" + ",melt_serial_id" + " FROM refresh_commitments" + " JOIN known_coins kc" + " ON (refresh_commitments.old_coin_pub = kc.coin_pub)" + " JOIN denominations denoms" + " USING (denominations_serial)" + " WHERE old_coin_pub=$1;"); + PREPARE (pg, + "get_purse_deposit_by_coin_pub", + "SELECT" + " partner_base_url" + ",pd.amount_with_fee_val" + ",pd.amount_with_fee_frac" + ",denoms.fee_deposit_val" + ",denoms.fee_deposit_frac" + ",pd.purse_pub" + ",kc.age_commitment_hash" + ",pd.coin_sig" + ",pd.purse_deposit_serial_id" + ",pr.refunded" + " FROM purse_deposits pd" + " LEFT JOIN partners" + " USING (partner_serial_id)" + " JOIN purse_requests pr" + " USING (purse_pub)" + " JOIN known_coins kc" + " ON (pd.coin_pub = kc.coin_pub)" + " JOIN denominations denoms" + " USING (denominations_serial)" + // FIXME: use to-be-created materialized index + // on coin_pub (query crosses partitions!) + " WHERE pd.coin_pub=$1;"); + PREPARE (pg, + "get_refunds_by_coin", + "SELECT" + " dep.merchant_pub" + ",ref.merchant_sig" + ",dep.h_contract_terms" + ",ref.rtransaction_id" + ",ref.amount_with_fee_val" + ",ref.amount_with_fee_frac" + ",denom.fee_refund_val " + ",denom.fee_refund_frac " + ",ref.refund_serial_id" + " FROM refunds ref" + " JOIN deposits dep" + " ON (ref.coin_pub = dep.coin_pub AND ref.deposit_serial_id = dep.deposit_serial_id)" + " JOIN known_coins kc" + " ON (ref.coin_pub = kc.coin_pub)" + " JOIN denominations denom" + " USING (denominations_serial)" + " WHERE ref.coin_pub=$1;"); + PREPARE (pg, + "recoup_by_old_coin", + "SELECT" + " coins.coin_pub" + ",coin_sig" + ",coin_blind" + ",amount_val" + ",amount_frac" + ",recoup_timestamp" + ",denoms.denom_pub_hash" + ",coins.denom_sig" + ",recoup_refresh_uuid" + " FROM recoup_refresh" + " JOIN known_coins coins" + " USING (coin_pub)" + " JOIN denominations denoms" + " USING (denominations_serial)" + " WHERE rrc_serial IN" + " (SELECT rrc.rrc_serial" + " FROM refresh_commitments" + " JOIN refresh_revealed_coins rrc" + " USING (melt_serial_id)" + " WHERE old_coin_pub=$1);"); + PREPARE (pg, + "recoup_by_coin", + "SELECT" + " reserves.reserve_pub" + ",denoms.denom_pub_hash" + ",coin_sig" + ",coin_blind" + ",amount_val" + ",amount_frac" + ",recoup_timestamp" + ",recoup_uuid" + " FROM recoup rcp" + /* NOTE: suboptimal JOIN follows: crosses shards! + Could theoretically be improved via a materialized + index. But likely not worth it (query is rare and + number of reserve shards might be limited) */ + " JOIN reserves_out ro" + " USING (reserve_out_serial_id)" + " JOIN reserves" + " USING (reserve_uuid)" + " JOIN known_coins coins" + " USING (coin_pub)" + " JOIN denominations denoms" + " ON (denoms.denominations_serial = coins.denominations_serial)" + " WHERE coins.coin_pub=$1;"); + /* Used in #postgres_get_coin_transactions() to obtain recoup transactions + for a refreshed coin */ + PREPARE (pg, + "recoup_by_refreshed_coin", + "SELECT" + " old_coins.coin_pub AS old_coin_pub" + ",coin_sig" + ",coin_blind" + ",amount_val" + ",amount_frac" + ",recoup_timestamp" + ",denoms.denom_pub_hash" + ",coins.denom_sig" + ",recoup_refresh_uuid" + " FROM recoup_refresh" + " JOIN refresh_revealed_coins rrc" + " USING (rrc_serial)" + " JOIN refresh_commitments rfc" + " ON (rrc.melt_serial_id = rfc.melt_serial_id)" + " JOIN known_coins old_coins" + " ON (rfc.old_coin_pub = old_coins.coin_pub)" + " JOIN known_coins coins" + " ON (recoup_refresh.coin_pub = coins.coin_pub)" + " JOIN denominations denoms" + " ON (denoms.denominations_serial = coins.denominations_serial)" + " WHERE coins.coin_pub=$1;"); + PREPARE (pg, + "reserve_open_by_coin", + "SELECT" + " reserve_open_deposit_uuid" + ",coin_sig" + ",reserve_sig" + ",contribution_val" + ",contribution_frac" + " FROM reserves_open_deposits" + " WHERE coin_pub=$1;"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Getting transactions for coin %s\n", + TALER_B2S (coin_pub)); + for (unsigned int i = 0; NULL != work[i].statement; i++) + { + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + work[i].statement, + params, + work[i].cb, + &chc); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Coin %s yielded %d transactions of type %s\n", + TALER_B2S (coin_pub), + qs, + work[i].statement); + if ( (0 > qs) || + (chc.failed) ) + { + if (NULL != chc.head) + TEH_COMMON_free_coin_transaction_list (cls, + chc.head); + *tlp = NULL; + if (chc.failed) + qs = GNUNET_DB_STATUS_HARD_ERROR; + return qs; + } + } + *tlp = chc.head; + if (NULL == chc.head) + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} diff --git a/src/exchangedb/pg_get_coin_transactions.h b/src/exchangedb/pg_get_coin_transactions.h new file mode 100644 index 000000000..c95fd0947 --- /dev/null +++ b/src/exchangedb/pg_get_coin_transactions.h @@ -0,0 +1,44 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file pg_get_coin_transactions.h + * @brief implementation of the get_coin_transactions function + * @author Christian Grothoff + */ +#ifndef PG_GET_COIN_TRANSACTIONS_H +#define PG_GET_COIN_TRANSACTIONS_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Compile a list of all (historic) transactions performed with the given coin + * (/refresh/melt, /deposit, /refund and /recoup operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param coin_pub coin to investigate + * @param[out] tlp set to list of transactions, NULL if coin is fresh + * @return database transaction status + */ +enum GNUNET_DB_QueryStatus +TEH_PG_get_coin_transactions ( + void *cls, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_EXCHANGEDB_TransactionList **tlp); + +#endif diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c index ca5903501..10263adff 100644 --- a/src/exchangedb/plugin_exchangedb_common.c +++ b/src/exchangedb/plugin_exchangedb_common.c @@ -19,16 +19,14 @@ * included in each plugin. * @author Christian Grothoff */ +#include "platform.h" +#include "plugin_exchangedb_common.h" -/** - * Free memory associated with the given reserve history. - * - * @param cls the @e cls of this struct with the plugin-specific state (unused) - * @param rh history to free. - */ -static void -common_free_reserve_history (void *cls, - struct TALER_EXCHANGEDB_ReserveHistory *rh) + +void +TEH_COMMON_free_reserve_history ( + void *cls, + struct TALER_EXCHANGEDB_ReserveHistory *rh) { (void) cls; while (NULL != rh) @@ -99,15 +97,10 @@ common_free_reserve_history (void *cls, } -/** - * Free linked list of transactions. - * - * @param cls the @e cls of this struct with the plugin-specific state (unused) - * @param tl list to free - */ -static void -common_free_coin_transaction_list (void *cls, - struct TALER_EXCHANGEDB_TransactionList *tl) +void +TEH_COMMON_free_coin_transaction_list ( + void *cls, + struct TALER_EXCHANGEDB_TransactionList *tl) { (void) cls; while (NULL != tl) diff --git a/src/exchangedb/plugin_exchangedb_common.h b/src/exchangedb/plugin_exchangedb_common.h new file mode 100644 index 000000000..0355c44ab --- /dev/null +++ b/src/exchangedb/plugin_exchangedb_common.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file plugin_exchangedb_common.h + * @brief implementation of database-independent functions + * @author Christian Grothoff + */ +#ifndef PLUGIN_EXCHANGEDB_COMMON_H +#define PLUGIN_EXCHANGEDB_COMMON_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + +/** + * Free memory associated with the given reserve history. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param[in] rh history to free. + */ +void +TEH_COMMON_free_reserve_history ( + void *cls, + struct TALER_EXCHANGEDB_ReserveHistory *rh); + + +/** + * Free linked list of transactions. + * + * @param cls the @e cls of this struct with the plugin-specific state (unused) + * @param[in] tl list to free + */ +void +TEH_COMMON_free_coin_transaction_list ( + void *cls, + struct TALER_EXCHANGEDB_TransactionList *tl); + +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 9bf421553..940523b76 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -29,8 +29,10 @@ #include "taler_util.h" #include "taler_json_lib.h" #include "taler_exchangedb_plugin.h" +#include "plugin_exchangedb_common.h" #include "pg_helper.h" #include "pg_do_reserve_open.h" +#include "pg_get_coin_transactions.h" #include "pg_get_expired_reserves.h" #include "pg_get_unfinished_close_requests.h" #include "pg_insert_close_request.h" @@ -45,7 +47,6 @@ #include #include -#include "plugin_exchangedb_common.c" /** * Set to 1 to enable Postgres auto_explain module. This will @@ -1177,53 +1178,6 @@ prepare_statements (struct PostgresClosure *pg) " ON (kc.denominations_serial = denom.denominations_serial)" " WHERE melt_serial_id>=$1" " ORDER BY melt_serial_id ASC;"), - /* Query the 'refresh_commitments' by coin public key, - used in #postgres_get_coin_transactions() */ - GNUNET_PQ_make_prepare ( - "get_refresh_session_by_coin", - "SELECT" - " rc" - ",old_coin_sig" - ",amount_with_fee_val" - ",amount_with_fee_frac" - ",denoms.denom_pub_hash" - ",denoms.fee_refresh_val" - ",denoms.fee_refresh_frac" - ",kc.age_commitment_hash" - ",melt_serial_id" - " FROM refresh_commitments" - " JOIN known_coins kc" - " ON (refresh_commitments.old_coin_pub = kc.coin_pub)" - " JOIN denominations denoms" - " USING (denominations_serial)" - " WHERE old_coin_pub=$1;"), - /* Find purse deposits by coin, - used in #postgres_get_coin_transactions() */ - GNUNET_PQ_make_prepare ( - "get_purse_deposit_by_coin_pub", - "SELECT" - " partner_base_url" - ",pd.amount_with_fee_val" - ",pd.amount_with_fee_frac" - ",denoms.fee_deposit_val" - ",denoms.fee_deposit_frac" - ",pd.purse_pub" - ",kc.age_commitment_hash" - ",pd.coin_sig" - ",pd.purse_deposit_serial_id" - ",pr.refunded" - " FROM purse_deposits pd" - " LEFT JOIN partners" - " USING (partner_serial_id)" - " JOIN purse_requests pr" - " USING (purse_pub)" - " JOIN known_coins kc" - " ON (pd.coin_pub = kc.coin_pub)" - " JOIN denominations denoms" - " USING (denominations_serial)" - // FIXME: use to-be-created materialized index - // on coin_pub (query crosses partitions!) - " WHERE pd.coin_pub=$1;"), /* Store information about the desired denominations for a refresh operation, used in #postgres_insert_refresh_reveal() */ GNUNET_PQ_make_prepare ( @@ -1287,26 +1241,6 @@ prepare_statements (struct PostgresClosure *pg) " AND h_contract_terms=$4" " AND merchant_pub=$2"), /* Query the 'refunds' by coin public key */ - GNUNET_PQ_make_prepare ( - "get_refunds_by_coin", - "SELECT" - " dep.merchant_pub" - ",ref.merchant_sig" - ",dep.h_contract_terms" - ",ref.rtransaction_id" - ",ref.amount_with_fee_val" - ",ref.amount_with_fee_frac" - ",denom.fee_refund_val " - ",denom.fee_refund_frac " - ",ref.refund_serial_id" - " FROM refunds ref" - " JOIN deposits dep" - " ON (ref.coin_pub = dep.coin_pub AND ref.deposit_serial_id = dep.deposit_serial_id)" - " JOIN known_coins kc" - " ON (ref.coin_pub = kc.coin_pub)" - " JOIN denominations denom" - " USING (denominations_serial)" - " WHERE ref.coin_pub=$1;"), /* Query the 'refunds' by coin public key, merchant_pub and contract hash */ GNUNET_PQ_make_prepare ( "get_refunds_by_coin_and_contract", @@ -1727,36 +1661,6 @@ prepare_statements (struct PostgresClosure *pg) " WHERE wire_target_h_payto=$1" " AND wtid_raw=$2"), - /* Used in #postgres_get_coin_transactions() to obtain information - about how a coin has been spend with /deposit requests. */ - GNUNET_PQ_make_prepare ( - "get_deposit_with_coin_pub", - "SELECT" - " dep.amount_with_fee_val" - ",dep.amount_with_fee_frac" - ",denoms.fee_deposit_val" - ",denoms.fee_deposit_frac" - ",denoms.denom_pub_hash" - ",kc.age_commitment_hash" - ",dep.wallet_timestamp" - ",dep.refund_deadline" - ",dep.wire_deadline" - ",dep.merchant_pub" - ",dep.h_contract_terms" - ",dep.wire_salt" - ",wt.payto_uri" - ",dep.coin_sig" - ",dep.deposit_serial_id" - ",dep.done" - " FROM deposits dep" - " JOIN wire_targets wt" - " USING (wire_target_h_payto)" - " JOIN known_coins kc" - " ON (kc.coin_pub = dep.coin_pub)" - " JOIN denominations denoms" - " USING (denominations_serial)" - " WHERE dep.coin_pub=$1;"), - /* Used in #postgres_get_link_data(). */ GNUNET_PQ_make_prepare ( "get_link", @@ -2212,31 +2116,6 @@ prepare_statements (struct PostgresClosure *pg) " JOIN exchange_do_recoup_by_reserve($1) robr" " USING (denominations_serial)" " WHERE recoup_timestamp>=$2;"), - /* Used in #postgres_get_coin_transactions() to obtain recoup transactions - affecting old coins of refreshed coins */ - GNUNET_PQ_make_prepare ( - "recoup_by_old_coin", - "SELECT" - " coins.coin_pub" - ",coin_sig" - ",coin_blind" - ",amount_val" - ",amount_frac" - ",recoup_timestamp" - ",denoms.denom_pub_hash" - ",coins.denom_sig" - ",recoup_refresh_uuid" - " FROM recoup_refresh" - " JOIN known_coins coins" - " USING (coin_pub)" - " JOIN denominations denoms" - " USING (denominations_serial)" - " WHERE rrc_serial IN" - " (SELECT rrc.rrc_serial" - " FROM refresh_commitments" - " JOIN refresh_revealed_coins rrc" - " USING (melt_serial_id)" - " WHERE old_coin_pub=$1);"), /* Used in #postgres_get_reserve_history() */ GNUNET_PQ_make_prepare ( "close_by_reserve", @@ -2348,57 +2227,6 @@ prepare_statements (struct PostgresClosure *pg) " AND request_timestamp>=$2;"), /* Used in #postgres_get_coin_transactions() to obtain recoup transactions for a coin */ - GNUNET_PQ_make_prepare ( - "recoup_by_coin", - "SELECT" - " reserves.reserve_pub" - ",denoms.denom_pub_hash" - ",coin_sig" - ",coin_blind" - ",amount_val" - ",amount_frac" - ",recoup_timestamp" - ",recoup_uuid" - " FROM recoup rcp" - /* NOTE: suboptimal JOIN follows: crosses shards! - Could theoretically be improved via a materialized - index. But likely not worth it (query is rare and - number of reserve shards might be limited) */ - " JOIN reserves_out ro" - " USING (reserve_out_serial_id)" - " JOIN reserves" - " USING (reserve_uuid)" - " JOIN known_coins coins" - " USING (coin_pub)" - " JOIN denominations denoms" - " ON (denoms.denominations_serial = coins.denominations_serial)" - " WHERE coins.coin_pub=$1;"), - /* Used in #postgres_get_coin_transactions() to obtain recoup transactions - for a refreshed coin */ - GNUNET_PQ_make_prepare ( - "recoup_by_refreshed_coin", - "SELECT" - " old_coins.coin_pub AS old_coin_pub" - ",coin_sig" - ",coin_blind" - ",amount_val" - ",amount_frac" - ",recoup_timestamp" - ",denoms.denom_pub_hash" - ",coins.denom_sig" - ",recoup_refresh_uuid" - " FROM recoup_refresh" - " JOIN refresh_revealed_coins rrc" - " USING (rrc_serial)" - " JOIN refresh_commitments rfc" - " ON (rrc.melt_serial_id = rfc.melt_serial_id)" - " JOIN known_coins old_coins" - " ON (rfc.old_coin_pub = old_coins.coin_pub)" - " JOIN known_coins coins" - " ON (recoup_refresh.coin_pub = coins.coin_pub)" - " JOIN denominations denoms" - " ON (denoms.denominations_serial = coins.denominations_serial)" - " WHERE coins.coin_pub=$1;"), /* Used in #postgres_get_reserve_by_h_blind() */ GNUNET_PQ_make_prepare ( "reserve_by_h_blind", @@ -5580,8 +5408,8 @@ postgres_get_reserve_history (void *cls, if ( (qs < 0) || (rhc.status != GNUNET_OK) ) { - common_free_reserve_history (cls, - rhc.rh); + TEH_COMMON_free_reserve_history (cls, + rhc.rh); rhc.rh = NULL; if (qs >= 0) { @@ -5691,8 +5519,8 @@ postgres_get_reserve_status (void *cls, if ( (qs < 0) || (rhc.status != GNUNET_OK) ) { - common_free_reserve_history (cls, - rhc.rh); + TEH_COMMON_free_reserve_history (cls, + rhc.rh); rhc.rh = NULL; if (qs >= 0) { @@ -7150,620 +6978,6 @@ postgres_get_link_data (void *cls, } -/** - * Closure for callbacks called from #postgres_get_coin_transactions() - */ -struct CoinHistoryContext -{ - /** - * Head of the coin's history list. - */ - struct TALER_EXCHANGEDB_TransactionList *head; - - /** - * Public key of the coin we are building the history for. - */ - const struct TALER_CoinSpendPublicKeyP *coin_pub; - - /** - * Closure for all callbacks of this database plugin. - */ - void *db_cls; - - /** - * Plugin context. - */ - struct PostgresClosure *pg; - - /** - * Set to 'true' if the transaction failed. - */ - bool failed; - - /** - * Set to 'true' if we found a deposit or melt (for invariant check). - */ - bool have_deposit_or_melt; -}; - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_deposit (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; i < num_results; i++) - { - struct TALER_EXCHANGEDB_DepositListEntry *deposit; - struct TALER_EXCHANGEDB_TransactionList *tl; - uint64_t serial_id; - - chc->have_deposit_or_melt = true; - deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry); - { - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &deposit->amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", - &deposit->deposit_fee), - GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &deposit->h_denom_pub), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", - &deposit->h_age_commitment), - &deposit->no_age_commitment), - GNUNET_PQ_result_spec_timestamp ("wallet_timestamp", - &deposit->timestamp), - GNUNET_PQ_result_spec_timestamp ("refund_deadline", - &deposit->refund_deadline), - GNUNET_PQ_result_spec_timestamp ("wire_deadline", - &deposit->wire_deadline), - GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", - &deposit->merchant_pub), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &deposit->h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("wire_salt", - &deposit->wire_salt), - GNUNET_PQ_result_spec_string ("payto_uri", - &deposit->receiver_wire_account), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &deposit->csig), - GNUNET_PQ_result_spec_uint64 ("deposit_serial_id", - &serial_id), - GNUNET_PQ_result_spec_auto_from_type ("done", - &deposit->done), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (deposit); - chc->failed = true; - return; - } - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_DEPOSIT; - tl->details.deposit = deposit; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_purse_deposit (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; i < num_results; i++) - { - struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit; - struct TALER_EXCHANGEDB_TransactionList *tl; - uint64_t serial_id; - - chc->have_deposit_or_melt = true; - deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry); - { - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &deposit->amount), - TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", - &deposit->deposit_fee), - GNUNET_PQ_result_spec_auto_from_type ("purse_pub", - &deposit->purse_pub), - GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id", - &serial_id), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("partner_base_url", - &deposit->exchange_base_url), - NULL), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &deposit->coin_sig), - GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", - &deposit->h_age_commitment), - GNUNET_PQ_result_spec_bool ("refunded", - &deposit->refunded), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (deposit); - chc->failed = true; - return; - } - deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment); - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT; - tl->details.purse_deposit = deposit; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_melt (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; ihave_deposit_or_melt = true; - melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry); - { - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("rc", - &melt->rc), - /* oldcoin_index not needed */ - GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &melt->h_denom_pub), - GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", - &melt->coin_sig), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &melt->amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh", - &melt->melt_fee), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", - &melt->h_age_commitment), - &melt->no_age_commitment), - GNUNET_PQ_result_spec_uint64 ("melt_serial_id", - &serial_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (melt); - chc->failed = true; - return; - } - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_MELT; - tl->details.melt = melt; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_refund (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; imerchant_pub), - GNUNET_PQ_result_spec_auto_from_type ("merchant_sig", - &refund->merchant_sig), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", - &refund->h_contract_terms), - GNUNET_PQ_result_spec_uint64 ("rtransaction_id", - &refund->rtransaction_id), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &refund->refund_amount), - TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", - &refund->refund_fee), - GNUNET_PQ_result_spec_uint64 ("refund_serial_id", - &serial_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (refund); - chc->failed = true; - return; - } - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_REFUND; - tl->details.refund = refund; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_old_coin_recoup (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; icoin.coin_pub), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &recoup->coin_sig), - GNUNET_PQ_result_spec_auto_from_type ("coin_blind", - &recoup->coin_blind), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &recoup->value), - GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", - &recoup->timestamp), - GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &recoup->coin.denom_pub_hash), - TALER_PQ_result_spec_denom_sig ("denom_sig", - &recoup->coin.denom_sig), - GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", - &serial_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (recoup); - chc->failed = true; - return; - } - recoup->old_coin_pub = *chc->coin_pub; - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP; - tl->details.old_coin_recoup = recoup; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_recoup (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; ireserve_pub), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &recoup->coin_sig), - GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &recoup->h_denom_pub), - GNUNET_PQ_result_spec_auto_from_type ("coin_blind", - &recoup->coin_blind), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &recoup->value), - GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", - &recoup->timestamp), - GNUNET_PQ_result_spec_uint64 ("recoup_uuid", - &serial_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (recoup); - chc->failed = true; - return; - } - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_RECOUP; - tl->details.recoup = recoup; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Function to be called with the results of a SELECT statement - * that has returned @a num_results results. - * - * @param cls closure of type `struct CoinHistoryContext` - * @param result the postgres result - * @param num_results the number of results in @a result - */ -static void -add_coin_recoup_refresh (void *cls, - PGresult *result, - unsigned int num_results) -{ - struct CoinHistoryContext *chc = cls; - struct PostgresClosure *pg = chc->pg; - - for (unsigned int i = 0; iold_coin_pub), - GNUNET_PQ_result_spec_auto_from_type ("coin_sig", - &recoup->coin_sig), - GNUNET_PQ_result_spec_auto_from_type ("coin_blind", - &recoup->coin_blind), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &recoup->value), - GNUNET_PQ_result_spec_timestamp ("recoup_timestamp", - &recoup->timestamp), - GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &recoup->coin.denom_pub_hash), - TALER_PQ_result_spec_denom_sig ("denom_sig", - &recoup->coin.denom_sig), - GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid", - &serial_id), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - { - GNUNET_break (0); - GNUNET_free (recoup); - chc->failed = true; - return; - } - recoup->coin.coin_pub = *chc->coin_pub; - } - tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); - tl->next = chc->head; - tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH; - tl->details.recoup_refresh = recoup; - tl->serial_id = serial_id; - chc->head = tl; - } -} - - -/** - * Work we need to do. - */ -struct Work -{ - /** - * SQL prepared statement name. - */ - const char *statement; - - /** - * Function to call to handle the result(s). - */ - GNUNET_PQ_PostgresResultHandler cb; -}; - - -/** - * Compile a list of all (historic) transactions performed with the given coin - * (/refresh/melt, /deposit, /refund and /recoup operations). - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param coin_pub coin to investigate - * @param[out] tlp set to list of transactions, NULL if coin is fresh - * @return database transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_get_coin_transactions ( - void *cls, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_EXCHANGEDB_TransactionList **tlp) -{ - struct PostgresClosure *pg = cls; - static const struct Work work[] = { - /** #TALER_EXCHANGEDB_TT_DEPOSIT */ - { "get_deposit_with_coin_pub", - &add_coin_deposit }, - /** #TALER_EXCHANGEDB_TT_MELT */ - { "get_refresh_session_by_coin", - &add_coin_melt }, - /** #TALER_EXCHANGEDB_TT_PURSE_DEPOSIT */ - { "get_purse_deposit_by_coin_pub", - &add_coin_purse_deposit }, - /** #TALER_EXCHANGEDB_TT_REFUND */ - { "get_refunds_by_coin", - &add_coin_refund }, - /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */ - { "recoup_by_old_coin", - &add_old_coin_recoup }, - /** #TALER_EXCHANGEDB_TT_RECOUP */ - { "recoup_by_coin", - &add_coin_recoup }, - /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */ - { "recoup_by_refreshed_coin", - &add_coin_recoup_refresh }, - { NULL, NULL } - }; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - struct CoinHistoryContext chc = { - .head = NULL, - .coin_pub = coin_pub, - .pg = pg, - .db_cls = cls - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Getting transactions for coin %s\n", - TALER_B2S (coin_pub)); - for (unsigned int i = 0; NULL != work[i].statement; i++) - { - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - work[i].statement, - params, - work[i].cb, - &chc); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Coin %s yielded %d transactions of type %s\n", - TALER_B2S (coin_pub), - qs, - work[i].statement); - if ( (0 > qs) || - (chc.failed) ) - { - if (NULL != chc.head) - common_free_coin_transaction_list (cls, - chc.head); - *tlp = NULL; - if (chc.failed) - qs = GNUNET_DB_STATUS_HARD_ERROR; - return qs; - } - } - *tlp = chc.head; - if (NULL == chc.head) - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - /** * Closure for #handle_wt_result. */ @@ -14927,7 +14141,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->get_reserve_balance = &postgres_get_reserve_balance; plugin->get_reserve_history = &postgres_get_reserve_history; plugin->get_reserve_status = &postgres_get_reserve_status; - plugin->free_reserve_history = &common_free_reserve_history; plugin->count_known_coins = &postgres_count_known_coins; plugin->ensure_coin_known = &postgres_ensure_coin_known; plugin->get_known_coin = &postgres_get_known_coin; @@ -14952,8 +14165,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->insert_refresh_reveal = &postgres_insert_refresh_reveal; plugin->get_refresh_reveal = &postgres_get_refresh_reveal; plugin->get_link_data = &postgres_get_link_data; - plugin->get_coin_transactions = &postgres_get_coin_transactions; - plugin->free_coin_transaction_list = &common_free_coin_transaction_list; plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer; plugin->lookup_transfer_by_deposit = &postgres_lookup_transfer_by_deposit; plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking; @@ -15133,6 +14344,12 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) /* NEW style, sort alphabetically! */ plugin->do_reserve_open = &TEH_PG_do_reserve_open; + plugin->free_coin_transaction_list + = &TEH_COMMON_free_coin_transaction_list; + plugin->free_reserve_history + = &TEH_COMMON_free_reserve_history; + plugin->get_coin_transactions + = &TEH_PG_get_coin_transactions; plugin->get_expired_reserves = &TEH_PG_get_expired_reserves; plugin->get_unfinished_close_requests diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index d361d5393..0df32241c 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1788,6 +1788,31 @@ struct TALER_EXCHANGEDB_PurseDepositListEntry }; +/** + * Information about a /reserves/$RID/open operation in a coin transaction history. + */ +struct TALER_EXCHANGEDB_ReserveOpenListEntry +{ + + /** + * Signature of the reserve. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Contribution of the coin to the open fee, including + * deposit fee. + */ + struct TALER_Amount coin_contribution; + + /** + * Signature by the coin affirming the open deposit. + */ + struct TALER_CoinSpendSignatureP coin_sig; + +}; + + /** * Information about a /purses/$PID/deposit operation. */ @@ -1947,7 +1972,12 @@ enum TALER_EXCHANGEDB_TransactionType /** * Purse deposit operation. */ - TALER_EXCHANGEDB_TT_PURSE_DEPOSIT = 6 + TALER_EXCHANGEDB_TT_PURSE_DEPOSIT = 6, + + /** + * Reserve open deposit operation. + */ + TALER_EXCHANGEDB_TT_RESERVE_OPEN = 7 }; @@ -2023,6 +2053,12 @@ struct TALER_EXCHANGEDB_TransactionList */ struct TALER_EXCHANGEDB_PurseDepositListEntry *purse_deposit; + /** + * Coin was used to pay to open a reserve. + * (#TALER_EXCHANGEDB_TT_RESERVE_OPEN) + */ + struct TALER_EXCHANGEDB_ReserveOpenListEntry *reserve_open; + } details; }; -- cgit v1.2.3