/* This file is part of TALER Copyright (C) 2022-2023 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_exchangedb_plugin.h" #include "taler_pq_lib.h" #include "pg_get_coin_transactions.h" #include "pg_helper.h" #include "pg_start_read_committed.h" #include "pg_commit.h" #include "pg_rollback.h" #include "plugin_exchangedb_common.h" /** * How often do we re-try when encountering DB serialization issues? * (We are read-only, so can only happen due to concurrent insert, * which should be very rare.) */ #define RETRIES 3 /** * 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; /** * Plugin context. */ struct PostgresClosure *pg; /** * Set to 'true' if the transaction failed. */ bool failed; }; /** * 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; 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_allow_null ( GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash", &deposit->wallet_data_hash), &deposit->no_wallet_data_hash), 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 ("coin_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; deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry); { bool not_finished; 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_allow_null ( GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", &deposit->h_age_commitment), &deposit->no_age_commitment), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_bool ("refunded", &deposit->refunded), ¬_finished), 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; } if (not_finished) deposit->refunded = false; 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; irc), /* 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_coin_purse_decision (void *cls, PGresult *result, unsigned int num_results) { struct CoinHistoryContext *chc = cls; struct PostgresClosure *pg = chc->pg; for (unsigned int i = 0; ipurse_pub), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &prefund->refund_amount), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", &prefund->refund_fee), GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id", &serial_id), GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i)) { GNUNET_break (0); GNUNET_free (prefund); chc->failed = true; return; } } tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList); tl->next = chc->head; tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND; tl->details.purse_refund = prefund; 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 { /** * Name of the table. */ const char *table; /** * SQL prepared statement name. */ const char *statement; /** * Function to call to handle the result(s). */ GNUNET_PQ_PostgresResultHandler cb; }; /** * We found a coin history entry. Lookup details * from the respective table and store in @a cls. * * @param[in,out] cls a `struct CoinHistoryContext` * @param result a coin history entry result set * @param num_results total number of results in @a results */ static void handle_history_entry (void *cls, PGresult *result, unsigned int num_results) { struct CoinHistoryContext *chc = cls; struct PostgresClosure *pg = chc->pg; static const struct Work work[] = { [TALER_EXCHANGEDB_TT_DEPOSIT] = { "coin_deposits", "get_deposit_with_coin_pub", &add_coin_deposit }, [TALER_EXCHANGEDB_TT_MELT] = { "refresh_commitments", "get_refresh_session_by_coin", &add_coin_melt }, [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] = { "purse_deposits", "get_purse_deposit_by_coin_pub", &add_coin_purse_deposit }, [TALER_EXCHANGEDB_TT_PURSE_REFUND] = { "purse_decision", "get_purse_decision_by_coin_pub", &add_coin_purse_decision }, [TALER_EXCHANGEDB_TT_REFUND] = { "refunds", "get_refunds_by_coin", &add_coin_refund }, [TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP] = { "recoup_refresh::OLD", "recoup_by_old_coin", &add_old_coin_recoup }, [TALER_EXCHANGEDB_TT_RECOUP] = { "recoup", "recoup_by_coin", &add_coin_recoup }, [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] = { "recoup_refresh::NEW", "recoup_by_refreshed_coin", &add_coin_recoup_refresh }, [TALER_EXCHANGEDB_TT_RESERVE_OPEN] = { "reserves_open_deposits", "reserve_open_by_coin", &add_coin_reserve_open }, { NULL, NULL, NULL } }; char *table_name; uint64_t serial_id; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("table_name", &table_name), GNUNET_PQ_result_spec_uint64 ("serial_id", &serial_id), GNUNET_PQ_result_spec_end }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (chc->coin_pub), GNUNET_PQ_query_param_uint64 (&serial_id), GNUNET_PQ_query_param_end }; for (unsigned int i = 0; ifailed = true; return; } for (unsigned int s = 0; NULL != work[s].statement; s++) { if (0 != strcmp (table_name, work[s].table)) continue; found = true; qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, work[s].statement, params, work[s].cb, chc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Coin %s had %d transactions at %llu in table %s\n", TALER_B2S (chc->coin_pub), (int) qs, (unsigned long long) serial_id, table_name); if (0 >= qs) chc->failed = true; break; } if (! found) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Coin history includes unsupported table `%s`\n", table_name); chc->failed = true; } GNUNET_PQ_cleanup_result (rs); if (chc->failed) break; } } enum GNUNET_DB_QueryStatus TEH_PG_get_coin_transactions ( void *cls, const struct TALER_CoinSpendPublicKeyP *coin_pub, uint64_t start_off, uint64_t etag_in, uint64_t *etag_out, struct TALER_Amount *balance, struct TALER_DenominationHashP *h_denom_pub, struct TALER_EXCHANGEDB_TransactionList **tlp) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam lparams[] = { GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_uint64 (&start_off), GNUNET_PQ_query_param_end }; struct CoinHistoryContext chc = { .head = NULL, .coin_pub = coin_pub, .pg = pg }; *tlp = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Getting transactions for coin %s\n", TALER_B2S (coin_pub)); PREPARE (pg, "get_coin_history_etag_balance", "SELECT" " ch.coin_history_serial_id" ",kc.remaining" ",denom.denom_pub_hash" " FROM coin_history ch" " JOIN known_coins kc" " USING (coin_pub)" " JOIN denominations denom" " USING (denominations_serial)" " WHERE coin_pub=$1" " ORDER BY coin_history_serial_id DESC" " LIMIT 1;"); PREPARE (pg, "get_coin_history", "SELECT" " table_name" ",serial_id" " FROM coin_history" " WHERE coin_pub=$1" " AND coin_history_serial_id > $2" " ORDER BY coin_history_serial_id DESC;"); PREPARE (pg, "get_deposit_with_coin_pub", "SELECT" " cdep.amount_with_fee" ",denoms.fee_deposit" ",denoms.denom_pub_hash" ",kc.age_commitment_hash" ",bdep.wallet_timestamp" ",bdep.refund_deadline" ",bdep.wire_deadline" ",bdep.merchant_pub" ",bdep.h_contract_terms" ",bdep.wallet_data_hash" ",bdep.wire_salt" ",wt.payto_uri" ",cdep.coin_sig" ",cdep.coin_deposit_serial_id" ",bdep.done" " FROM coin_deposits cdep" " JOIN batch_deposits bdep" " USING (batch_deposit_serial_id)" " JOIN wire_targets wt" " USING (wire_target_h_payto)" " JOIN known_coins kc" " ON (kc.coin_pub = cdep.coin_pub)" " JOIN denominations denoms" " USING (denominations_serial)" " WHERE cdep.coin_pub=$1" " AND cdep.coin_deposit_serial_id=$2;"); PREPARE (pg, "get_refresh_session_by_coin", "SELECT" " rc" ",old_coin_sig" ",amount_with_fee" ",denoms.denom_pub_hash" ",denoms.fee_refresh" ",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" " AND melt_serial_id=$2;"); PREPARE (pg, "get_purse_deposit_by_coin_pub", "SELECT" " partner_base_url" ",pd.amount_with_fee" ",denoms.fee_deposit" ",pd.purse_pub" ",kc.age_commitment_hash" ",pd.coin_sig" ",pd.purse_deposit_serial_id" ",pdes.refunded" " FROM purse_deposits pd" " LEFT JOIN partners" " USING (partner_serial_id)" " JOIN purse_requests pr" " USING (purse_pub)" " LEFT JOIN purse_decision pdes" " USING (purse_pub)" " JOIN known_coins kc" " ON (pd.coin_pub = kc.coin_pub)" " JOIN denominations denoms" " USING (denominations_serial)" " WHERE pd.purse_deposit_serial_id=$2" " AND pd.coin_pub=$1;"); PREPARE (pg, "get_purse_decision_by_coin_pub", "SELECT" " pdes.purse_pub" ",pd.amount_with_fee" ",denom.fee_refund" ",pdes.purse_decision_serial_id" " FROM purse_decision pdes" " JOIN purse_deposits pd" " USING (purse_pub)" " JOIN known_coins kc" " ON (pd.coin_pub = kc.coin_pub)" " JOIN denominations denom" " USING (denominations_serial)" " WHERE pd.coin_pub=$1" " AND pdes.purse_decision_serial_id=$2" " AND pdes.refunded;"); PREPARE (pg, "get_refunds_by_coin", "SELECT" " bdep.merchant_pub" ",ref.merchant_sig" ",bdep.h_contract_terms" ",ref.rtransaction_id" ",ref.amount_with_fee" ",denom.fee_refund" ",ref.refund_serial_id" " FROM refunds ref" " JOIN coin_deposits cdep" " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)" " JOIN batch_deposits bdep" " ON (ref.batch_deposit_serial_id = bdep.batch_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" " AND ref.refund_serial_id=$2;"); PREPARE (pg, "recoup_by_old_coin", "SELECT" " coins.coin_pub" ",rr.coin_sig" ",rr.coin_blind" ",rr.amount" ",rr.recoup_timestamp" ",denoms.denom_pub_hash" ",coins.denom_sig" ",rr.recoup_refresh_uuid" " FROM recoup_refresh rr" " JOIN known_coins coins" " USING (coin_pub)" " JOIN denominations denoms" " USING (denominations_serial)" " WHERE recoup_refresh_uuid=$2" " AND rrc_serial IN" " (SELECT rrc.rrc_serial" " FROM refresh_commitments melt" " JOIN refresh_revealed_coins rrc" " USING (melt_serial_id)" " WHERE melt.old_coin_pub=$1);"); PREPARE (pg, "recoup_by_coin", "SELECT" " res.reserve_pub" ",denoms.denom_pub_hash" ",rcp.coin_sig" ",rcp.coin_blind" ",rcp.amount" ",rcp.recoup_timestamp" ",rcp.recoup_uuid" " FROM recoup rcp" " JOIN reserves_out ro" " USING (reserve_out_serial_id)" " JOIN reserves res" " USING (reserve_uuid)" " JOIN known_coins coins" " USING (coin_pub)" " JOIN denominations denoms" " ON (denoms.denominations_serial = coins.denominations_serial)" " WHERE rcp.recoup_uuid=$2" " AND 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" ",rr.coin_sig" ",rr.coin_blind" ",rr.amount" ",rr.recoup_timestamp" ",denoms.denom_pub_hash" ",coins.denom_sig" ",recoup_refresh_uuid" " FROM recoup_refresh rr" " 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 (rr.coin_pub = coins.coin_pub)" " JOIN denominations denoms" " ON (denoms.denominations_serial = coins.denominations_serial)" " WHERE rr.recoup_refresh_uuid=$2" " AND coins.coin_pub=$1;"); PREPARE (pg, "reserve_open_by_coin", "SELECT" " reserve_open_deposit_uuid" ",coin_sig" ",reserve_sig" ",contribution" " FROM reserves_open_deposits" " WHERE coin_pub=$1" " AND reserve_open_deposit_uuid=$2;"); for (unsigned int i = 0; iconn, "get_coin_history_etag_balance", params, rs); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: TEH_PG_rollback (pg); return qs; case GNUNET_DB_STATUS_SOFT_ERROR: TEH_PG_rollback (pg); continue; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: TEH_PG_rollback (pg); return qs; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: *etag_out = end; if (end == etag_in) return qs; } /* We indeed need to iterate over the history */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Current ETag for coin %s is %llu\n", TALER_B2S (coin_pub), (unsigned long long) end); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, "get_coin_history", lparams, &handle_history_entry, &chc); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: TEH_PG_rollback (pg); return qs; case GNUNET_DB_STATUS_SOFT_ERROR: TEH_PG_rollback (pg); continue; default: break; } if (chc.failed) { TEH_PG_rollback (pg); TEH_COMMON_free_coin_transaction_list (pg, chc.head); return GNUNET_DB_STATUS_SOFT_ERROR; } qs = TEH_PG_commit (pg); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: TEH_COMMON_free_coin_transaction_list (pg, chc.head); chc.head = NULL; return qs; case GNUNET_DB_STATUS_SOFT_ERROR: TEH_COMMON_free_coin_transaction_list (pg, chc.head); chc.head = NULL; continue; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: *tlp = chc.head; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } } return GNUNET_DB_STATUS_SOFT_ERROR; }