summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2017-06-19 16:07:34 +0200
committerChristian Grothoff <christian@grothoff.org>2017-06-19 16:07:34 +0200
commit92e6744ac032a3c4c4118ac6b251f769c5478aa6 (patch)
tree9098913719d9b1a70d0ed8d8a0ba8e500c96768a
parent3d701e8d2a39e766b8345e242c3ffb501f935a3e (diff)
downloadexchange-92e6744ac032a3c4c4118ac6b251f769c5478aa6.tar.gz
exchange-92e6744ac032a3c4c4118ac6b251f769c5478aa6.tar.bz2
exchange-92e6744ac032a3c4c4118ac6b251f769c5478aa6.zip
address #5010 for /refund
-rw-r--r--src/auditor/taler-auditor.c9
-rw-r--r--src/exchange/taler-exchange-httpd_db.c289
-rw-r--r--src/exchange/taler-exchange-httpd_db.h14
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c7
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c357
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c88
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h51
-rw-r--r--src/exchangedb/perf_taler_exchangedb_interpreter.c11
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c688
-rw-r--r--src/exchangedb/test_exchangedb.c13
-rw-r--r--src/include/taler_exchangedb_plugin.h19
11 files changed, 784 insertions, 762 deletions
diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c
index 0d7d0b01a..21c9579cd 100644
--- a/src/auditor/taler-auditor.c
+++ b/src/auditor/taler-auditor.c
@@ -1864,12 +1864,15 @@ wire_transfer_information_cb (void *cls,
struct TALER_Amount coin_value_without_fee;
struct TALER_EXCHANGEDB_TransactionList *tl;
const struct TALER_CoinPublicInfo *coin;
+ enum GNUNET_DB_QueryStatus qs;
/* Obtain coin's transaction history */
- tl = edb->get_coin_transactions (edb->cls,
+ qs = edb->get_coin_transactions (edb->cls,
esession,
- coin_pub);
- if (NULL == tl)
+ coin_pub,
+ &tl);
+ if ( (qs < 0) ||
+ (NULL == tl) )
{
wcc->ok = GNUNET_SYSERR;
report_row_inconsistency ("aggregation",
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 298f6c35a..9e95ff40a 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -280,279 +280,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
/**
- * Execute a "/refund". Returns a confirmation that the refund
- * was successful, or a failure if we are not aware of a matching
- * /deposit or if it is too late to do the refund.
- *
- * @param connection the MHD connection to handle
- * @param refund refund details
- * @return MHD result code
- */
-int
-TEH_DB_execute_refund (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refund *refund)
-{
- struct TALER_EXCHANGEDB_Session *session;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- struct TALER_EXCHANGEDB_TransactionList *tlp;
- const struct TALER_EXCHANGEDB_Deposit *dep;
- const struct TALER_EXCHANGEDB_Refund *ref;
- struct TEH_KS_StateHandle *mks;
- struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
- struct TALER_Amount expect_fee;
- enum GNUNET_DB_QueryStatus qs;
- int ret;
- int deposit_found;
- int refund_found;
- int done;
- int fee_cmp;
-
- if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
- {
- GNUNET_break (0);
- return TEH_RESPONSE_reply_internal_db_error (connection,
- TALER_EC_DB_SETUP_FAILED);
- }
- dep = NULL;
- ref = NULL;
- START_TRANSACTION (session, connection);
- tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- session,
- &refund->coin.coin_pub);
- if (NULL == tl)
- {
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- return TEH_RESPONSE_reply_refund_failure (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_REFUND_COIN_NOT_FOUND);
- }
- deposit_found = GNUNET_NO;
- refund_found = GNUNET_NO;
- for (tlp = tl; NULL != tlp; tlp = tlp->next)
- {
- switch (tlp->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- if (GNUNET_NO == deposit_found)
- {
- if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
- &refund->merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.deposit->h_contract_terms,
- &refund->h_contract_terms,
- sizeof (struct GNUNET_HashCode))) )
- {
- dep = tlp->details.deposit;
- deposit_found = GNUNET_YES;
- break;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_REFRESH_MELT:
- /* Melts cannot be refunded, ignore here */
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- if (GNUNET_NO == refund_found)
- {
- /* First, check if existing refund request is identical */
- if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
- &refund->merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.refund->h_contract_terms,
- &refund->h_contract_terms,
- sizeof (struct GNUNET_HashCode))) &&
- (tlp->details.refund->rtransaction_id == refund->rtransaction_id) )
- {
- ref = tlp->details.refund;
- refund_found = GNUNET_YES;
- break;
- }
- /* Second, check if existing refund request conflicts */
- if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
- &refund->merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP))) &&
- (0 == memcmp (&tlp->details.refund->h_contract_terms,
- &refund->h_contract_terms,
- sizeof (struct GNUNET_HashCode))) &&
- (tlp->details.refund->rtransaction_id != refund->rtransaction_id) )
- {
- GNUNET_break_op (0); /* conflicting refund found */
- refund_found = GNUNET_SYSERR;
- /* NOTE: Alternatively we could total up all existing
- refunds and check if the sum still permits the
- refund requested (thus allowing multiple, partial
- refunds). Fow now, we keep it simple. */
- break;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_PAYBACK:
- /* Paybacks cannot be refunded, ignore here */
- break;
- }
- }
- /* handle if deposit was NOT found */
- if (GNUNET_NO == deposit_found)
- {
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return TEH_RESPONSE_reply_transaction_unknown (connection,
- TALER_EC_REFUND_DEPOSIT_NOT_FOUND);
- }
- /* handle if conflicting refund found */
- if (GNUNET_SYSERR == refund_found)
- {
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- ret = TEH_RESPONSE_reply_refund_conflict (connection,
- tl);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return ret;
- }
- /* handle if identical refund found */
- if (GNUNET_YES == refund_found)
- {
- /* /refund already done, simply re-transmit confirmation */
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- ret = TEH_RESPONSE_reply_refund_success (connection,
- ref);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return ret;
- }
-
- /* check currency is compatible */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund->refund_amount,
- &dep->amount_with_fee)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund->refund_fee,
- &dep->deposit_fee)) )
- {
- GNUNET_break_op (0); /* currency missmatch */
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- return TEH_RESPONSE_reply_refund_failure (connection,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_REFUND_CURRENCY_MISSMATCH);
- }
-
- /* check if we already send the money for the /deposit */
- done = TEH_plugin->test_deposit_done (TEH_plugin->cls,
- session,
- dep);
- if (GNUNET_SYSERR == done)
- {
- /* Internal error, we first had the deposit in the history,
- but now it is gone? */
- GNUNET_break (0);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- return TEH_RESPONSE_reply_internal_error (connection,
- TALER_EC_REFUND_DB_INCONSISTENT,
- "database inconsistent");
- }
- if (GNUNET_YES == done)
- {
- /* money was already transferred to merchant, can no longer refund */
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return TEH_RESPONSE_reply_refund_failure (connection,
- MHD_HTTP_GONE,
- TALER_EC_REFUND_MERCHANT_ALREADY_PAID);
- }
-
- /* check refund amount is sufficiently low */
- if (1 == TALER_amount_cmp (&refund->refund_amount,
- &dep->amount_with_fee) )
- {
- GNUNET_break_op (0); /* cannot refund more than original value */
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return TEH_RESPONSE_reply_refund_failure (connection,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_REFUND_INSUFFICIENT_FUNDS);
- }
-
- /* Check refund fee matches fee of denomination key! */
- mks = TEH_KS_acquire ();
- dki = TEH_KS_denomination_key_lookup (mks,
- &dep->coin.denom_pub,
- TEH_KS_DKU_DEPOSIT);
- if (NULL == dki)
- {
- /* DKI not found, but we do have a coin with this DK in our database;
- not good... */
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- TEH_KS_release (mks);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return TEH_RESPONSE_reply_internal_error (connection,
- TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND,
- "denomination key not found");
- }
- TALER_amount_ntoh (&expect_fee,
- &dki->issue.properties.fee_refund);
- fee_cmp = TALER_amount_cmp (&refund->refund_fee,
- &expect_fee);
- TEH_KS_release (mks);
-
- if (-1 == fee_cmp)
- {
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return TEH_RESPONSE_reply_arg_invalid (connection,
- TALER_EC_REFUND_FEE_TOO_LOW,
- "refund_fee");
- }
- if (1 == fee_cmp)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Refund fee proposed by merchant is higher than necessary.\n");
- }
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
-
- /* Finally, store new refund data */
- qs = TEH_plugin->insert_refund (TEH_plugin->cls,
- session,
- refund);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TALER_LOG_WARNING ("Failed to store /refund information in database\n");
- TEH_plugin->rollback (TEH_plugin->cls,
- session);
- return TEH_RESPONSE_reply_internal_db_error (connection,
- TALER_EC_REFUND_STORE_DB_ERROR);
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* FIXME: #5010: retry! */
- }
-
- COMMIT_TRANSACTION (session, connection);
-
- return TEH_RESPONSE_reply_refund_success (connection,
- refund);
-}
-
-
-/**
* Parse coin melt requests from a JSON object and write them to
* the database.
*
@@ -581,6 +308,7 @@ refresh_check_melt (struct MHD_Connection *connection,
struct TALER_Amount coin_residual;
struct TALER_Amount spent;
int res;
+ enum GNUNET_DB_QueryStatus qs;
dk = TEH_KS_denomination_key_lookup (key_state,
&coin_details->coin_info.denom_pub,
@@ -597,9 +325,11 @@ refresh_check_melt (struct MHD_Connection *connection,
/* fee for THIS transaction; the melt amount includes the fee! */
spent = coin_details->melt_amount_with_fee;
/* add historic transaction costs of this coin */
- tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
- &coin_details->coin_info.coin_pub);
+ &coin_details->coin_info.coin_pub,
+ &tl);
+ (void) qs; /* FIXME #5010 */
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
&spent,
@@ -1921,7 +1651,8 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
struct TALER_Amount amount;
struct TALER_Amount spent;
struct GNUNET_TIME_Absolute now;
-
+ enum GNUNET_DB_QueryStatus qs;
+
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
@@ -1955,9 +1686,11 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
}
/* Calculate remaining balance. */
- tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
- &coin->coin_pub);
+ &coin->coin_pub,
+ &tl);
+ (void) qs; /* FIXME #5010 */
TALER_amount_get_zero (value->currency,
&spent);
if (GNUNET_OK !=
diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h
index e3717bdb4..5e2a27a31 100644
--- a/src/exchange/taler-exchange-httpd_db.h
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -84,20 +84,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
/**
- * Execute a "/refund". Returns a confirmation that the refund
- * was successful, or a failure if we are not aware of a matching
- * /deposit or if it is too late to do the refund.
- *
- * @param connection the MHD connection to handle
- * @param refund refund details
- * @return MHD result code
- */
-int
-TEH_DB_execute_refund (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refund *refund);
-
-
-/**
* @brief Details about a melt operation of an individual coin.
*/
struct TEH_DB_MeltDetails
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index c9c59edef..ccbd7754c 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -163,9 +163,12 @@ deposit_transaction (void *cls,
/* Start with fee for THIS transaction */
spent = deposit->amount_with_fee;
/* add cost of all previous transactions */
- tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
- &deposit->coin.coin_pub);
+ &deposit->coin.coin_pub,
+ &tl);
+ if (0 > qs)
+ return qs;
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
&spent,
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index a5787eea9..9846c73ba 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
+ Copyright (C) 2014-2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -37,6 +37,350 @@
/**
+ * Generate successful refund confirmation message.
+ *
+ * @param connection connection to the client
+ * @param refund details about the successful refund
+ * @return MHD result code
+ */
+static int
+reply_refund_success (struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_Refund *refund)
+{
+ struct TALER_RefundConfirmationPS rc;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
+ rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
+ rc.h_contract_terms = refund->h_contract_terms;
+ rc.coin_pub = refund->coin.coin_pub;
+ rc.merchant = refund->merchant_pub;
+ rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
+ TALER_amount_hton (&rc.refund_amount,
+ &refund->refund_amount);
+ TALER_amount_hton (&rc.refund_fee,
+ &refund->refund_fee);
+ TEH_KS_sign (&rc.purpose,
+ &pub,
+ &sig);
+ return TEH_RESPONSE_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:s, s:o, s:o}",
+ "status", "REFUND_OK",
+ "sig", GNUNET_JSON_from_data_auto (&sig),
+ "pub", GNUNET_JSON_from_data_auto (&pub));
+}
+
+
+/**
+ * Generate generic refund failure message. All the details
+ * are in the @a response_code. The body can be empty.
+ *
+ * @param connection connection to the client
+ * @param response_code response code to generate
+ * @param ec taler error code to include
+ * @return MHD result code
+ */
+static int
+reply_refund_failure (struct MHD_Connection *connection,
+ unsigned int response_code,
+ enum TALER_ErrorCode ec)
+{
+ return TEH_RESPONSE_reply_json_pack (connection,
+ response_code,
+ "{s:s, s:I}",
+ "status", "refund failure",
+ "code", (json_int_t) ec);
+}
+
+
+/**
+ * Generate refund conflict failure message. Returns the
+ * transaction list @a tl with the details about the conflict.
+ *
+ * @param connection connection to the client
+ * @param tl transaction list showing the conflict
+ * @return MHD result code
+ */
+static int
+reply_refund_conflict (struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+ return TEH_RESPONSE_reply_json_pack (connection,
+ MHD_HTTP_CONFLICT,
+ "{s:s, s:I, s:o}",
+ "status", "conflicting refund",
+ "code", (json_int_t) TALER_EC_REFUND_CONFLICT,
+ "history", TEH_RESPONSE_compile_transaction_history (tl));
+}
+
+
+/**
+ * Execute a "/refund" transaction. Returns a confirmation that the
+ * refund was successful, or a failure if we are not aware of a
+ * matching /deposit or if it is too late to do the refund.
+ *
+ * IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
+ * @param connection MHD request which triggered the transaction
+ * @param session database session to use
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+refund_transaction (void *cls,
+ struct MHD_Connection *connection,
+ struct TALER_EXCHANGEDB_Session *session,
+ int *mhd_ret)
+{
+ const struct TALER_EXCHANGEDB_Refund *refund = cls;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ const struct TALER_EXCHANGEDB_Deposit *dep;
+ const struct TALER_EXCHANGEDB_Refund *ref;
+ struct TEH_KS_StateHandle *mks;
+ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
+ struct TALER_Amount expect_fee;
+ enum GNUNET_DB_QueryStatus qs;
+ int deposit_found;
+ int refund_found;
+ int fee_cmp;
+
+ dep = NULL;
+ ref = NULL;
+ tl = NULL;
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ session,
+ &refund->coin.coin_pub,
+ &tl);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = reply_refund_failure (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_REFUND_COIN_NOT_FOUND);
+ return qs;
+ }
+ deposit_found = GNUNET_NO;
+ refund_found = GNUNET_NO;
+ for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl;
+ NULL != tlp;
+ tlp = tlp->next)
+ {
+ switch (tlp->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ if (GNUNET_NO == deposit_found)
+ {
+ if ( (0 == memcmp (&tlp->details.deposit->merchant_pub,
+ &refund->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) &&
+ (0 == memcmp (&tlp->details.deposit->h_contract_terms,
+ &refund->h_contract_terms,
+ sizeof (struct GNUNET_HashCode))) )
+ {
+ dep = tlp->details.deposit;
+ deposit_found = GNUNET_YES;
+ break;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFRESH_MELT:
+ /* Melts cannot be refunded, ignore here */
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ if (GNUNET_NO == refund_found)
+ {
+ /* First, check if existing refund request is identical */
+ if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
+ &refund->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) &&
+ (0 == memcmp (&tlp->details.refund->h_contract_terms,
+ &refund->h_contract_terms,
+ sizeof (struct GNUNET_HashCode))) &&
+ (tlp->details.refund->rtransaction_id == refund->rtransaction_id) )
+ {
+ ref = tlp->details.refund;
+ refund_found = GNUNET_YES;
+ break;
+ }
+ /* Second, check if existing refund request conflicts */
+ if ( (0 == memcmp (&tlp->details.refund->merchant_pub,
+ &refund->merchant_pub,
+ sizeof (struct TALER_MerchantPublicKeyP))) &&
+ (0 == memcmp (&tlp->details.refund->h_contract_terms,
+ &refund->h_contract_terms,
+ sizeof (struct GNUNET_HashCode))) &&
+ (tlp->details.refund->rtransaction_id != refund->rtransaction_id) )
+ {
+ GNUNET_break_op (0); /* conflicting refund found */
+ refund_found = GNUNET_SYSERR;
+ /* NOTE: Alternatively we could total up all existing
+ refunds and check if the sum still permits the
+ refund requested (thus allowing multiple, partial
+ refunds). Fow now, we keep it simple. */
+ break;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_PAYBACK:
+ /* Paybacks cannot be refunded, ignore here */
+ break;
+ }
+ }
+ /* handle if deposit was NOT found */
+ if (GNUNET_NO == deposit_found)
+ {
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TEH_RESPONSE_reply_transaction_unknown (connection,
+ TALER_EC_REFUND_DEPOSIT_NOT_FOUND);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* handle if conflicting refund found */
+ if (GNUNET_SYSERR == refund_found)
+ {
+ *mhd_ret = reply_refund_conflict (connection,
+ tl);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* handle if identical refund found */
+ if (GNUNET_YES == refund_found)
+ {
+ /* /refund already done, simply re-transmit confirmation */
+ *mhd_ret = reply_refund_success (connection,
+ ref);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* check currency is compatible */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&refund->refund_amount,
+ &dep->amount_with_fee)) ||
+ (GNUNET_YES !=
+ TALER_amount_cmp_currency (&refund->refund_fee,
+ &dep->deposit_fee)) )
+ {
+ GNUNET_break_op (0); /* currency missmatch */
+ *mhd_ret = reply_refund_failure (connection,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_REFUND_CURRENCY_MISSMATCH);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* check if we already send the money for the /deposit */
+ // FIXME: DB API...
+ qs = TEH_plugin->test_deposit_done (TEH_plugin->cls,
+ session,
+ dep);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ /* Internal error, we first had the deposit in the history,
+ but now it is gone? */
+ GNUNET_break (0);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_REFUND_DB_INCONSISTENT,
+ "database inconsistent");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs; /* go and retry */
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* money was already transferred to merchant, can no longer refund */
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = reply_refund_failure (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_REFUND_MERCHANT_ALREADY_PAID);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* check refund amount is sufficiently low */
+ if (1 == TALER_amount_cmp (&refund->refund_amount,
+ &dep->amount_with_fee) )
+ {
+ GNUNET_break_op (0); /* cannot refund more than original value */
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = reply_refund_failure (connection,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_REFUND_INSUFFICIENT_FUNDS);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ // FIXME: do this outside of transaction function?
+ /* Check refund fee matches fee of denomination key! */
+ mks = TEH_KS_acquire ();
+ dki = TEH_KS_denomination_key_lookup (mks,
+ &dep->coin.denom_pub,
+ TEH_KS_DKU_DEPOSIT);
+ if (NULL == dki)
+ {
+ /* DKI not found, but we do have a coin with this DK in our database;
+ not good... */
+ GNUNET_break (0);
+ TEH_KS_release (mks);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_REFUND_DENOMINATION_KEY_NOT_FOUND,
+ "denomination key not found");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TALER_amount_ntoh (&expect_fee,
+ &dki->issue.properties.fee_refund);
+ fee_cmp = TALER_amount_cmp (&refund->refund_fee,
+ &expect_fee);
+ TEH_KS_release (mks);
+
+ if (-1 == fee_cmp)
+ {
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ *mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection,
+ TALER_EC_REFUND_FEE_TOO_LOW,
+ "refund_fee");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (1 == fee_cmp)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Refund fee proposed by merchant is higher than necessary.\n");
+ }
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+
+ /* Finally, store new refund data */
+ qs = TEH_plugin->insert_refund (TEH_plugin->cls,
+ session,
+ refund);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ TALER_LOG_WARNING ("Failed to store /refund information in database\n");
+ *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
+ TALER_EC_REFUND_STORE_DB_ERROR);
+ return qs;
+ }
+ /* Success or soft failure */
+ return qs;
+}
+
+
+/**
* We have parsed the JSON information about the refund, do some basic
* sanity checks (especially that the signature on the coin is valid)
* and then execute the refund. Note that we need the DB to check
@@ -51,6 +395,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_Refund *refund)
{
struct TALER_RefundRequestPS rr;
+ int mhd_ret;
rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
@@ -90,8 +435,14 @@ verify_and_execute_refund (struct MHD_Connection *connection,
TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
"merchant_sig");
}
- return TEH_DB_execute_refund (connection,
- refund);
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ &mhd_ret,
+ &refund_transaction,
+ (void *) refund))
+ return mhd_ret;
+ return reply_refund_success (connection,
+ refund);
}
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 48df955f7..3f3f57215 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -467,8 +467,8 @@ TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection)
* @param tl transaction history to JSON-ify
* @return json representation of the @a rh
*/
-static json_t *
-compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl)
+json_t *
+TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl)
{
json_t *history;
@@ -663,7 +663,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
{
json_t *history;
- history = compile_transaction_history (tl);
+ history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history)
return TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
@@ -879,86 +879,6 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto
/**
- * Generate refund conflict failure message. Returns the
- * transaction list @a tl with the details about the conflict.
- *
- * @param connection connection to the client
- * @param tl transaction list showing the conflict
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_TransactionList *tl)
-{
- return TEH_RESPONSE_reply_json_pack (connection,
- MHD_HTTP_CONFLICT,
- "{s:s, s:I, s:o}",
- "status", "conflicting refund",
- "code", (json_int_t) TALER_EC_REFUND_CONFLICT,
- "history", compile_transaction_history (tl));
-}
-
-
-/**
- * Generate generic refund failure message. All the details
- * are in the @a response_code. The body can be empty.
- *
- * @param connection connection to the client
- * @param response_code response code to generate
- * @param ec taler error code to include
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection,
- unsigned int response_code,
- enum TALER_ErrorCode ec)
-{
- return TEH_RESPONSE_reply_json_pack (connection,
- response_code,
- "{s:s, s:I}",
- "status", "refund failure",
- "code", (json_int_t) ec);
-}
-
-
-/**
- * Generate successful refund confirmation message.
- *
- * @param connection connection to the client
- * @param refund details about the successful refund
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refund *refund)
-{
- struct TALER_RefundConfirmationPS rc;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
- rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
- rc.h_contract_terms = refund->h_contract_terms;
- rc.coin_pub = refund->coin.coin_pub;
- rc.merchant = refund->merchant_pub;
- rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
- TALER_amount_hton (&rc.refund_amount,
- &refund->refund_amount);
- TALER_amount_hton (&rc.refund_fee,
- &refund->refund_fee);
- TEH_KS_sign (&rc.purpose,
- &pub,
- &sig);
- return TEH_RESPONSE_reply_json_pack (connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o}",
- "status", "REFUND_OK",
- "sig", GNUNET_JSON_from_data_auto (&sig),
- "pub", GNUNET_JSON_from_data_auto (&pub));
-}
-
-
-/**
* Send a response for a failed "/refresh/melt" request. The
* transaction history of the given coin demonstrates that the
* @a residual value of the coin is below the @a requested
@@ -983,7 +903,7 @@ TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *conne
{
json_t *history;
- history = compile_transaction_history (tl);
+ history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history)
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);
diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h
index aef5bf94d..27f253b78 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -274,46 +274,6 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
/**
- * Generate refund conflict failure message. Returns the
- * transaction list @a tl with the details about the conflict.
- *
- * @param connection connection to the client
- * @param tl transaction list showing the conflict
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_conflict (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_TransactionList *tl);
-
-
-/**
- * Generate generic refund failure message. All the details
- * are in the @a response_code. The body can be empty.
- *
- * @param connection connection to the client
- * @param response_code response code to generate
- * @param ec error code uniquely identifying the error
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_failure (struct MHD_Connection *connection,
- unsigned int response_code,
- enum TALER_ErrorCode ec);
-
-
-/**
- * Generate successful refund confirmation message.
- *
- * @param connection connection to the client
- * @param refund details about the successful refund
- * @return MHD result code
- */
-int
-TEH_RESPONSE_reply_refund_success (struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refund *refund);
-
-
-/**
* A merchant asked for details about a deposit, but
* we do not know anything about the deposit. Generate the
* 404 reply.
@@ -555,8 +515,19 @@ int
TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
+
const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute timestamp);
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh
+ */
+json_t *
+TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl);
+
+
#endif
diff --git a/src/exchangedb/perf_taler_exchangedb_interpreter.c b/src/exchangedb/perf_taler_exchangedb_interpreter.c
index 5e5c6a399..3f52e90e2 100644
--- a/src/exchangedb/perf_taler_exchangedb_interpreter.c
+++ b/src/exchangedb/perf_taler_exchangedb_interpreter.c
@@ -1529,13 +1529,16 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
unsigned int coin_index;
struct PERF_TALER_EXCHANGEDB_Coin *coin;
struct TALER_EXCHANGEDB_TransactionList *transactions;
+ enum GNUNET_DB_QueryStatus qs;
coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin;
coin = state->cmd[coin_index].exposed.data.coin;
- transactions = state->plugin->get_coin_transactions (state->plugin->cls,
- state->session,
- &coin->public_info.coin_pub);
- GNUNET_assert (transactions != NULL);
+ qs = state->plugin->get_coin_transactions (state->plugin->cls,
+ state->session,
+ &coin->public_info.coin_pub,
+ &transactions);
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ GNUNET_assert (transactions != NULL);
state->plugin->free_coin_transaction_list (state->plugin->cls,
transactions);
}
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 41b5f720d..9eec10399 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -2827,10 +2827,11 @@ postgres_mark_deposit_tiny (void *cls,
* @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database
* @param deposit the deposit to check
- * @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not,
- * #GNUNET_SYSERR on error (deposit unknown)
+ * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
+ * otherwise transaction error status (incl. deposit unknown)
*/
-static int
+static enum GNUNET_DB_QueryStatus
postgres_test_deposit_done (void *cls,
struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit)
@@ -2842,50 +2843,25 @@ postgres_test_deposit_done (void *cls,
GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
GNUNET_PQ_query_param_end
};
- PGresult *result;
-
- result = GNUNET_PQ_exec_prepared (session->conn,
- "test_deposit_done",
- params);
- if (PGRES_TUPLES_OK !=
- PQresultStatus (result))
- {
- BREAK_DB_ERR (result, session->conn);
- PQclear (result);
- return GNUNET_SYSERR;
- }
- if (0 == PQntuples (result))
- {
- PQclear (result);
- return GNUNET_SYSERR;
- }
- if (1 != PQntuples (result))
- {
- GNUNET_break (0);
- PQclear (result);
- return GNUNET_SYSERR;
- }
-
- {
- uint8_t done = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
+ uint8_t done = 0;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("done",
+ &done),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- 0))
- {
- GNUNET_break (0);
- PQclear (result);
- return GNUNET_SYSERR;
- }
- PQclear (result);
- return (done ? GNUNET_YES : GNUNET_NO);
- }
+ qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
+ "test_deposit_done",
+ params,
+ rs);
+ if (qs < 0)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */
+ return (done
+ ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS);
}
@@ -4071,317 +4047,385 @@ postgres_get_transfer (void *cls,
/**
- * Compile a list of all (historic) transactions performed
- * with the given coin (/refresh/melt, /deposit and /refund operations).
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param session database connection
- * @param coin_pub coin to investigate
- * @return list of transactions, NULL if coin is fresh
+ * Closure for callbacks called from #postgres_get_coin_transactions()
*/
-static struct TALER_EXCHANGEDB_TransactionList *
-postgres_get_coin_transactions (void *cls,
- struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub)
+struct CoinHistoryContext
{
+ /**
+ * Head of the coin's history list.
+ */
struct TALER_EXCHANGEDB_TransactionList *head;
- head = NULL;
- /** #TALER_EXCHANGEDB_TT_DEPOSIT */
+ /**
+ * 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;
+
+ /**
+ * Database session we are using.
+ */
+ struct TALER_EXCHANGEDB_Session *session;
+
+ /**
+ * Set to #GNUNET_SYSERR on errors
+ */
+ int status;
+};
+
+
+/**
+ * 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_result the number of results in @a result
+ */
+static void
+add_coin_deposit (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- int nrows;
- PGresult *result;
+ struct TALER_EXCHANGEDB_Deposit *deposit;
struct TALER_EXCHANGEDB_TransactionList *tl;
-
- result = GNUNET_PQ_exec_prepared (session->conn,
- "get_deposit_with_coin_pub",
- params);
- if (PGRES_TUPLES_OK != PQresultStatus (result))
- {
- QUERY_ERR (result, session->conn);
- PQclear (result);
- goto cleanup;
- }
- nrows = PQntuples (result);
- for (int i = 0; i < nrows; i++)
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
{
- struct TALER_EXCHANGEDB_Deposit *deposit;
-
- deposit = GNUNET_new (struct TALER_EXCHANGEDB_Deposit);
- {
- 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_absolute_time ("timestamp",
- &deposit->timestamp),
- GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit->refund_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 ("h_wire",
- &deposit->h_wire),
- TALER_PQ_result_spec_json ("wire",
- &deposit->receiver_wire_account),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &deposit->csig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (deposit);
- PQclear (result);
- goto cleanup;
- }
- deposit->coin.coin_pub = *coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = head;
- tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
- tl->details.deposit = deposit;
- if (GNUNET_SYSERR == get_known_coin (cls,
- session,
- &deposit->coin.coin_pub,
- &deposit->coin))
+ 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_absolute_time ("timestamp",
+ &deposit->timestamp),
+ GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
+ &deposit->refund_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 ("h_wire",
+ &deposit->h_wire),
+ TALER_PQ_result_spec_json ("wire",
+ &deposit->receiver_wire_account),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->csig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
{
- GNUNET_break (0);
- GNUNET_free (deposit);
- PQclear (result);
- goto cleanup;
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->status = GNUNET_SYSERR;
+ return;
}
- head = tl;
- continue;
+ deposit->coin.coin_pub = *chc->coin_pub;
}
- PQclear (result);
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
+ tl->details.deposit = deposit;
+ if (GNUNET_SYSERR == get_known_coin (chc->db_cls,
+ chc->session,
+ chc->coin_pub,
+ &deposit->coin))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->status = GNUNET_SYSERR;
+ return;
+ }
+ chc->head = tl;
}
- /** #TALER_EXCHANGEDB_TT_REFRESH_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_result the number of results in @a result
+ */
+static void
+add_coin_melt (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+
+ for (unsigned int i=0;i<num_results;i++)
{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin_pub->eddsa_pub),
- GNUNET_PQ_query_param_end
- };
- int nrows;
- PGresult *result;
+ struct TALER_EXCHANGEDB_RefreshMelt *melt;
struct TALER_EXCHANGEDB_TransactionList *tl;
- /* check if the melt records exist and get them */
- result = GNUNET_PQ_exec_prepared (session->conn,
- "get_refresh_session_by_coin",
- params);
- if (PGRES_TUPLES_OK != PQresultStatus (result))
- {
- BREAK_DB_ERR (result, session->conn);
- PQclear (result);
- goto cleanup;
- }
- nrows = PQntuples (result);
- for (int i=0;i<nrows;i++)
+ melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
{
- struct TALER_EXCHANGEDB_RefreshMelt *melt;
-
- melt = GNUNET_new (struct TALER_EXCHANGEDB_RefreshMelt);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("session_hash",
- &melt->session_hash),
- /* oldcoin_index not needed */
- 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_end
- };
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (melt);
- PQclear (result);
- goto cleanup;
- }
- melt->coin.coin_pub = *coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = head;
- tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT;
- tl->details.melt = melt;
- if (GNUNET_SYSERR == get_known_coin (cls,
- session,
- coin_pub,
- &melt->coin))
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("session_hash",
+ &melt->session_hash),
+ /* oldcoin_index not needed */
+ 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_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
{
- GNUNET_break (0);
- GNUNET_free (melt);
- PQclear (result);
- goto cleanup;
+ GNUNET_break (0);
+ GNUNET_free (melt);
+ chc->status = GNUNET_SYSERR;
+ return;
}
- head = tl;
- continue;
+ melt->coin.coin_pub = *chc->coin_pub;
}
- PQclear (result);
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_REFRESH_MELT;
+ tl->details.melt = melt;
+ if (GNUNET_SYSERR == get_known_coin (chc->db_cls,
+ chc->session,
+ chc->coin_pub,
+ &melt->coin))
+ {
+ GNUNET_break (0);
+ GNUNET_free (melt);
+ chc->status = GNUNET_SYSERR;
+ return;
+ }
+ chc->head = tl;
}
- /** #TALER_EXCHANGEDB_TT_REFUND */
+}
+
+
+/**
+ * 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_result the number of results in @a result
+ */
+static void
+add_coin_refund (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+
+ for (unsigned int i=0;i<num_results;i++)
{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- int nrows;
- PGresult *result;
+ struct TALER_EXCHANGEDB_Refund *refund;
struct TALER_EXCHANGEDB_TransactionList *tl;
- /* check if a refund records exist and get them */
- result = GNUNET_PQ_exec_prepared (session->conn,
- "get_refunds_by_coin",
- params);
- if (PGRES_TUPLES_OK != PQresultStatus (result))
+ refund = GNUNET_new (struct TALER_EXCHANGEDB_Refund);
{
- BREAK_DB_ERR (result, session->conn);
- PQclear (result);
- goto cleanup;
- }
- nrows = PQntuples (result);
- for (int i=0;i<nrows;i++)
- {
- struct TALER_EXCHANGEDB_Refund *refund;
-
- refund = GNUNET_new (struct TALER_EXCHANGEDB_Refund);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &refund->merchant_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_end
- };
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (refund);
- PQclear (result);
- goto cleanup;
- }
- refund->coin.coin_pub = *coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = head;
- tl->type = TALER_EXCHANGEDB_TT_REFUND;
- tl->details.refund = refund;
- if (GNUNET_SYSERR ==
- get_known_coin (cls,
- session,
- coin_pub,
- &refund->coin))
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &refund->merchant_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_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
{
- GNUNET_break (0);
- GNUNET_free (refund);
- PQclear (result);
- goto cleanup;
+ GNUNET_break (0);
+ GNUNET_free (refund);
+ chc->status = GNUNET_SYSERR;
+ return;
}
- head = tl;
- continue;
+ refund->coin.coin_pub = *chc->coin_pub;
}
- PQclear (result);
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_REFUND;
+ tl->details.refund = refund;
+ if (GNUNET_SYSERR ==
+ get_known_coin (chc->db_cls,
+ chc->session,
+ chc->coin_pub,
+ &refund->coin))
+ {
+ GNUNET_break (0);
+ GNUNET_free (refund);
+ chc->status = GNUNET_SYSERR;
+ return;
+ }
+ chc->head = tl;
}
- /** #TALER_EXCHANGEDB_TT_PAYBACK */
+}
+
+
+/**
+ * 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_result the number of results in @a result
+ */
+static void
+add_coin_payback (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+
+ for (unsigned int i=0;i<num_results;i++)
{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- int nrows;
- PGresult *result;
+ struct TALER_EXCHANGEDB_Payback *payback;
struct TALER_EXCHANGEDB_TransactionList *tl;
-
- /* check if a refund records exist and get them */
- result = GNUNET_PQ_exec_prepared (session->conn,
- "payback_by_coin",
- params);
- if (PGRES_TUPLES_OK != PQresultStatus (result))
- {
- BREAK_DB_ERR (result, session->conn);
- PQclear (result);
- goto cleanup;
- }
- nrows = PQntuples (result);
- for (int i=0;i<nrows;i++)
+
+ payback = GNUNET_new (struct TALER_EXCHANGEDB_Payback);
{
- struct TALER_EXCHANGEDB_Payback *payback;
-
- payback = GNUNET_new (struct TALER_EXCHANGEDB_Payback);
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("amount",
+ &payback->value),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &payback->reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &payback->coin_blind),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &payback->coin_sig),
+ GNUNET_PQ_result_spec_absolute_time ("timestamp",
+ &payback->timestamp),
+ GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
+ &payback->coin.denom_pub.rsa_public_key),
+ GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
+ &payback->coin.denom_sig.rsa_signature),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
{
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_amount ("amount",
- &payback->value),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &payback->reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &payback->coin_blind),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &payback->coin_sig),
- GNUNET_PQ_result_spec_absolute_time ("timestamp",
- &payback->timestamp),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &payback->coin.denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &payback->coin.denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (payback);
- PQclear (result);
- goto cleanup;
- }
- payback->coin.coin_pub = *coin_pub;
+ GNUNET_break (0);
+ GNUNET_free (payback);
+ chc->status = GNUNET_SYSERR;
+ return;
}
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = head;
- tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
- tl->details.payback = payback;
- head = tl;
- continue;
+ payback->coin.coin_pub = *chc->coin_pub;
}
- PQclear (result);
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
+ tl->details.payback = payback;
+ chc->head = tl;
}
+}
- return head;
- cleanup:
- if (NULL != head)
- common_free_coin_transaction_list (cls,
- head);
- return NULL;
+
+/**
+ * Compile a list of all (historic) transactions performed
+ * with the given coin (/refresh/melt, /deposit and /refund operations).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param session database connection
+ * @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,
+ struct TALER_EXCHANGEDB_Session *session,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp)
+{
+ struct CoinHistoryContext chc;
+ 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 {
+ /**
+ * SQL prepared statement name.
+ */
+ const char *statement;
+
+ /**
+ * Function to call to handle the result(s).
+ */
+ GNUNET_PQ_PostgresResultHandler cb;
+ } work[] = {
+ /** #TALER_EXCHANGEDB_TT_DEPOSIT */
+ { "get_deposit_with_coin_pub",
+ &add_coin_deposit },
+ /** #TALER_EXCHANGEDB_TT_REFRESH_MELT */
+ { "get_refresh_session_by_coin",
+ &add_coin_melt },
+ /** #TALER_EXCHANGEDB_TT_REFUND */
+ { "get_refunds_by_coin",
+ &add_coin_refund },
+ /** #TALER_EXCHANGEDB_TT_PAYBACK */
+ { "payback_by_coin",
+ &add_coin_payback },
+ { NULL, NULL }
+ };
+
+ chc.head = NULL;
+ chc.status = GNUNET_OK;
+ chc.coin_pub = coin_pub;
+ chc.session = session;
+ chc.db_cls = cls;
+ for (unsigned int i=0;NULL != work[i].statement; i++)
+ {
+ qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
+ work[i].statement,
+ params,
+ work[i].cb,
+ &chc);
+ if ( (0 > qs) ||
+ (GNUNET_OK != chc.status) )
+ {
+ if (NULL != chc.head)
+ common_free_coin_transaction_list (cls,
+ chc.head);
+ *tlp = NULL;
+ if (GNUNET_OK != chc.status)
+ 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/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index ac1301d44..19e8caa2e 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -726,10 +726,13 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
{
/* Just to test fetching a coin with melt history */
struct TALER_EXCHANGEDB_TransactionList *tl;
+ enum GNUNET_DB_QueryStatus qs;
- tl = plugin->get_coin_transactions (plugin->cls,
+ qs = plugin->get_coin_transactions (plugin->cls,
session,
- &meltp->coin.coin_pub);
+ &meltp->coin.coin_pub,
+ &tl);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
plugin->free_coin_transaction_list (plugin->cls,
tl);
}
@@ -1930,9 +1933,11 @@ run (void *cls)
NULL));
FAILIF (1 != auditor_row_cnt);
- tl = plugin->get_coin_transactions (plugin->cls,
+ qs = plugin->get_coin_transactions (plugin->cls,
session,
- &refund.coin.coin_pub);
+ &refund.coin.coin_pub,
+ &tl);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
GNUNET_assert (NULL != tl);
matched = 0;
for (tlp = tl; NULL != tlp; tlp = tlp->next)
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index af6b5547d..8a2010819 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -1346,16 +1346,17 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * Test if a deposit was marked as done, thereby declaring that it cannot be
- * refunded anymore.
+ * Test if a deposit was marked as done, thereby declaring that it
+ * cannot be refunded anymore.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database
* @param deposit the deposit to check
- * @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not,
- * #GNUNET_SYSERR on error (deposit unknown)
+ * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
+ * otherwise transaction error status (incl. deposit unknown)
*/
- int
+ enum GNUNET_DB_QueryStatus
(*test_deposit_done) (void *cls,
struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit);
@@ -1700,12 +1701,14 @@ struct TALER_EXCHANGEDB_Plugin
* @param cls the @e cls of this struct with the plugin-specific state
* @param session database connection
* @param coin_pub coin to investigate
- * @return list of transactions, NULL if coin is fresh
+ * @param[out] tlp set to list of transactions, NULL if coin is fresh
+ * @return database transaction status
*/
- struct TALER_EXCHANGEDB_TransactionList *
+ enum GNUNET_DB_QueryStatus
(*get_coin_transactions) (void *cls,
struct TALER_EXCHANGEDB_Session *session,
- const struct TALER_CoinSpendPublicKeyP *coin_pub);
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp);
/**