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_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",

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
* 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,6 +1651,7 @@ 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)))
{
@ -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 !=

View File

@ -83,20 +83,6 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
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.
*/

View File

@ -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,

View File

@ -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
@ -36,6 +36,350 @@
#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
* 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)
{
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);
}

View File

@ -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,
@ -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
* 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;
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);

View File

@ -273,46 +273,6 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection,
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
* we do not know anything about the deposit. Generate the
@ -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

View File

@ -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);
}

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 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;
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,
"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
};
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);
}
@ -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
* 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 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
*/
static struct TALER_EXCHANGEDB_TransactionList *
static enum GNUNET_DB_QueryStatus
postgres_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)
{
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[] = {
GNUNET_PQ_query_param_auto_from_type (coin_pub),
GNUNET_PQ_query_param_end
};
int nrows;
PGresult *result;
struct TALER_EXCHANGEDB_TransactionList *tl;
result = GNUNET_PQ_exec_prepared (session->conn,
"get_deposit_with_coin_pub",
params);
if (PGRES_TUPLES_OK != PQresultStatus (result))
qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
work[i].statement,
params,
work[i].cb,
&chc);
if ( (0 > qs) ||
(GNUNET_OK != chc.status) )
{
QUERY_ERR (result, session->conn);
PQclear (result);
goto cleanup;
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;
}
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 */
{
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_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;
*tlp = chc.head;
if (NULL == chc.head)
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}

View File

@ -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)

View File

@ -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);
/**