optimize /deposit logic to minimize serialization failures (presumably)
This commit is contained in:
parent
21951eacc2
commit
e0700ad916
@ -1 +1 @@
|
||||
Subproject commit b7320181c5e0d95c6f2e2a9e5c53dce0bc1a35a8
|
||||
Subproject commit b123140349c3e3b300878d2e35cea1553c9a381d
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2017 Taler Systems SA
|
||||
Copyright (C) 2014-2017, 2021 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
@ -28,6 +28,55 @@
|
||||
#include "taler-exchange-httpd_responses.h"
|
||||
|
||||
|
||||
/**
|
||||
* Send a response for a failed request. The transaction history of the given
|
||||
* coin demonstrates that the @a residual value of the coin is below the @a
|
||||
* requested contribution of the coin for the operation. Thus, the exchange
|
||||
* refuses the operation.
|
||||
*
|
||||
* @param connection the connection to send the response to
|
||||
* @param coin_pub public key of the coin
|
||||
* @param coin_value original value of the coin
|
||||
* @param tl transaction history for the coin
|
||||
* @param requested how much this coin was supposed to contribute, including fee
|
||||
* @param residual remaining value of the coin (after subtracting @a tl)
|
||||
* @return a MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
reply_insufficient_funds (
|
||||
struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl,
|
||||
const struct TALER_Amount *requested,
|
||||
const struct TALER_Amount *residual)
|
||||
{
|
||||
json_t *history;
|
||||
|
||||
history = TEH_RESPONSE_compile_transaction_history (coin_pub,
|
||||
tl);
|
||||
if (NULL == history)
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_GENERIC_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
|
||||
NULL);
|
||||
return TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS),
|
||||
GNUNET_JSON_pack_data_auto ("coin_pub",
|
||||
coin_pub),
|
||||
TALER_JSON_pack_amount ("original_value",
|
||||
coin_value),
|
||||
TALER_JSON_pack_amount ("residual_value",
|
||||
residual),
|
||||
TALER_JSON_pack_amount ("requested_value",
|
||||
requested),
|
||||
GNUNET_JSON_pack_array_steal ("history",
|
||||
history));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* How often should we retry a transaction before giving up
|
||||
* (for transactions resulting in serialization/dead locks only).
|
||||
@ -114,6 +163,122 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_check_coin_balance (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
const struct TALER_Amount *op_cost,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
struct TALER_Amount spent;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* Start with zero cost, as we already added this melt transaction
|
||||
to the DB, so we will see it again during the queries below. */
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TEH_currency,
|
||||
&spent));
|
||||
|
||||
/* get historic transaction costs of this coin, including recoups as
|
||||
we might be a zombie coin */
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
coin_pub,
|
||||
check_recoup,
|
||||
&tl);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"coin transaction history");
|
||||
return qs;
|
||||
}
|
||||
if (zombie_required)
|
||||
{
|
||||
/* The denomination key is only usable for a melt if this is a true
|
||||
zombie coin, i.e. it was refreshed and the resulting fresh coin was
|
||||
then recouped. Check that this is truly the case. */
|
||||
for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
|
||||
NULL != tp;
|
||||
tp = tp->next)
|
||||
{
|
||||
if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type)
|
||||
{
|
||||
zombie_required = false; /* clear flag: was satisfied! */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (zombie_required)
|
||||
{
|
||||
/* zombie status not satisfied */
|
||||
GNUNET_break_op (0);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_GENERIC_COIN_HISTORY_COMPUTATION_FAILED,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* Refuse to refresh when the coin's value is insufficient
|
||||
for the cost of all transactions. */
|
||||
if (0 > TALER_amount_cmp (coin_value,
|
||||
&spent))
|
||||
{
|
||||
struct TALER_Amount coin_residual;
|
||||
struct TALER_Amount spent_already;
|
||||
|
||||
/* First subtract the melt cost from 'spent' to
|
||||
compute the total amount already spent of the coin */
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&spent_already,
|
||||
&spent,
|
||||
op_cost));
|
||||
/* The residual coin value is the original coin value minus
|
||||
what we have spent (before the melt) */
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&coin_residual,
|
||||
coin_value,
|
||||
&spent_already));
|
||||
*mhd_ret = reply_insufficient_funds (
|
||||
connection,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
tl,
|
||||
op_cost,
|
||||
&coin_residual);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* we're good, coin has sufficient funds to be melted */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||
const char *name,
|
||||
|
@ -41,6 +41,32 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
MHD_RESULT *mhd_ret);
|
||||
|
||||
|
||||
/**
|
||||
* Check that a coin has an adequate balance so that we can
|
||||
* commit the current transaction. If the balance is
|
||||
* insufficient for all transactions associated with the
|
||||
* coin, return a hard error.
|
||||
*
|
||||
* @param connection HTTP connection to report hard errors on
|
||||
* @param coin_pub coin to analyze
|
||||
* @param coin_value total value of the original coin (by denomination)
|
||||
* @param op_cost cost of the current operation (for error reporting)
|
||||
* @param check_recoup should we include recoup transactions in the check
|
||||
* @param zombie_required additional requirement that the coin must
|
||||
* be a zombie coin, or also hard failure
|
||||
* @param[out] mhd_ret set to response status code, on hard error only
|
||||
* @return transaction status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_check_coin_balance (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
const struct TALER_Amount *op_cost,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
MHD_RESULT *mhd_ret);
|
||||
|
||||
|
||||
/**
|
||||
* Function implementing a database transaction. Runs the transaction
|
||||
* logic; IF it returns a non-error code, the transaction logic MUST
|
||||
|
@ -162,113 +162,85 @@ deposit_transaction (void *cls,
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
struct TALER_Amount deposit_fee;
|
||||
|
||||
/* Check for idempotency: did we get this request before? */
|
||||
qs = TEH_plugin->have_deposit (TEH_plugin->cls,
|
||||
deposit,
|
||||
&deposit_fee,
|
||||
&dc->exchange_timestamp);
|
||||
/* begin optimistically: assume this is a new deposit */
|
||||
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||
dc->exchange_timestamp,
|
||||
deposit);
|
||||
if (qs < 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
NULL);
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
/* Check for idempotency: did we get this request before? */
|
||||
qs = TEH_plugin->have_deposit (TEH_plugin->cls,
|
||||
deposit,
|
||||
&deposit_fee,
|
||||
&dc->exchange_timestamp);
|
||||
if (qs < 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"have_deposit");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
|
||||
{
|
||||
struct TALER_Amount amount_without_fee;
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"/deposit replay, accepting again!\n");
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit_fee));
|
||||
*mhd_ret = reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&dc->h_wire,
|
||||
NULL /* h_extensions! */,
|
||||
&deposit->h_contract_terms,
|
||||
dc->exchange_timestamp,
|
||||
deposit->refund_deadline,
|
||||
deposit->wire_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* Start with fee for THIS transaction */
|
||||
spent = deposit->amount_with_fee;
|
||||
/* add cost of all previous transactions; skip RECOUP as revoked
|
||||
denominations are not eligible for deposit, and if we are the old coin
|
||||
pub of a revoked coin (aka a zombie), then ONLY refresh is allowed. */
|
||||
{
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
&deposit->coin.coin_pub,
|
||||
GNUNET_NO,
|
||||
&tl);
|
||||
if (0 > qs)
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
NULL);
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
|
||||
&spent, /* starting offset */
|
||||
&spent /* result */))
|
||||
{
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
|
||||
NULL);
|
||||
/* Conflict on insert, but record does not exist?
|
||||
That makes no sense. */
|
||||
GNUNET_break (0);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
/* Check that cost of all transactions (including the current one) is
|
||||
smaller (or equal) than the value of the coin. */
|
||||
if (0 < TALER_amount_cmp (&spent,
|
||||
&dc->value))
|
||||
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||
"Deposited coin has insufficient funds left!\n");
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
|
||||
TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS,
|
||||
&deposit->coin.
|
||||
coin_pub,
|
||||
tl);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
struct TALER_Amount amount_without_fee;
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"/deposit replay, accepting again!\n");
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&amount_without_fee,
|
||||
&deposit->amount_with_fee,
|
||||
&deposit_fee));
|
||||
*mhd_ret = reply_deposit_success (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&dc->h_wire,
|
||||
NULL /* h_extensions! */,
|
||||
&deposit->h_contract_terms,
|
||||
dc->exchange_timestamp,
|
||||
deposit->refund_deadline,
|
||||
deposit->wire_deadline,
|
||||
&deposit->merchant_pub,
|
||||
&amount_without_fee);
|
||||
/* Note: we return "hard error" to ensure the wrapper
|
||||
does not retry the transaction, and to also not generate
|
||||
a "fresh" response (as we would on "success") */
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
}
|
||||
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||
dc->exchange_timestamp,
|
||||
deposit);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
NULL);
|
||||
}
|
||||
return qs;
|
||||
|
||||
/* Start with zero cost, as we already added this melt transaction
|
||||
to the DB, so we will see it again during the queries below. */
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TEH_currency,
|
||||
&spent));
|
||||
|
||||
return TEH_check_coin_balance (connection,
|
||||
&deposit->coin.coin_pub,
|
||||
&dc->value,
|
||||
&deposit->amount_with_fee,
|
||||
false, /* no need for recoup */
|
||||
false, /* no need for zombie */
|
||||
mhd_ret);
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,56 +33,6 @@
|
||||
#include "taler_exchangedb_lib.h"
|
||||
|
||||
|
||||
/**
|
||||
* Send a response for a failed "melt" request. The
|
||||
* transaction history of the given coin demonstrates that the
|
||||
* @a residual value of the coin is below the @a requested
|
||||
* contribution of the coin for the melt. Thus, the exchange
|
||||
* refuses the melt operation.
|
||||
*
|
||||
* @param connection the connection to send the response to
|
||||
* @param coin_pub public key of the coin
|
||||
* @param coin_value original value of the coin
|
||||
* @param tl transaction history for the coin
|
||||
* @param requested how much this coin was supposed to contribute, including fee
|
||||
* @param residual remaining value of the coin (after subtracting @a tl)
|
||||
* @return a MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
reply_melt_insufficient_funds (
|
||||
struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl,
|
||||
const struct TALER_Amount *requested,
|
||||
const struct TALER_Amount *residual)
|
||||
{
|
||||
json_t *history;
|
||||
|
||||
history = TEH_RESPONSE_compile_transaction_history (coin_pub,
|
||||
tl);
|
||||
if (NULL == history)
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
|
||||
NULL);
|
||||
return TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_JSON_pack_ec (TALER_EC_EXCHANGE_MELT_INSUFFICIENT_FUNDS),
|
||||
GNUNET_JSON_pack_data_auto ("coin_pub",
|
||||
coin_pub),
|
||||
TALER_JSON_pack_amount ("original_value",
|
||||
coin_value),
|
||||
TALER_JSON_pack_amount ("residual_value",
|
||||
residual),
|
||||
TALER_JSON_pack_amount ("requested_value",
|
||||
requested),
|
||||
GNUNET_JSON_pack_array_steal ("history",
|
||||
history));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a response to a "melt" request.
|
||||
*
|
||||
@ -165,127 +115,6 @@ struct MeltContext
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that the coin has sufficient funds left for the selected
|
||||
* melt operation.
|
||||
*
|
||||
* @param connection the connection to send errors to
|
||||
* @param[in,out] rmc melt context
|
||||
* @param[out] mhd_ret status code to return to MHD on hard error
|
||||
* @return transaction status code
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
refresh_check_melt (struct MHD_Connection *connection,
|
||||
struct MeltContext *rmc,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
struct TALER_Amount spent;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* Start with zero cost, as we already added this melt transaction
|
||||
to the DB, so we will see it again during the queries below. */
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TEH_currency,
|
||||
&spent));
|
||||
|
||||
/* get historic transaction costs of this coin, including recoups as
|
||||
we might be a zombie coin */
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
GNUNET_YES,
|
||||
&tl);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"coin transaction history");
|
||||
return qs;
|
||||
}
|
||||
if (rmc->zombie_required)
|
||||
{
|
||||
/* The denomination key is only usable for a melt if this is a true
|
||||
zombie coin, i.e. it was refreshed and the resulting fresh coin was
|
||||
then recouped. Check that this is truly the case. */
|
||||
for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
|
||||
NULL != tp;
|
||||
tp = tp->next)
|
||||
{
|
||||
if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type)
|
||||
{
|
||||
rmc->zombie_required = false; /* clear flag: was satisfied! */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rmc->zombie_required)
|
||||
{
|
||||
/* zombie status not satisfied */
|
||||
GNUNET_break_op (0);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
|
||||
&spent,
|
||||
&spent))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_HISTORY_COMPUTATION_FAILED,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* Refuse to refresh when the coin's value is insufficient
|
||||
for the cost of all transactions. */
|
||||
if (0 > TALER_amount_cmp (&rmc->coin_value,
|
||||
&spent))
|
||||
{
|
||||
struct TALER_Amount coin_residual;
|
||||
struct TALER_Amount spent_already;
|
||||
|
||||
/* First subtract the melt cost from 'spent' to
|
||||
compute the total amount already spent of the coin */
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&spent_already,
|
||||
&spent,
|
||||
&rmc->refresh_session.amount_with_fee));
|
||||
/* The residual coin value is the original coin value minus
|
||||
what we have spent (before the melt) */
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_subtract (&coin_residual,
|
||||
&rmc->coin_value,
|
||||
&spent_already));
|
||||
*mhd_ret = reply_melt_insufficient_funds (
|
||||
connection,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
&rmc->coin_value,
|
||||
tl,
|
||||
&rmc->refresh_session.amount_with_fee,
|
||||
&coin_residual);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* we're good, coin has sufficient funds to be melted */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a "melt". We have been given a list of valid
|
||||
* coins and a request to melt them into the given @a
|
||||
@ -366,14 +195,13 @@ melt_transaction (void *cls,
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* check coin has enough funds remaining on it to cover melt cost */
|
||||
qs = refresh_check_melt (connection,
|
||||
rmc,
|
||||
mhd_ret);
|
||||
if (0 > qs)
|
||||
return qs; /* if we failed, tell caller */
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
return TEH_check_coin_balance (connection,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
&rmc->coin_value,
|
||||
&rmc->refresh_session.amount_with_fee,
|
||||
true,
|
||||
rmc->zombie_required,
|
||||
mhd_ret);
|
||||
}
|
||||
|
||||
|
||||
|
@ -105,6 +105,11 @@ struct RevealContext
|
||||
*/
|
||||
struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
|
||||
|
||||
/**
|
||||
* Melt data for our session we got from the database for @e rc.
|
||||
*/
|
||||
struct TALER_EXCHANGEDB_Melt melt;
|
||||
|
||||
/**
|
||||
* Denominations being requested.
|
||||
*/
|
||||
@ -266,35 +271,6 @@ refreshes_reveal_transaction (void *cls,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
struct TALER_EXCHANGEDB_Melt melt;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* Obtain basic information about the refresh operation and what
|
||||
gamma we committed to. */
|
||||
// FIXME: why do we do 'get_melt' twice?
|
||||
qs = TEH_plugin->get_melt (TEH_plugin->cls,
|
||||
&rctx->rc,
|
||||
&melt);
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
|
||||
(melt.session.noreveal_index >= TALER_CNC_KAPPA) )
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"melt");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* Verify commitment */
|
||||
{
|
||||
@ -310,7 +286,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
{
|
||||
struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
|
||||
|
||||
if (i == melt.session.noreveal_index)
|
||||
if (i == rctx->melt.session.noreveal_index)
|
||||
{
|
||||
/* Take these coin envelopes from the client */
|
||||
rce->transfer_pub = rctx->gamma_tp;
|
||||
@ -327,7 +303,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
|
||||
&rce->transfer_pub.ecdhe_pub);
|
||||
TALER_link_reveal_transfer_secret (tpriv,
|
||||
&melt.session.coin.coin_pub,
|
||||
&rctx->melt.session.coin.coin_pub,
|
||||
&ts);
|
||||
rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
|
||||
struct TALER_RefreshCoinData);
|
||||
@ -356,15 +332,15 @@ refreshes_reveal_transaction (void *cls,
|
||||
TALER_CNC_KAPPA,
|
||||
rctx->num_fresh_coins,
|
||||
rcs,
|
||||
&melt.session.coin.coin_pub,
|
||||
&melt.session.amount_with_fee);
|
||||
&rctx->melt.session.coin.coin_pub,
|
||||
&rctx->melt.session.amount_with_fee);
|
||||
|
||||
/* Free resources allocated above */
|
||||
for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
|
||||
{
|
||||
struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
|
||||
|
||||
if (i == melt.session.noreveal_index)
|
||||
if (i == rctx->melt.session.noreveal_index)
|
||||
continue; /* This offset is special: not allocated! */
|
||||
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
|
||||
{
|
||||
@ -395,7 +371,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
{
|
||||
struct TALER_Amount refresh_cost;
|
||||
|
||||
refresh_cost = melt.melt_fee;
|
||||
refresh_cost = rctx->melt.melt_fee;
|
||||
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
|
||||
{
|
||||
struct TALER_Amount total;
|
||||
@ -418,7 +394,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
}
|
||||
}
|
||||
if (0 < TALER_amount_cmp (&refresh_cost,
|
||||
&melt.session.amount_with_fee))
|
||||
&rctx->melt.session.amount_with_fee))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
@ -505,7 +481,6 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
struct TALER_DenominationHash dk_h[num_fresh_coins];
|
||||
struct TALER_RefreshCoinData rcds[num_fresh_coins];
|
||||
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
|
||||
struct TALER_EXCHANGEDB_Melt melt;
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
MHD_RESULT ret;
|
||||
struct TEH_KeyStateHandle *ksh;
|
||||
@ -612,11 +587,10 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
// FIXME: why do we do 'get_melt' twice?
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
|
||||
&rctx->rc,
|
||||
&melt)))
|
||||
&rctx->melt)))
|
||||
{
|
||||
switch (qs)
|
||||
{
|
||||
@ -643,6 +617,17 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
/* Obtain basic information about the refresh operation and what
|
||||
gamma we committed to. */
|
||||
if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"melt");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
/* Parse link signatures array */
|
||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
||||
@ -666,7 +651,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
&rctx->gamma_tp,
|
||||
rcds[i].coin_ev,
|
||||
rcds[i].coin_ev_size,
|
||||
&melt.session.coin.coin_pub,
|
||||
&rctx->melt.session.coin.coin_pub,
|
||||
&link_sigs[i]))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
|
@ -1034,7 +1034,8 @@ prepare_statements (struct PostgresClosure *pg)
|
||||
") SELECT known_coin_id, $2, $3, $4, $5, $6, "
|
||||
" $7, $8, $9, $10, $11, $12, $13"
|
||||
" FROM known_coins"
|
||||
" WHERE coin_pub=$1;",
|
||||
" WHERE coin_pub=$1"
|
||||
" ON CONFLICT DO NOTHING;",
|
||||
13),
|
||||
/* Fetch an existing deposit request, used to ensure idempotency
|
||||
during /deposit processing. Used in #postgres_have_deposit(). */
|
||||
|
@ -263,7 +263,7 @@ verify_deposit_signature_conflict (
|
||||
ec = TALER_JSON_get_error_code (json);
|
||||
switch (ec)
|
||||
{
|
||||
case TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS:
|
||||
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||
if (0 >
|
||||
TALER_amount_add (&total,
|
||||
&total,
|
||||
|
@ -97,7 +97,7 @@ struct TALER_EXCHANGE_MeltHandle
|
||||
* @param[out] noreveal_index set to the noreveal index selected by the exchange
|
||||
* @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
|
||||
*/
|
||||
static int
|
||||
static enum GNUNET_GenericReturnValue
|
||||
verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
const json_t *json,
|
||||
struct TALER_ExchangePublicKeyP *exchange_pub,
|
||||
@ -208,7 +208,7 @@ verify_melt_signature_denom_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
* @param json json reply with the signature(s) and transaction history
|
||||
* @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
|
||||
*/
|
||||
static int
|
||||
static enum GNUNET_GenericReturnValue
|
||||
verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
const json_t *json)
|
||||
{
|
||||
@ -282,7 +282,7 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
ec = TALER_JSON_get_error_code (json);
|
||||
switch (ec)
|
||||
{
|
||||
case TALER_EC_EXCHANGE_MELT_INSUFFICIENT_FUNDS:
|
||||
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||
/* check if melt operation was really too expensive given history */
|
||||
if (0 >
|
||||
TALER_amount_add (&total,
|
||||
@ -379,7 +379,7 @@ handle_melt_finished (void *cls,
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
switch (hr.ec)
|
||||
{
|
||||
case TALER_EC_EXCHANGE_MELT_INSUFFICIENT_FUNDS:
|
||||
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||
/* Double spending; check signatures on transaction history */
|
||||
if (GNUNET_OK !=
|
||||
verify_melt_signature_spend_conflict (mh,
|
||||
|
Loading…
Reference in New Issue
Block a user