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
|
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
|
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
|
terms of the GNU General Public License as published by the Free Software
|
||||||
@ -28,6 +28,55 @@
|
|||||||
#include "taler-exchange-httpd_responses.h"
|
#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
|
* How often should we retry a transaction before giving up
|
||||||
* (for transactions resulting in serialization/dead locks only).
|
* (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
|
enum GNUNET_GenericReturnValue
|
||||||
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
TEH_DB_run_transaction (struct MHD_Connection *connection,
|
||||||
const char *name,
|
const char *name,
|
||||||
|
@ -41,6 +41,32 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
|||||||
MHD_RESULT *mhd_ret);
|
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
|
* Function implementing a database transaction. Runs the transaction
|
||||||
* logic; IF it returns a non-error code, the transaction logic MUST
|
* 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;
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
struct TALER_Amount deposit_fee;
|
struct TALER_Amount deposit_fee;
|
||||||
|
|
||||||
/* Check for idempotency: did we get this request before? */
|
/* begin optimistically: assume this is a new deposit */
|
||||||
qs = TEH_plugin->have_deposit (TEH_plugin->cls,
|
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||||
deposit,
|
dc->exchange_timestamp,
|
||||||
&deposit_fee,
|
deposit);
|
||||||
&dc->exchange_timestamp);
|
|
||||||
if (qs < 0)
|
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_ret = TALER_MHD_reply_with_error (connection,
|
||||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||||
"have_deposit");
|
"have_deposit");
|
||||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
}
|
}
|
||||||
return qs;
|
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 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_HARD_ERROR == qs)
|
/* Conflict on insert, but record does not exist?
|
||||||
*mhd_ret = TALER_MHD_reply_with_error (
|
That makes no sense. */
|
||||||
connection,
|
GNUNET_break (0);
|
||||||
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);
|
|
||||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
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,
|
struct TALER_Amount amount_without_fee;
|
||||||
"Deposited coin has insufficient funds left!\n");
|
|
||||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||||
TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS,
|
"/deposit replay, accepting again!\n");
|
||||||
&deposit->coin.
|
GNUNET_assert (0 <=
|
||||||
coin_pub,
|
TALER_amount_subtract (&amount_without_fee,
|
||||||
tl);
|
&deposit->amount_with_fee,
|
||||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
&deposit_fee));
|
||||||
tl);
|
*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;
|
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,
|
/* Start with zero cost, as we already added this melt transaction
|
||||||
deposit);
|
to the DB, so we will see it again during the queries below. */
|
||||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
GNUNET_assert (GNUNET_OK ==
|
||||||
{
|
TALER_amount_set_zero (TEH_currency,
|
||||||
TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
|
&spent));
|
||||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
|
||||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
return TEH_check_coin_balance (connection,
|
||||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
&deposit->coin.coin_pub,
|
||||||
NULL);
|
&dc->value,
|
||||||
}
|
&deposit->amount_with_fee,
|
||||||
return qs;
|
false, /* no need for recoup */
|
||||||
|
false, /* no need for zombie */
|
||||||
|
mhd_ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,56 +33,6 @@
|
|||||||
#include "taler_exchangedb_lib.h"
|
#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.
|
* 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
|
* Execute a "melt". We have been given a list of valid
|
||||||
* coins and a request to melt them into the given @a
|
* 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;
|
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return TEH_check_coin_balance (connection,
|
||||||
/* check coin has enough funds remaining on it to cover melt cost */
|
&rmc->refresh_session.coin.coin_pub,
|
||||||
qs = refresh_check_melt (connection,
|
&rmc->coin_value,
|
||||||
rmc,
|
&rmc->refresh_session.amount_with_fee,
|
||||||
mhd_ret);
|
true,
|
||||||
if (0 > qs)
|
rmc->zombie_required,
|
||||||
return qs; /* if we failed, tell caller */
|
mhd_ret);
|
||||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,6 +105,11 @@ struct RevealContext
|
|||||||
*/
|
*/
|
||||||
struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
|
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.
|
* Denominations being requested.
|
||||||
*/
|
*/
|
||||||
@ -266,35 +271,6 @@ refreshes_reveal_transaction (void *cls,
|
|||||||
MHD_RESULT *mhd_ret)
|
MHD_RESULT *mhd_ret)
|
||||||
{
|
{
|
||||||
struct RevealContext *rctx = cls;
|
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 */
|
/* Verify commitment */
|
||||||
{
|
{
|
||||||
@ -310,7 +286,7 @@ refreshes_reveal_transaction (void *cls,
|
|||||||
{
|
{
|
||||||
struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
|
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 */
|
/* Take these coin envelopes from the client */
|
||||||
rce->transfer_pub = rctx->gamma_tp;
|
rce->transfer_pub = rctx->gamma_tp;
|
||||||
@ -327,7 +303,7 @@ refreshes_reveal_transaction (void *cls,
|
|||||||
GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
|
GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
|
||||||
&rce->transfer_pub.ecdhe_pub);
|
&rce->transfer_pub.ecdhe_pub);
|
||||||
TALER_link_reveal_transfer_secret (tpriv,
|
TALER_link_reveal_transfer_secret (tpriv,
|
||||||
&melt.session.coin.coin_pub,
|
&rctx->melt.session.coin.coin_pub,
|
||||||
&ts);
|
&ts);
|
||||||
rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
|
rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
|
||||||
struct TALER_RefreshCoinData);
|
struct TALER_RefreshCoinData);
|
||||||
@ -356,15 +332,15 @@ refreshes_reveal_transaction (void *cls,
|
|||||||
TALER_CNC_KAPPA,
|
TALER_CNC_KAPPA,
|
||||||
rctx->num_fresh_coins,
|
rctx->num_fresh_coins,
|
||||||
rcs,
|
rcs,
|
||||||
&melt.session.coin.coin_pub,
|
&rctx->melt.session.coin.coin_pub,
|
||||||
&melt.session.amount_with_fee);
|
&rctx->melt.session.amount_with_fee);
|
||||||
|
|
||||||
/* Free resources allocated above */
|
/* Free resources allocated above */
|
||||||
for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
|
for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
|
||||||
{
|
{
|
||||||
struct TALER_RefreshCommitmentEntry *rce = &rcs[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! */
|
continue; /* This offset is special: not allocated! */
|
||||||
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
|
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;
|
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++)
|
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
|
||||||
{
|
{
|
||||||
struct TALER_Amount total;
|
struct TALER_Amount total;
|
||||||
@ -418,7 +394,7 @@ refreshes_reveal_transaction (void *cls,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (0 < TALER_amount_cmp (&refresh_cost,
|
if (0 < TALER_amount_cmp (&refresh_cost,
|
||||||
&melt.session.amount_with_fee))
|
&rctx->melt.session.amount_with_fee))
|
||||||
{
|
{
|
||||||
GNUNET_break_op (0);
|
GNUNET_break_op (0);
|
||||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
*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_DenominationHash dk_h[num_fresh_coins];
|
||||||
struct TALER_RefreshCoinData rcds[num_fresh_coins];
|
struct TALER_RefreshCoinData rcds[num_fresh_coins];
|
||||||
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
|
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
|
||||||
struct TALER_EXCHANGEDB_Melt melt;
|
|
||||||
enum GNUNET_GenericReturnValue res;
|
enum GNUNET_GenericReturnValue res;
|
||||||
MHD_RESULT ret;
|
MHD_RESULT ret;
|
||||||
struct TEH_KeyStateHandle *ksh;
|
struct TEH_KeyStateHandle *ksh;
|
||||||
@ -612,11 +587,10 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
|||||||
{
|
{
|
||||||
enum GNUNET_DB_QueryStatus qs;
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
// FIXME: why do we do 'get_melt' twice?
|
|
||||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||||
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
|
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
|
||||||
&rctx->rc,
|
&rctx->rc,
|
||||||
&melt)))
|
&rctx->melt)))
|
||||||
{
|
{
|
||||||
switch (qs)
|
switch (qs)
|
||||||
{
|
{
|
||||||
@ -643,6 +617,17 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
|||||||
}
|
}
|
||||||
goto cleanup;
|
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 */
|
/* Parse link signatures array */
|
||||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
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,
|
&rctx->gamma_tp,
|
||||||
rcds[i].coin_ev,
|
rcds[i].coin_ev,
|
||||||
rcds[i].coin_ev_size,
|
rcds[i].coin_ev_size,
|
||||||
&melt.session.coin.coin_pub,
|
&rctx->melt.session.coin.coin_pub,
|
||||||
&link_sigs[i]))
|
&link_sigs[i]))
|
||||||
{
|
{
|
||||||
GNUNET_break_op (0);
|
GNUNET_break_op (0);
|
||||||
|
@ -1034,7 +1034,8 @@ prepare_statements (struct PostgresClosure *pg)
|
|||||||
") SELECT known_coin_id, $2, $3, $4, $5, $6, "
|
") SELECT known_coin_id, $2, $3, $4, $5, $6, "
|
||||||
" $7, $8, $9, $10, $11, $12, $13"
|
" $7, $8, $9, $10, $11, $12, $13"
|
||||||
" FROM known_coins"
|
" FROM known_coins"
|
||||||
" WHERE coin_pub=$1;",
|
" WHERE coin_pub=$1"
|
||||||
|
" ON CONFLICT DO NOTHING;",
|
||||||
13),
|
13),
|
||||||
/* Fetch an existing deposit request, used to ensure idempotency
|
/* Fetch an existing deposit request, used to ensure idempotency
|
||||||
during /deposit processing. Used in #postgres_have_deposit(). */
|
during /deposit processing. Used in #postgres_have_deposit(). */
|
||||||
|
@ -263,7 +263,7 @@ verify_deposit_signature_conflict (
|
|||||||
ec = TALER_JSON_get_error_code (json);
|
ec = TALER_JSON_get_error_code (json);
|
||||||
switch (ec)
|
switch (ec)
|
||||||
{
|
{
|
||||||
case TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS:
|
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||||
if (0 >
|
if (0 >
|
||||||
TALER_amount_add (&total,
|
TALER_amount_add (&total,
|
||||||
&total,
|
&total,
|
||||||
|
@ -97,7 +97,7 @@ struct TALER_EXCHANGE_MeltHandle
|
|||||||
* @param[out] noreveal_index set to the noreveal index selected by the exchange
|
* @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
|
* @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,
|
verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||||
const json_t *json,
|
const json_t *json,
|
||||||
struct TALER_ExchangePublicKeyP *exchange_pub,
|
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
|
* @param json json reply with the signature(s) and transaction history
|
||||||
* @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
|
* @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,
|
verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||||
const json_t *json)
|
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);
|
ec = TALER_JSON_get_error_code (json);
|
||||||
switch (ec)
|
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 */
|
/* check if melt operation was really too expensive given history */
|
||||||
if (0 >
|
if (0 >
|
||||||
TALER_amount_add (&total,
|
TALER_amount_add (&total,
|
||||||
@ -379,7 +379,7 @@ handle_melt_finished (void *cls,
|
|||||||
hr.ec = TALER_JSON_get_error_code (j);
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
switch (hr.ec)
|
switch (hr.ec)
|
||||||
{
|
{
|
||||||
case TALER_EC_EXCHANGE_MELT_INSUFFICIENT_FUNDS:
|
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||||
/* Double spending; check signatures on transaction history */
|
/* Double spending; check signatures on transaction history */
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
verify_melt_signature_spend_conflict (mh,
|
verify_melt_signature_spend_conflict (mh,
|
||||||
|
Loading…
Reference in New Issue
Block a user