address #5010 for /refund

This commit is contained in:
Christian Grothoff 2017-06-19 16:07:34 +02:00
parent 3d701e8d2a
commit 92e6744ac0
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
11 changed files with 804 additions and 782 deletions

View File

@ -1864,12 +1864,15 @@ wire_transfer_information_cb (void *cls,
struct TALER_Amount coin_value_without_fee; struct TALER_Amount coin_value_without_fee;
struct TALER_EXCHANGEDB_TransactionList *tl; struct TALER_EXCHANGEDB_TransactionList *tl;
const struct TALER_CoinPublicInfo *coin; const struct TALER_CoinPublicInfo *coin;
enum GNUNET_DB_QueryStatus qs;
/* Obtain coin's transaction history */ /* Obtain coin's transaction history */
tl = edb->get_coin_transactions (edb->cls, qs = edb->get_coin_transactions (edb->cls,
esession, esession,
coin_pub); coin_pub,
if (NULL == tl) &tl);
if ( (qs < 0) ||
(NULL == tl) )
{ {
wcc->ok = GNUNET_SYSERR; wcc->ok = GNUNET_SYSERR;
report_row_inconsistency ("aggregation", report_row_inconsistency ("aggregation",

View File

@ -279,279 +279,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 * Parse coin melt requests from a JSON object and write them to
* the database. * the database.
@ -581,6 +308,7 @@ refresh_check_melt (struct MHD_Connection *connection,
struct TALER_Amount coin_residual; struct TALER_Amount coin_residual;
struct TALER_Amount spent; struct TALER_Amount spent;
int res; int res;
enum GNUNET_DB_QueryStatus qs;
dk = TEH_KS_denomination_key_lookup (key_state, dk = TEH_KS_denomination_key_lookup (key_state,
&coin_details->coin_info.denom_pub, &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! */ /* fee for THIS transaction; the melt amount includes the fee! */
spent = coin_details->melt_amount_with_fee; spent = coin_details->melt_amount_with_fee;
/* add historic transaction costs of this coin */ /* 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, session,
&coin_details->coin_info.coin_pub); &coin_details->coin_info.coin_pub,
&tl);
(void) qs; /* FIXME #5010 */
if (GNUNET_OK != if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl, TEH_DB_calculate_transaction_list_totals (tl,
&spent, &spent,
@ -1921,7 +1651,8 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
struct TALER_Amount amount; struct TALER_Amount amount;
struct TALER_Amount spent; struct TALER_Amount spent;
struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute now;
enum GNUNET_DB_QueryStatus qs;
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{ {
GNUNET_break (0); GNUNET_break (0);
@ -1955,9 +1686,11 @@ TEH_DB_execute_payback (struct MHD_Connection *connection,
} }
/* Calculate remaining balance. */ /* Calculate remaining balance. */
tl = TEH_plugin->get_coin_transactions (TEH_plugin->cls, qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session, session,
&coin->coin_pub); &coin->coin_pub,
&tl);
(void) qs; /* FIXME #5010 */
TALER_amount_get_zero (value->currency, TALER_amount_get_zero (value->currency,
&spent); &spent);
if (GNUNET_OK != if (GNUNET_OK !=

View File

@ -83,20 +83,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
struct TALER_Amount *ret); struct TALER_Amount *ret);
/**
* 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. * @brief Details about a melt operation of an individual coin.
*/ */

View File

@ -163,9 +163,12 @@ deposit_transaction (void *cls,
/* Start with fee for THIS transaction */ /* Start with fee for THIS transaction */
spent = deposit->amount_with_fee; spent = deposit->amount_with_fee;
/* add cost of all previous transactions */ /* 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, session,
&deposit->coin.coin_pub); &deposit->coin.coin_pub,
&tl);
if (0 > qs)
return qs;
if (GNUNET_OK != if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl, TEH_DB_calculate_transaction_list_totals (tl,
&spent, &spent,

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 terms of the GNU Affero General Public License as published by the Free Software
@ -36,6 +36,350 @@
#include "taler-exchange-httpd_validation.h" #include "taler-exchange-httpd_validation.h"
/**
* 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 * We have parsed the JSON information about the refund, do some basic
* sanity checks (especially that the signature on the coin is valid) * sanity checks (especially that the signature on the coin is valid)
@ -51,6 +395,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_Refund *refund) const struct TALER_EXCHANGEDB_Refund *refund)
{ {
struct TALER_RefundRequestPS rr; struct TALER_RefundRequestPS rr;
int mhd_ret;
rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); 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, TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
"merchant_sig"); "merchant_sig");
} }
return TEH_DB_execute_refund (connection, if (GNUNET_OK !=
refund); TEH_DB_run_transaction (connection,
&mhd_ret,
&refund_transaction,
(void *) refund))
return mhd_ret;
return reply_refund_success (connection,
refund);
} }

View File

@ -467,8 +467,8 @@ TEH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection)
* @param tl transaction history to JSON-ify * @param tl transaction history to JSON-ify
* @return json representation of the @a rh * @return json representation of the @a rh
*/ */
static json_t * json_t *
compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl) TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_TransactionList *tl)
{ {
json_t *history; json_t *history;
@ -663,7 +663,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
{ {
json_t *history; json_t *history;
history = compile_transaction_history (tl); history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history) if (NULL == history)
return TEH_RESPONSE_reply_internal_error (connection, return TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, TALER_EC_COIN_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
@ -878,86 +878,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 * Send a response for a failed "/refresh/melt" request. The
* transaction history of the given coin demonstrates that the * transaction history of the given coin demonstrates that the
@ -983,7 +903,7 @@ TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *conne
{ {
json_t *history; json_t *history;
history = compile_transaction_history (tl); history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history) if (NULL == history)
return TEH_RESPONSE_reply_internal_db_error (connection, return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS); TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);

View File

@ -273,46 +273,6 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_TransactionList *tl); const struct TALER_EXCHANGEDB_TransactionList *tl);
/**
* 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 * A merchant asked for details about a deposit, but
* we do not know anything about the deposit. Generate the * we do not know anything about the deposit. Generate the
@ -555,8 +515,19 @@ int
TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection, TEH_RESPONSE_reply_payback_success (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *amount, const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute timestamp); 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 #endif

View File

@ -1529,13 +1529,16 @@ interpret (struct PERF_TALER_EXCHANGEDB_interpreter_state *state)
unsigned int coin_index; unsigned int coin_index;
struct PERF_TALER_EXCHANGEDB_Coin *coin; struct PERF_TALER_EXCHANGEDB_Coin *coin;
struct TALER_EXCHANGEDB_TransactionList *transactions; struct TALER_EXCHANGEDB_TransactionList *transactions;
enum GNUNET_DB_QueryStatus qs;
coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin; coin_index = state->cmd[state->i].details.get_coin_transaction.index_coin;
coin = state->cmd[coin_index].exposed.data.coin; coin = state->cmd[coin_index].exposed.data.coin;
transactions = state->plugin->get_coin_transactions (state->plugin->cls, qs = state->plugin->get_coin_transactions (state->plugin->cls,
state->session, state->session,
&coin->public_info.coin_pub); &coin->public_info.coin_pub,
GNUNET_assert (transactions != NULL); &transactions);
GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
GNUNET_assert (transactions != NULL);
state->plugin->free_coin_transaction_list (state->plugin->cls, state->plugin->free_coin_transaction_list (state->plugin->cls,
transactions); transactions);
} }

View File

@ -2827,10 +2827,11 @@ postgres_mark_deposit_tiny (void *cls,
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database * @param session connection to the database
* @param deposit the deposit to check * @param deposit the deposit to check
* @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not, * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
* #GNUNET_SYSERR on error (deposit unknown) * #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, postgres_test_deposit_done (void *cls,
struct TALER_EXCHANGEDB_Session *session, struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit) 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_auto_from_type (&deposit->h_wire),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
PGresult *result; 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;
result = GNUNET_PQ_exec_prepared (session->conn, qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
"test_deposit_done", "test_deposit_done",
params); params,
if (PGRES_TUPLES_OK != rs);
PQresultStatus (result)) if (qs < 0)
{ return qs;
BREAK_DB_ERR (result, session->conn); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
PQclear (result); return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */
return GNUNET_SYSERR; return (done
} ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
if (0 == PQntuples (result)) : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS);
{
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
};
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);
}
} }
@ -4070,6 +4046,310 @@ postgres_get_transfer (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;
/**
* 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 TALER_EXCHANGEDB_Deposit *deposit;
struct TALER_EXCHANGEDB_TransactionList *tl;
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);
chc->status = GNUNET_SYSERR;
return;
}
deposit->coin.coin_pub = *chc->coin_pub;
}
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;
}
}
/**
* 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 TALER_EXCHANGEDB_RefreshMelt *melt;
struct TALER_EXCHANGEDB_TransactionList *tl;
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);
chc->status = GNUNET_SYSERR;
return;
}
melt->coin.coin_pub = *chc->coin_pub;
}
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;
}
}
/**
* 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 TALER_EXCHANGEDB_Refund *refund;
struct TALER_EXCHANGEDB_TransactionList *tl;
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);
chc->status = GNUNET_SYSERR;
return;
}
refund->coin.coin_pub = *chc->coin_pub;
}
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;
}
}
/**
* 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 TALER_EXCHANGEDB_Payback *payback;
struct TALER_EXCHANGEDB_TransactionList *tl;
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))
{
GNUNET_break (0);
GNUNET_free (payback);
chc->status = GNUNET_SYSERR;
return;
}
payback->coin.coin_pub = *chc->coin_pub;
}
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
tl->next = chc->head;
tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
tl->details.payback = payback;
chc->head = tl;
}
}
/** /**
* Compile a list of all (historic) transactions performed * Compile a list of all (historic) transactions performed
* with the given coin (/refresh/melt, /deposit and /refund operations). * with the given coin (/refresh/melt, /deposit and /refund operations).
@ -4077,311 +4357,75 @@ postgres_get_transfer (void *cls,
* @param cls the `struct PostgresClosure` with the plugin-specific state * @param cls the `struct PostgresClosure` with the plugin-specific state
* @param session database connection * @param session database connection
* @param coin_pub coin to investigate * @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
*/ */
static struct TALER_EXCHANGEDB_TransactionList * static enum GNUNET_DB_QueryStatus
postgres_get_coin_transactions (void *cls, postgres_get_coin_transactions (void *cls,
struct TALER_EXCHANGEDB_Session *session, struct TALER_EXCHANGEDB_Session *session,
const struct TALER_CoinSpendPublicKeyP *coin_pub) const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_EXCHANGEDB_TransactionList **tlp)
{ {
struct TALER_EXCHANGEDB_TransactionList *head; 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;
head = NULL; /**
/** #TALER_EXCHANGEDB_TT_DEPOSIT */ * 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++)
{ {
struct GNUNET_PQ_QueryParam params[] = { qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
GNUNET_PQ_query_param_auto_from_type (coin_pub), work[i].statement,
GNUNET_PQ_query_param_end params,
}; work[i].cb,
int nrows; &chc);
PGresult *result; if ( (0 > qs) ||
struct TALER_EXCHANGEDB_TransactionList *tl; (GNUNET_OK != chc.status) )
result = GNUNET_PQ_exec_prepared (session->conn,
"get_deposit_with_coin_pub",
params);
if (PGRES_TUPLES_OK != PQresultStatus (result))
{ {
QUERY_ERR (result, session->conn); if (NULL != chc.head)
PQclear (result); common_free_coin_transaction_list (cls,
goto cleanup; chc.head);
*tlp = NULL;
if (GNUNET_OK != chc.status)
qs = GNUNET_DB_STATUS_HARD_ERROR;
return qs;
} }
nrows = PQntuples (result);
for (int i = 0; i < nrows; i++)
{
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))
{
GNUNET_break (0);
GNUNET_free (deposit);
PQclear (result);
goto cleanup;
}
head = tl;
continue;
}
PQclear (result);
} }
/** #TALER_EXCHANGEDB_TT_REFRESH_MELT */ *tlp = chc.head;
{ if (NULL == chc.head)
struct GNUNET_PQ_QueryParam params[] = { return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
GNUNET_PQ_query_param_auto_from_type (&coin_pub->eddsa_pub), return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
GNUNET_PQ_query_param_end
};
int nrows;
PGresult *result;
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++)
{
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))
{
GNUNET_break (0);
GNUNET_free (melt);
PQclear (result);
goto cleanup;
}
head = tl;
continue;
}
PQclear (result);
}
/** #TALER_EXCHANGEDB_TT_REFUND */
{
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_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))
{
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))
{
GNUNET_break (0);
GNUNET_free (refund);
PQclear (result);
goto cleanup;
}
head = tl;
continue;
}
PQclear (result);
}
/** #TALER_EXCHANGEDB_TT_PAYBACK */
{
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_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++)
{
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))
{
GNUNET_break (0);
GNUNET_free (payback);
PQclear (result);
goto cleanup;
}
payback->coin.coin_pub = *coin_pub;
}
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
tl->next = head;
tl->type = TALER_EXCHANGEDB_TT_PAYBACK;
tl->details.payback = payback;
head = tl;
continue;
}
PQclear (result);
}
return head;
cleanup:
if (NULL != head)
common_free_coin_transaction_list (cls,
head);
return NULL;
} }

View File

@ -726,10 +726,13 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
{ {
/* Just to test fetching a coin with melt history */ /* Just to test fetching a coin with melt history */
struct TALER_EXCHANGEDB_TransactionList *tl; 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, 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, plugin->free_coin_transaction_list (plugin->cls,
tl); tl);
} }
@ -1930,9 +1933,11 @@ run (void *cls)
NULL)); NULL));
FAILIF (1 != auditor_row_cnt); FAILIF (1 != auditor_row_cnt);
tl = plugin->get_coin_transactions (plugin->cls, qs = plugin->get_coin_transactions (plugin->cls,
session, session,
&refund.coin.coin_pub); &refund.coin.coin_pub,
&tl);
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
GNUNET_assert (NULL != tl); GNUNET_assert (NULL != tl);
matched = 0; matched = 0;
for (tlp = tl; NULL != tlp; tlp = tlp->next) for (tlp = tl; NULL != tlp; tlp = tlp->next)

View File

@ -1346,16 +1346,17 @@ struct TALER_EXCHANGEDB_Plugin
/** /**
* Test if a deposit was marked as done, thereby declaring that it cannot be * Test if a deposit was marked as done, thereby declaring that it
* refunded anymore. * cannot be refunded anymore.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param session connection to the database * @param session connection to the database
* @param deposit the deposit to check * @param deposit the deposit to check
* @return #GNUNET_YES if is is marked done done, #GNUNET_NO if not, * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
* #GNUNET_SYSERR on error (deposit unknown) * #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, (*test_deposit_done) (void *cls,
struct TALER_EXCHANGEDB_Session *session, struct TALER_EXCHANGEDB_Session *session,
const struct TALER_EXCHANGEDB_Deposit *deposit); 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 cls the @e cls of this struct with the plugin-specific state
* @param session database connection * @param session database connection
* @param coin_pub coin to investigate * @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, (*get_coin_transactions) (void *cls,
struct TALER_EXCHANGEDB_Session *session, struct TALER_EXCHANGEDB_Session *session,
const struct TALER_CoinSpendPublicKeyP *coin_pub); const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_EXCHANGEDB_TransactionList **tlp);
/** /**