simplify refund processing, add additional checks for matching currency

This commit is contained in:
Christian Grothoff 2020-03-16 20:22:30 +01:00
parent c04bcb0a82
commit cd83daaeae
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
4 changed files with 281 additions and 238 deletions

View File

@ -417,8 +417,8 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&hc); &hc);
if (NULL == dki) if (NULL == dki)
{ {
TEH_KS_release (key_state);
TALER_LOG_WARNING ("Unknown denomination key in /deposit request\n"); TALER_LOG_WARNING ("Unknown denomination key in /deposit request\n");
TEH_KS_release (key_state);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
hc, hc,
@ -427,6 +427,18 @@ TEH_handler_deposit (struct MHD_Connection *connection,
} }
TALER_amount_ntoh (&deposit.deposit_fee, TALER_amount_ntoh (&deposit.deposit_fee,
&dki->issue.properties.fee_deposit); &dki->issue.properties.fee_deposit);
if (GNUNET_YES !=
TALER_amount_cmp_currency (&deposit.amount_with_fee,
&deposit.deposit_fee) )
{
GNUNET_break_op (0);
TEH_KS_release (key_state);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_DEPOSIT_CURRENCY_MISSMATCH,
"contribution");
}
/* check coin signature */ /* check coin signature */
if (GNUNET_YES != if (GNUNET_YES !=
TALER_test_coin_valid (&deposit.coin, TALER_test_coin_valid (&deposit.coin,

View File

@ -15,7 +15,7 @@
*/ */
/** /**
* @file taler-exchange-httpd_refund.c * @file taler-exchange-httpd_refund.c
* @brief Handle /refund requests; parses the POST and JSON and * @brief Handle refund requests; parses the POST and JSON and
* verifies the coin signature before handing things off * verifies the coin signature before handing things off
* to the database. * to the database.
* @author Florian Dold * @author Florian Dold
@ -48,16 +48,17 @@ reply_refund_success (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_EXCHANGEDB_RefundListEntry *refund) const struct TALER_EXCHANGEDB_RefundListEntry *refund)
{ {
struct TALER_RefundConfirmationPS rc;
struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig; struct TALER_ExchangeSignatureP sig;
struct TALER_RefundConfirmationPS rc = {
.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
.purpose.size = htonl (sizeof (rc)),
.h_contract_terms = refund->h_contract_terms,
.coin_pub = *coin_pub,
.merchant = refund->merchant_pub,
.rtransaction_id = GNUNET_htonll (refund->rtransaction_id)
};
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 = *coin_pub;
rc.merchant = refund->merchant_pub;
rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
TALER_amount_hton (&rc.refund_amount, TALER_amount_hton (&rc.refund_amount,
&refund->refund_amount); &refund->refund_amount);
TALER_amount_hton (&rc.refund_fee, TALER_amount_hton (&rc.refund_fee,
@ -70,7 +71,7 @@ reply_refund_success (struct MHD_Connection *connection,
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_BAD_CONFIGURATION, TALER_EC_EXCHANGE_BAD_CONFIGURATION,
"no keys"); "no online signing key");
} }
return TALER_MHD_reply_json_pack (connection, return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK, MHD_HTTP_OK,
@ -81,51 +82,6 @@ reply_refund_success (struct MHD_Connection *connection,
} }
/**
* Generate refund conflict failure message. Returns the
* transaction list @a tl with the details about the conflict.
*
* @param connection connection to the client
* @param coin_pub public key this is about
* @param tl transaction list showing the conflict
* @return MHD result code
*/
static int
reply_refund_conflict (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_EXCHANGEDB_TransactionList *tl)
{
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_CONFLICT,
"{s:s, s:I, s:o}",
"hint", "conflicting refund",
"code",
(json_int_t) TALER_EC_REFUND_CONFLICT,
"history",
TEH_RESPONSE_compile_transaction_history (
coin_pub,
tl));
}
/**
* Closure for the transaction.
*/
struct TALER_EXCHANGEDB_RefundContext
{
/**
* Information about the refund.
*/
const struct TALER_EXCHANGEDB_Refund *refund;
/**
* Expected refund fee by the denomination of the coin.
*/
struct TALER_Amount expect_fee;
};
/** /**
* Execute a "/refund" transaction. Returns a confirmation that the * Execute a "/refund" transaction. Returns a confirmation that the
* refund was successful, or a failure if we are not aware of a * refund was successful, or a failure if we are not aware of a
@ -150,18 +106,14 @@ refund_transaction (void *cls,
struct TALER_EXCHANGEDB_Session *session, struct TALER_EXCHANGEDB_Session *session,
int *mhd_ret) int *mhd_ret)
{ {
struct TALER_EXCHANGEDB_RefundContext *rc = cls; const struct TALER_EXCHANGEDB_Refund *refund = cls;
const struct TALER_EXCHANGEDB_Refund *refund = rc->refund;
struct TALER_EXCHANGEDB_TransactionList *tl; struct TALER_EXCHANGEDB_TransactionList *tl;
const struct TALER_EXCHANGEDB_DepositListEntry *dep; const struct TALER_EXCHANGEDB_DepositListEntry *dep;
const struct TALER_EXCHANGEDB_RefundListEntry *ref; const struct TALER_EXCHANGEDB_RefundListEntry *ref;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
int deposit_found; int deposit_found;
int refund_found; int refund_found;
int fee_cmp;
dep = NULL;
ref = NULL;
tl = NULL; tl = NULL;
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session, session,
@ -177,6 +129,8 @@ refund_transaction (void *cls,
"database transaction failure"); "database transaction failure");
return qs; return qs;
} }
dep = NULL;
ref = NULL;
deposit_found = GNUNET_NO; deposit_found = GNUNET_NO;
refund_found = GNUNET_NO; refund_found = GNUNET_NO;
for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl; for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl;
@ -267,9 +221,15 @@ refund_transaction (void *cls,
/* handle if conflicting refund found */ /* handle if conflicting refund found */
if (GNUNET_SYSERR == refund_found) if (GNUNET_SYSERR == refund_found)
{ {
*mhd_ret = reply_refund_conflict (connection, *mhd_ret = TALER_MHD_reply_json_pack (
&refund->coin.coin_pub, connection,
tl); MHD_HTTP_CONFLICT,
"{s:s, s:I, s:o}",
"hint", "conflicting refund",
"code", (json_int_t) TALER_EC_REFUND_CONFLICT,
"history", TEH_RESPONSE_compile_transaction_history (
&refund->coin.coin_pub,
tl));
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl); tl);
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
@ -321,7 +281,7 @@ refund_transaction (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection, *mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_REFUND_DB_INCONSISTENT, TALER_EC_REFUND_DB_INCONSISTENT,
"database inconsistent"); "database inconsistent (deposit data became inaccessible during transaction)");
return qs; return qs;
} }
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -352,24 +312,6 @@ refund_transaction (void *cls,
"refund requested exceeds original value"); "refund requested exceeds original value");
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
/* Check refund fee matches fee of denomination key! */
fee_cmp = TALER_amount_cmp (&refund->details.refund_fee,
&rc->expect_fee);
if (-1 == fee_cmp)
{
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
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, TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl); tl);
@ -405,50 +347,35 @@ static int
verify_and_execute_refund (struct MHD_Connection *connection, verify_and_execute_refund (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_Refund *refund) const struct TALER_EXCHANGEDB_Refund *refund)
{ {
struct TALER_EXCHANGEDB_RefundContext rc;
struct TALER_RefundRequestPS rr;
struct GNUNET_HashCode denom_hash; struct GNUNET_HashCode denom_hash;
struct TALER_Amount expect_fee;
if (GNUNET_YES !=
TALER_amount_cmp_currency (&refund->details.refund_amount,
&refund->details.refund_fee) )
{ {
GNUNET_break_op (0); struct TALER_RefundRequestPS rr = {
return TALER_MHD_reply_with_error (connection, .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
MHD_HTTP_BAD_REQUEST, .purpose.size = htonl (sizeof (rr)),
TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH, .h_contract_terms = refund->details.h_contract_terms,
"refund_fee"); .coin_pub = refund->coin.coin_pub,
} .merchant = refund->details.merchant_pub,
if (-1 == TALER_amount_cmp (&refund->details.refund_amount, .rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id)
&refund->details.refund_fee) ) };
{
GNUNET_break_op (0); TALER_amount_hton (&rr.refund_amount,
return TALER_MHD_reply_with_error (connection, &refund->details.refund_amount);
MHD_HTTP_BAD_REQUEST, TALER_amount_hton (&rr.refund_fee,
TALER_EC_REFUND_FEE_ABOVE_AMOUNT, &refund->details.refund_fee);
"refund_amount"); if (GNUNET_OK !=
} GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); &rr.purpose,
rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); &refund->details.merchant_sig.eddsa_sig,
rr.h_contract_terms = refund->details.h_contract_terms; &refund->details.merchant_pub.eddsa_pub))
rr.coin_pub = refund->coin.coin_pub; {
rr.merchant = refund->details.merchant_pub; TALER_LOG_WARNING ("Invalid signature on refund request\n");
rr.rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id); return TALER_MHD_reply_with_error (connection,
TALER_amount_hton (&rr.refund_amount, MHD_HTTP_FORBIDDEN,
&refund->details.refund_amount); TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
TALER_amount_hton (&rr.refund_fee, "merchant_sig");
&refund->details.refund_fee); }
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
&rr.purpose,
&refund->details.merchant_sig.eddsa_sig,
&refund->details.merchant_pub.eddsa_pub))
{
TALER_LOG_WARNING ("Invalid signature on /refund request\n");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID,
"merchant_sig");
} }
/* Fetch the coin's denomination (hash) */ /* Fetch the coin's denomination (hash) */
@ -503,23 +430,53 @@ verify_and_execute_refund (struct MHD_Connection *connection,
ec, ec,
"denomination not found, but coin known"); "denomination not found, but coin known");
} }
TALER_amount_ntoh (&rc.expect_fee, TALER_amount_ntoh (&expect_fee,
&dki->issue.properties.fee_refund); &dki->issue.properties.fee_refund);
} }
TEH_KS_release (key_state); TEH_KS_release (key_state);
} }
/* Check refund fee matches fee of denomination key! */
if (GNUNET_YES !=
TALER_amount_cmp_currency (&expect_fee,
&refund->details.refund_fee) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH,
"refund_fee");
}
{
int fee_cmp;
fee_cmp = TALER_amount_cmp (&refund->details.refund_fee,
&expect_fee);
if (-1 == fee_cmp)
{
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_REFUND_FEE_TOO_LOW,
"refund_fee");
}
if (1 == fee_cmp)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund fee proposed by merchant is higher than necessary.\n");
}
}
/* Finally run the actual transaction logic */ /* Finally run the actual transaction logic */
{ {
int mhd_ret; int mhd_ret;
rc.refund = refund;
if (GNUNET_OK != if (GNUNET_OK !=
TEH_DB_run_transaction (connection, TEH_DB_run_transaction (connection,
"run refund", "run refund",
&mhd_ret, &mhd_ret,
&refund_transaction, &refund_transaction,
&rc)) (void *) refund))
{ {
return mhd_ret; return mhd_ret;
} }
@ -546,7 +503,6 @@ TEH_handler_refund (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub,
const json_t *root) const json_t *root)
{ {
int res;
struct TALER_EXCHANGEDB_Refund refund; struct TALER_EXCHANGEDB_Refund refund;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("refund_amount", &refund.details.refund_amount), TALER_JSON_spec_amount ("refund_amount", &refund.details.refund_amount),
@ -561,17 +517,46 @@ TEH_handler_refund (struct MHD_Connection *connection,
}; };
refund.coin.coin_pub = *coin_pub; refund.coin.coin_pub = *coin_pub;
res = TALER_MHD_parse_json_data (connection, {
root, int res;
spec);
if (GNUNET_SYSERR == res) res = TALER_MHD_parse_json_data (connection,
return MHD_NO; /* hard failure */ root,
if (GNUNET_NO == res) spec);
return MHD_YES; /* failure */ if (GNUNET_SYSERR == res)
res = verify_and_execute_refund (connection, return MHD_NO; /* hard failure */
&refund); if (GNUNET_NO == res)
GNUNET_JSON_parse_free (spec); return MHD_YES; /* failure */
return res; }
if (GNUNET_YES !=
TALER_amount_cmp_currency (&refund.details.refund_amount,
&refund.details.refund_fee) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH,
"refund_amount or refund_fee");
}
if (-1 == TALER_amount_cmp (&refund.details.refund_amount,
&refund.details.refund_fee) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_REFUND_FEE_ABOVE_AMOUNT,
"refund_amount");
}
{
int res;
res = verify_and_execute_refund (connection,
&refund);
GNUNET_JSON_parse_free (spec);
return res;
}
} }

View File

@ -18,7 +18,7 @@
*/ */
/** /**
* @file taler-exchange-httpd_withdraw.c * @file taler-exchange-httpd_withdraw.c
* @brief Handle /reserve/withdraw requests * @brief Handle /reserves/$RESERVE_PUB/withdraw requests
* @author Florian Dold * @author Florian Dold
* @author Benedikt Mueller * @author Benedikt Mueller
* @author Christian Grothoff * @author Christian Grothoff
@ -42,7 +42,7 @@
/** /**
* Send reserve status information to client with the * Send reserve history information to client with the
* message that we have insufficient funds for the * message that we have insufficient funds for the
* requested withdraw operation. * requested withdraw operation.
* *
@ -52,10 +52,10 @@
* @return MHD result code * @return MHD result code
*/ */
static int static int
reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, reply_withdraw_insufficient_funds (
const struct TALER_Amount *ebalance, struct MHD_Connection *connection,
const struct const struct TALER_Amount *ebalance,
TALER_EXCHANGEDB_ReserveHistory *rh) const struct TALER_EXCHANGEDB_ReserveHistory *rh)
{ {
json_t *json_history; json_t *json_history;
struct TALER_Amount balance; struct TALER_Amount balance;
@ -66,7 +66,7 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection,
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
"balance calculation failure"); "reserve balance calculation failure");
if (0 != if (0 !=
TALER_amount_cmp (&balance, TALER_amount_cmp (&balance,
ebalance)) ebalance))
@ -75,7 +75,7 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection,
json_decref (json_history); json_decref (json_history);
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_WITHDRAW_HISTORY_RESERVE_BALANCE_CORRUPT, TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT,
"internal balance inconsistency error"); "internal balance inconsistency error");
} }
return TALER_MHD_reply_json_pack (connection, return TALER_MHD_reply_json_pack (connection,
@ -92,21 +92,25 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection,
/** /**
* Send blinded coin information to client. * Send blind signature to client.
* *
* @param connection connection to the client * @param connection connection to the client
* @param collectable blinded coin to return * @param collectable blinded coin to return
* @return MHD result code * @return MHD result code
*/ */
static int static int
reply_reserve_withdraw_success (struct MHD_Connection *connection, reply_withdraw_success (
const struct struct MHD_Connection *connection,
TALER_EXCHANGEDB_CollectableBlindcoin * const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
collectable)
{ {
json_t *sig_json; json_t *sig_json;
sig_json = GNUNET_JSON_from_rsa_signature (collectable->sig.rsa_signature); sig_json = GNUNET_JSON_from_rsa_signature (collectable->sig.rsa_signature);
if (NULL == sig_json)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_JSON_ALLOCATION_FAILURE,
"GNUNET_JSON_from_rsa_signature() failed");
return TALER_MHD_reply_json_pack (connection, return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK, MHD_HTTP_OK,
"{s:o}", "{s:o}",
@ -168,7 +172,7 @@ struct WithdrawContext
/** /**
* Function implementing /reserve/withdraw transaction. Runs the * Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction * transaction logic; IF it returns a non-error code, the transaction
* logic MUST NOT queue a MHD response. IF it returns an hard error, * 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. * the transaction logic MUST queue a MHD response and set @a mhd_ret.
@ -180,9 +184,9 @@ struct WithdrawContext
* before entering the transaction, or because this function is run * before entering the transaction, or because this function is run
* twice (!) by #TEH_DB_run_transaction() and the first time created * twice (!) by #TEH_DB_run_transaction() and the first time created
* the signature and then failed to commit. Furthermore, we may get * the signature and then failed to commit. Furthermore, we may get
* a 2nd correct signature briefly if "get_withdraw_info" suceeds and * a 2nd correct signature briefly if "get_withdraw_info" succeeds and
* finds one in the DB. To avoid signing twice, the function may * finds one in the DB. To avoid signing twice, the function may
* return a valid signature in "wc->collectable.sig" even if it failed. * return a valid signature in "wc->collectable.sig" **even if it failed**.
* The caller must thus free the signature in either case. * The caller must thus free the signature in either case.
* *
* @param cls a `struct WithdrawContext *` * @param cls a `struct WithdrawContext *`
@ -201,7 +205,6 @@ withdraw_transaction (void *cls,
struct WithdrawContext *wc = cls; struct WithdrawContext *wc = cls;
struct TALER_EXCHANGEDB_Reserve r; struct TALER_EXCHANGEDB_Reserve r;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount fee_withdraw;
struct TALER_DenominationSignature denom_sig; struct TALER_DenominationSignature denom_sig;
#if OPTIMISTIC_SIGN #if OPTIMISTIC_SIGN
@ -227,18 +230,23 @@ withdraw_transaction (void *cls,
} }
/* Don't sign again if we have already signed the coin */ /* Don't sign again if we have already signed the coin */
if (1 == qs) if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{ {
/* Toss out the optimistic signature, we got another one from the DB;
optimization trade-off loses in this case: we unnecessarily computed
a signature :-( */
#if OPTIMISTIC_SIGN #if OPTIMISTIC_SIGN
GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature); GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature);
#endif #endif
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
} }
GNUNET_assert (0 == qs); /* We should never get more than one result, and we handled
wc->collectable.sig = denom_sig; the errors (negative case) above, so that leaves no results. */
GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
wc->collectable.sig = denom_sig; /* Note: might still be NULL if we didn't do OPTIMISTIC_SIGN */
/* Check if balance is sufficient */ /* Check if balance is sufficient */
r.pub = wc->wsrd.reserve_pub; r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Trying to withdraw from reserve: %s\n", "Trying to withdraw from reserve: %s\n",
TALER_B2S (&r.pub)); TALER_B2S (&r.pub));
@ -265,20 +273,25 @@ withdraw_transaction (void *cls,
if (0 < TALER_amount_cmp (&wc->amount_required, if (0 < TALER_amount_cmp (&wc->amount_required,
&r.balance)) &r.balance))
{ {
char *amount_required;
char *r_balance;
struct TALER_EXCHANGEDB_ReserveHistory *rh; struct TALER_EXCHANGEDB_ReserveHistory *rh;
/* The reserve does not have the required amount (actual /* The reserve does not have the required amount (actual
* amount + withdraw fee) */ * amount + withdraw fee) */
GNUNET_break_op (0); GNUNET_break_op (0);
amount_required = TALER_amount_to_string (&wc->amount_required); #if GNUNET_EXTRA_LOGGING
r_balance = TALER_amount_to_string (&r.balance); {
TALER_LOG_WARNING ("Asked %s over a reserve worth %s\n", char *amount_required;
amount_required, char *r_balance;
r_balance);
GNUNET_free (amount_required); amount_required = TALER_amount_to_string (&wc->amount_required);
GNUNET_free (r_balance); r_balance = TALER_amount_to_string (&r.balance);
TALER_LOG_WARNING ("Asked %s over a reserve worth %s\n",
amount_required,
r_balance);
GNUNET_free (amount_required);
GNUNET_free (r_balance);
}
#endif
qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
session, session,
&wc->wsrd.reserve_pub, &wc->wsrd.reserve_pub,
@ -292,9 +305,9 @@ withdraw_transaction (void *cls,
"failed to fetch reserve history"); "failed to fetch reserve history");
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
*mhd_ret = reply_reserve_withdraw_insufficient_funds (connection, *mhd_ret = reply_withdraw_insufficient_funds (connection,
&r.balance, &r.balance,
rh); rh);
TEH_plugin->free_reserve_history (TEH_plugin->cls, TEH_plugin->free_reserve_history (TEH_plugin->cls,
rh); rh);
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
@ -314,16 +327,15 @@ withdraw_transaction (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection, *mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_WITHDRAW_SIGNATURE_FAILED, TALER_EC_WITHDRAW_SIGNATURE_FAILED,
"Failed to create signature"); "Failed to create blind signature");
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
} }
#endif #endif
TALER_amount_ntoh (&fee_withdraw,
&wc->dki->issue.properties.fee_withdraw);
wc->collectable.denom_pub_hash = wc->denom_pub_hash; wc->collectable.denom_pub_hash = wc->denom_pub_hash;
wc->collectable.amount_with_fee = wc->amount_required; wc->collectable.amount_with_fee = wc->amount_required;
wc->collectable.withdraw_fee = fee_withdraw; TALER_amount_ntoh (&wc->collectable.withdraw_fee,
&wc->dki->issue.properties.fee_withdraw);
wc->collectable.reserve_pub = wc->wsrd.reserve_pub; wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
wc->collectable.reserve_sig = wc->signature; wc->collectable.reserve_sig = wc->signature;
@ -366,12 +378,6 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
const char *const args[2]) const char *const args[2])
{ {
struct WithdrawContext wc; struct WithdrawContext wc;
int res;
int mhd_ret;
unsigned int hc;
enum TALER_ErrorCode ec;
struct TALER_Amount amount;
struct TALER_Amount fee_withdraw;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_varsize ("coin_ev", GNUNET_JSON_spec_varsize ("coin_ev",
(void **) &wc.blinded_msg, (void **) &wc.blinded_msg,
@ -397,11 +403,15 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
"reserve public key malformed"); "reserve public key malformed");
} }
res = TALER_MHD_parse_json_data (connection, {
root, int res;
spec);
if (GNUNET_OK != res) res = TALER_MHD_parse_json_data (connection,
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; root,
spec);
if (GNUNET_OK != res)
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
if (NULL == wc.key_state) if (NULL == wc.key_state)
{ {
@ -412,41 +422,52 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
TALER_EC_EXCHANGE_BAD_CONFIGURATION, TALER_EC_EXCHANGE_BAD_CONFIGURATION,
"no keys"); "no keys");
} }
wc.dki = TEH_KS_denomination_key_lookup_by_hash (wc.key_state,
&wc.denom_pub_hash,
TEH_KS_DKU_WITHDRAW,
&ec,
&hc);
if (NULL == wc.dki)
{ {
GNUNET_JSON_parse_free (spec); unsigned int hc;
TEH_KS_release (wc.key_state); enum TALER_ErrorCode ec;
return TALER_MHD_reply_with_error (connection,
hc, wc.dki = TEH_KS_denomination_key_lookup_by_hash (wc.key_state,
ec, &wc.denom_pub_hash,
"could not find denomination key"); TEH_KS_DKU_WITHDRAW,
&ec,
&hc);
if (NULL == wc.dki)
{
GNUNET_JSON_parse_free (spec);
TEH_KS_release (wc.key_state);
return TALER_MHD_reply_with_error (connection,
hc,
ec,
"could not find denomination key");
}
} }
GNUNET_assert (NULL != wc.dki->denom_priv.rsa_private_key); GNUNET_assert (NULL != wc.dki->denom_priv.rsa_private_key);
TALER_amount_ntoh (&amount,
&wc.dki->issue.properties.value);
TALER_amount_ntoh (&fee_withdraw,
&wc.dki->issue.properties.fee_withdraw);
if (GNUNET_OK !=
TALER_amount_add (&wc.amount_required,
&amount,
&fee_withdraw))
{ {
GNUNET_JSON_parse_free (spec); struct TALER_Amount amount;
TEH_KS_release (wc.key_state); struct TALER_Amount fee_withdraw;
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_amount_ntoh (&amount,
TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW, &wc.dki->issue.properties.value);
"amount overflow for value plus withdraw fee"); TALER_amount_ntoh (&fee_withdraw,
&wc.dki->issue.properties.fee_withdraw);
if (GNUNET_OK !=
TALER_amount_add (&wc.amount_required,
&amount,
&fee_withdraw))
{
GNUNET_JSON_parse_free (spec);
TEH_KS_release (wc.key_state);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW,
"amount overflow for value plus withdraw fee");
}
TALER_amount_hton (&wc.wsrd.amount_with_fee,
&wc.amount_required);
TALER_amount_hton (&wc.wsrd.withdraw_fee,
&fee_withdraw);
} }
TALER_amount_hton (&wc.wsrd.amount_with_fee,
&wc.amount_required);
TALER_amount_hton (&wc.wsrd.withdraw_fee,
&fee_withdraw);
/* verify signature! */ /* verify signature! */
wc.wsrd.purpose.size wc.wsrd.purpose.size
= htonl (sizeof (struct TALER_WithdrawRequestPS)); = htonl (sizeof (struct TALER_WithdrawRequestPS));
@ -491,28 +512,39 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh,
} }
#endif #endif
if (GNUNET_OK != /* run transaction and sign (if not optimistically signed before) */
TEH_DB_run_transaction (connection,
"run withdraw",
&mhd_ret,
&withdraw_transaction,
&wc))
{ {
TEH_KS_release (wc.key_state); int mhd_ret;
/* Even if #withdraw_transaction() failed, it may have created a signature
(or we might have done it optimistically above). */ if (GNUNET_OK !=
if (NULL != wc.collectable.sig.rsa_signature) TEH_DB_run_transaction (connection,
GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); "run withdraw",
GNUNET_JSON_parse_free (spec); &mhd_ret,
return mhd_ret; &withdraw_transaction,
&wc))
{
TEH_KS_release (wc.key_state);
/* Even if #withdraw_transaction() failed, it may have created a signature
(or we might have done it optimistically above). */
if (NULL != wc.collectable.sig.rsa_signature)
GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
GNUNET_JSON_parse_free (spec);
return mhd_ret;
}
} }
/* Clean up and send back final (positive) response */
TEH_KS_release (wc.key_state); TEH_KS_release (wc.key_state);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
mhd_ret = reply_reserve_withdraw_success (connection, {
&wc.collectable); int ret;
GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
return mhd_ret; ret = reply_withdraw_success (connection,
&wc.collectable);
GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
return ret;
}
} }

View File

@ -382,11 +382,11 @@ enum TALER_ErrorCode
TALER_EC_DENOMINATION_KEY_LOST = 1116, TALER_EC_DENOMINATION_KEY_LOST = 1116,
/** /**
* The exchange's database entry with the reserve balance summary * The exchange's database entry with the reserve balance summary is
* is inconsistent with its own history of the reserve. * inconsistent with its own history of the reserve. Returned with an
* Returned with an HTTP status of #MHD_HTTP_INTERNAL_SERVER_ERROR. * HTTP status of #MHD_HTTP_INTERNAL_SERVER_ERROR.
*/ */
TALER_EC_WITHDRAW_HISTORY_RESERVE_BALANCE_CORRUPT = 1117, TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT = 1117,
/** /**
* The exchange failed to obtain the transaction history of the given * The exchange failed to obtain the transaction history of the given
@ -528,6 +528,13 @@ enum TALER_ErrorCode
*/ */
TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE = 1221, TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE = 1221,
/**
* The currency specified for the deposit is different from the
* currency of the coin. This response is provided with HTTP status
* code MHD_HTTP_PRECONDITION_FAILED.
*/
TALER_EC_DEPOSIT_CURRENCY_MISSMATCH = 1222,
/** /**
* The respective coin did not have sufficient residual value for the * The respective coin did not have sufficient residual value for the
* /refresh/melt operation. The "history" in this response provdes * /refresh/melt operation. The "history" in this response provdes
@ -544,7 +551,7 @@ enum TALER_ErrorCode
* "original_value". This response is provided with HTTP status code * "original_value". This response is provided with HTTP status code
* MHD_HTTP_CONFLICT. * MHD_HTTP_CONFLICT.
*/ */
TALER_EC_TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND = 1301, TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND = 1301,
/** /**
* The exchange had an internal error reconstructing the transaction * The exchange had an internal error reconstructing the transaction
@ -607,6 +614,13 @@ enum TALER_ErrorCode
*/ */
TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE = 1310, TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE = 1310,
/**
* The currency specified for the melt amount is different from the
* currency of the coin. This response is provided with HTTP status
* code MHD_HTTP_PRECONDITION_FAILED.
*/
TALER_EC_MELT_CURRENCY_MISSMATCH = 1311,
/** /**
* The exchange is unaware of the denomination key that was used to * The exchange is unaware of the denomination key that was used to
* sign the melted zombie coin. This response is provided with HTTP * sign the melted zombie coin. This response is provided with HTTP