protocol v12 changes (/recoup split, signature changes) plus database sharding plus O(n^2)=>O(n) worst-case complexity reduction on coin balance checks
This commit is contained in:
parent
2c14d33870
commit
87376e02eb
@ -1 +1 @@
|
||||
Subproject commit ce3c127a788d4bc35caf8472fa9799b45f8d2133
|
||||
Subproject commit 560c1ac1d321f7da4fc43ef96858ac27b7eaa2bd
|
@ -341,9 +341,12 @@ TEAH_DEPOSIT_CONFIRMATION_init (void)
|
||||
void
|
||||
TEAH_DEPOSIT_CONFIRMATION_done (void)
|
||||
{
|
||||
GNUNET_CONTAINER_multihashmap_destroy (cache);
|
||||
cache = NULL;
|
||||
GNUNET_assert (0 == pthread_mutex_destroy (&lock));
|
||||
if (NULL != cache)
|
||||
{
|
||||
GNUNET_CONTAINER_multihashmap_destroy (cache);
|
||||
cache = NULL;
|
||||
GNUNET_assert (0 == pthread_mutex_destroy (&lock));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1093,19 +1093,10 @@ struct RevealContext
|
||||
static void
|
||||
reveal_data_cb (void *cls,
|
||||
uint32_t num_freshcoins,
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
|
||||
unsigned int num_tprivs,
|
||||
const struct TALER_TransferPrivateKeyP *tprivs,
|
||||
const struct TALER_TransferPublicKeyP *tp)
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
|
||||
/* Note: optimization using custom database accessor API could avoid
|
||||
fetching these fields -- and we */
|
||||
(void) num_tprivs;
|
||||
(void) tprivs;
|
||||
(void) tp;
|
||||
|
||||
rctx->num_freshcoins = num_freshcoins;
|
||||
rctx->new_issues = GNUNET_new_array (
|
||||
num_freshcoins,
|
||||
@ -1117,9 +1108,8 @@ reveal_data_cb (void *cls,
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* lookup new coin denomination key */
|
||||
qs = TALER_ARL_get_denomination_info (&rrcs[i].denom_pub,
|
||||
&rctx->new_issues[i],
|
||||
NULL);
|
||||
qs = TALER_ARL_get_denomination_info_by_hash (&rrcs[i].h_denom_pub,
|
||||
&rctx->new_issues[i]);
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
report_row_inconsistency ("refresh_reveal",
|
||||
@ -1287,13 +1277,16 @@ refresh_session_cb (void *cls,
|
||||
|
||||
/* verify melt signature */
|
||||
{
|
||||
const struct TALER_DenominationHash h_denom_pub;
|
||||
struct TALER_DenominationHash h_denom_pub;
|
||||
struct TALER_Amount fee_refresh;
|
||||
|
||||
TALER_denom_pub_hash (denom_pub,
|
||||
&rmc.h_denom_pub);
|
||||
&h_denom_pub);
|
||||
TALER_amount_ntoh (&fee_refresh,
|
||||
&issue->fee_refresh);
|
||||
if (GNUNET_OK !=
|
||||
TALER_wallet_melt_verify (&rmc.amount_with_fee,
|
||||
&issue->fee_refresh,
|
||||
TALER_wallet_melt_verify (amount_with_fee,
|
||||
&fee_refresh,
|
||||
rc,
|
||||
&h_denom_pub,
|
||||
coin_pub,
|
||||
|
@ -298,6 +298,9 @@ add_deposit (const struct Merchant *m)
|
||||
{
|
||||
struct Deposit d;
|
||||
struct TALER_EXCHANGEDB_Deposit deposit;
|
||||
uint64_t known_coin_id;
|
||||
struct TALER_DenominationHash dph;
|
||||
struct TALER_AgeHash agh;
|
||||
|
||||
RANDOMIZE (&d.coin.coin_pub);
|
||||
d.coin.denom_pub_hash = h_denom_pub;
|
||||
@ -306,7 +309,10 @@ add_deposit (const struct Merchant *m)
|
||||
|
||||
if (0 >=
|
||||
plugin->ensure_coin_known (plugin->cls,
|
||||
&d.coin))
|
||||
&d.coin,
|
||||
&known_coin_id,
|
||||
&dph,
|
||||
&agh))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
global_ret = EXIT_FAILURE;
|
||||
|
@ -102,6 +102,7 @@ taler_exchange_httpd_SOURCES = \
|
||||
taler-exchange-httpd_metrics.c taler-exchange-httpd_metrics.h \
|
||||
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
|
||||
taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \
|
||||
taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \
|
||||
taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \
|
||||
taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
|
||||
taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "taler-exchange-httpd_metrics.h"
|
||||
#include "taler-exchange-httpd_mhd.h"
|
||||
#include "taler-exchange-httpd_recoup.h"
|
||||
#include "taler-exchange-httpd_recoup-refresh.h"
|
||||
#include "taler-exchange-httpd_refreshes_reveal.h"
|
||||
#include "taler-exchange-httpd_refund.h"
|
||||
#include "taler-exchange-httpd_reserves_get.h"
|
||||
@ -256,6 +257,10 @@ handle_post_coins (struct TEH_RequestContext *rc,
|
||||
.op = "recoup",
|
||||
.handler = &TEH_handler_recoup
|
||||
},
|
||||
{
|
||||
.op = "recoup-refresh",
|
||||
.handler = &TEH_handler_recoup_refresh
|
||||
},
|
||||
{
|
||||
.op = "refund",
|
||||
.handler = &TEH_handler_refund
|
||||
|
@ -29,55 +29,6 @@
|
||||
#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).
|
||||
@ -91,24 +42,22 @@ reply_insufficient_funds (
|
||||
#define MAX_TRANSACTION_COMMIT_RETRIES 100
|
||||
|
||||
|
||||
/**
|
||||
* Ensure coin is known in the database, and handle conflicts and errors.
|
||||
*
|
||||
* @param coin the coin to make known
|
||||
* @param connection MHD request context
|
||||
* @param[out] mhd_ret set to MHD status on error
|
||||
* @return transaction status, negative on error (@a mhd_ret will be set in this case)
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
struct MHD_Connection *connection,
|
||||
uint64_t *known_coin_id,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
enum TALER_EXCHANGEDB_CoinKnownStatus cks;
|
||||
struct TALER_DenominationHash h_denom_pub;
|
||||
struct TALER_AgeHash age_hash;
|
||||
|
||||
/* make sure coin is 'known' in database */
|
||||
cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
|
||||
coin);
|
||||
coin,
|
||||
known_coin_id,
|
||||
&h_denom_pub,
|
||||
&age_hash);
|
||||
switch (cks)
|
||||
{
|
||||
case TALER_EXCHANGEDB_CKS_ADDED:
|
||||
@ -124,250 +73,20 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
case TALER_EXCHANGEDB_CKS_CONFLICT:
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
&coin->coin_pub,
|
||||
GNUNET_NO,
|
||||
&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,
|
||||
NULL);
|
||||
return qs;
|
||||
}
|
||||
// FIXME: why do we even return the transaction
|
||||
// history here!? This is a coin with multiple
|
||||
// associated denominations, after all...
|
||||
// => this is probably the wrong call, as this
|
||||
// is NOT about insufficient funds!
|
||||
*mhd_ret
|
||||
= TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
|
||||
&coin->coin_pub,
|
||||
tl);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when we actually know that the balance (was) insufficient.
|
||||
* Re-does the check (slowly) to compute the full error message for
|
||||
* the client.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
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 (
|
||||
case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
tl,
|
||||
op_cost,
|
||||
&coin_residual);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
|
||||
&coin->coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
case TALER_EXCHANGEDB_CKS_AGE_CONFLICT:
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH,
|
||||
&coin->coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* This should not happen: The coin has sufficient funds
|
||||
after all!?!? */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
bool balance_ok = false;
|
||||
bool zombie_ok = false;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = TEH_plugin->do_check_coin_balance (TEH_plugin->cls,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
check_recoup,
|
||||
zombie_required,
|
||||
&balance_ok,
|
||||
&zombie_ok);
|
||||
switch (qs)
|
||||
{
|
||||
case GNUNET_DB_STATUS_HARD_ERROR:
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"check_coin_balance");
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_SOFT_ERROR:
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"check_coin_balance");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
|
||||
/* handled below */
|
||||
break;
|
||||
}
|
||||
if (! zombie_ok)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
*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 (balance_ok)
|
||||
return qs;
|
||||
/* balance is not OK, do expensive call to compute full error message */
|
||||
qs = check_coin_balance (connection,
|
||||
coin_pub,
|
||||
coin_value,
|
||||
op_cost,
|
||||
check_recoup,
|
||||
zombie_required,
|
||||
mhd_ret);
|
||||
if (qs < 0)
|
||||
return qs; /* we expected to fail (same check as before!) */
|
||||
GNUNET_break (0); /* stored procedure and individual statements
|
||||
disagree, should be impossible! */
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
|
||||
"stored procedure disagrees with full coin transaction history fetch");
|
||||
GNUNET_assert (0);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
|
@ -32,44 +32,17 @@
|
||||
*
|
||||
* @param coin the coin to make known
|
||||
* @param connection MHD request context
|
||||
* @param[out] known_coin_id set to the unique ID for the coin in the DB
|
||||
* @param[out] mhd_ret set to MHD status on error
|
||||
* @return transaction status, negative on error (@a mhd_ret will be set in this case)
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
|
||||
struct MHD_Connection *connection,
|
||||
uint64_t *known_coin_id,
|
||||
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.
|
||||
*
|
||||
* We first do a "fast" check using a stored procedure, and
|
||||
* only obtain the "full" data on failure (for performance).
|
||||
*
|
||||
* @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
|
||||
|
@ -47,6 +47,7 @@
|
||||
* @param connection connection to the client
|
||||
* @param coin_pub public key of the coin
|
||||
* @param h_wire hash of wire details
|
||||
* @param h_extensions hash of applicable extensions
|
||||
* @param h_contract_terms hash of contract details
|
||||
* @param exchange_timestamp exchange's timestamp
|
||||
* @param refund_deadline until when this deposit be refunded
|
||||
@ -118,23 +119,21 @@ struct DepositContext
|
||||
|
||||
/**
|
||||
* Our timestamp (when we received the request).
|
||||
* Possibly updated by the transaction if the
|
||||
* request is idempotent (was repeated).
|
||||
*/
|
||||
struct GNUNET_TIME_Timestamp exchange_timestamp;
|
||||
|
||||
/**
|
||||
* Calculated hash over the wire details.
|
||||
* Hash of the payto URI.
|
||||
*/
|
||||
struct TALER_MerchantWireHash h_wire;
|
||||
struct TALER_PaytoHash h_payto;
|
||||
|
||||
/**
|
||||
* Value of the coin.
|
||||
* Row of of the coin in the known_coins table.
|
||||
*/
|
||||
struct TALER_Amount value;
|
||||
uint64_t known_coin_id;
|
||||
|
||||
/**
|
||||
* payto:// URI of the credited account.
|
||||
*/
|
||||
const char *payto_uri;
|
||||
};
|
||||
|
||||
|
||||
@ -157,15 +156,18 @@ deposit_transaction (void *cls,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct DepositContext *dc = cls;
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit;
|
||||
struct TALER_Amount spent;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
struct TALER_Amount deposit_fee;
|
||||
bool balance_ok;
|
||||
bool in_conflict;
|
||||
|
||||
/* begin optimistically: assume this is a new deposit */
|
||||
qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
|
||||
dc->exchange_timestamp,
|
||||
deposit);
|
||||
qs = TEH_plugin->do_deposit (TEH_plugin->cls,
|
||||
dc->deposit,
|
||||
dc->known_coin_id,
|
||||
&dc->h_payto,
|
||||
false, /* FIXME-OEC: extension blocked */
|
||||
&dc->exchange_timestamp,
|
||||
&balance_ok,
|
||||
&in_conflict);
|
||||
if (qs < 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
@ -174,73 +176,30 @@ deposit_transaction (void *cls,
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
NULL);
|
||||
"deposit");
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
if (in_conflict)
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
/* Conflict on insert, but record does not exist?
|
||||
That makes no sense. */
|
||||
GNUNET_break (0);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
{
|
||||
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->rollback (TEH_plugin->cls);
|
||||
*mhd_ret
|
||||
= TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
|
||||
&dc->deposit->coin.coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
if (! balance_ok)
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*mhd_ret
|
||||
= TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
|
||||
&dc->deposit->coin.coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
|
||||
@ -263,9 +222,10 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
{
|
||||
struct DepositContext dc;
|
||||
struct TALER_EXCHANGEDB_Deposit deposit;
|
||||
const char *payto_uri;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_string ("merchant_payto_uri",
|
||||
&dc.payto_uri),
|
||||
&payto_uri),
|
||||
GNUNET_JSON_spec_fixed_auto ("wire_salt",
|
||||
&deposit.wire_salt),
|
||||
TALER_JSON_spec_amount ("contribution",
|
||||
@ -290,6 +250,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
&deposit.wire_deadline),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
struct TALER_MerchantWireHash h_wire;
|
||||
|
||||
memset (&deposit,
|
||||
0,
|
||||
@ -316,7 +277,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
{
|
||||
char *emsg;
|
||||
|
||||
emsg = TALER_payto_validate (dc.payto_uri);
|
||||
emsg = TALER_payto_validate (payto_uri);
|
||||
if (NULL != emsg)
|
||||
{
|
||||
MHD_RESULT ret;
|
||||
@ -331,7 +292,6 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
deposit.receiver_wire_account = (char *) dc.payto_uri;
|
||||
if (GNUNET_TIME_timestamp_cmp (deposit.refund_deadline,
|
||||
>,
|
||||
deposit.wire_deadline))
|
||||
@ -343,9 +303,12 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
|
||||
NULL);
|
||||
}
|
||||
TALER_merchant_wire_signature_hash (dc.payto_uri,
|
||||
deposit.receiver_wire_account = (char *) payto_uri;
|
||||
TALER_payto_hash (payto_uri,
|
||||
&dc.h_payto);
|
||||
TALER_merchant_wire_signature_hash (payto_uri,
|
||||
&deposit.wire_salt,
|
||||
&dc.h_wire);
|
||||
&h_wire);
|
||||
dc.deposit = &deposit;
|
||||
|
||||
/* new deposit */
|
||||
@ -366,42 +329,30 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
|
||||
{
|
||||
/* This denomination is past the expiration time for deposits */
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&deposit.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"DEPOSIT");
|
||||
}
|
||||
if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
|
||||
{
|
||||
/* This denomination is not yet valid */
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&deposit.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"DEPOSIT");
|
||||
}
|
||||
if (dk->recoup_possible)
|
||||
{
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
/* This denomination has been revoked */
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&deposit.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
|
||||
"DEPOSIT");
|
||||
}
|
||||
@ -419,7 +370,6 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
dc.value = dk->meta.value;
|
||||
}
|
||||
if (0 < TALER_amount_cmp (&deposit.deposit_fee,
|
||||
&deposit.amount_with_fee))
|
||||
@ -435,7 +385,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
if (GNUNET_OK !=
|
||||
TALER_wallet_deposit_verify (&deposit.amount_with_fee,
|
||||
&deposit.deposit_fee,
|
||||
&dc.h_wire,
|
||||
&h_wire,
|
||||
&deposit.h_contract_terms,
|
||||
NULL /* h_extensions! */,
|
||||
&deposit.coin.denom_pub_hash,
|
||||
@ -470,6 +420,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
/* make sure coin is 'known' in database */
|
||||
qs = TEH_make_coin_known (&deposit.coin,
|
||||
connection,
|
||||
&dc.known_coin_id,
|
||||
&mhd_ret);
|
||||
/* no transaction => no serialization failures should be possible */
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
|
||||
@ -506,7 +457,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
|
||||
&deposit.deposit_fee));
|
||||
res = reply_deposit_success (connection,
|
||||
&deposit.coin.coin_pub,
|
||||
&dc.h_wire,
|
||||
&h_wire,
|
||||
NULL /* h_extensions! */,
|
||||
&deposit.h_contract_terms,
|
||||
dc.exchange_timestamp,
|
||||
|
@ -57,7 +57,7 @@
|
||||
* #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
|
||||
* exchange_api_handle.c!
|
||||
*/
|
||||
#define EXCHANGE_PROTOCOL_VERSION "11:0:1"
|
||||
#define EXCHANGE_PROTOCOL_VERSION "12:0:0"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2020 Taler Systems SA
|
||||
Copyright (C) 2014-2021 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
@ -89,6 +89,11 @@ struct MeltContext
|
||||
*/
|
||||
struct TALER_EXCHANGEDB_Refresh refresh_session;
|
||||
|
||||
/**
|
||||
* UUID of the coin in the known_coins table.
|
||||
*/
|
||||
uint64_t known_coin_id;
|
||||
|
||||
/**
|
||||
* Information about the @e coin's value.
|
||||
*/
|
||||
@ -141,15 +146,19 @@ melt_transaction (void *cls,
|
||||
{
|
||||
struct MeltContext *rmc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
uint32_t noreveal_index;
|
||||
bool balance_ok;
|
||||
|
||||
/* pick challenge and persist it */
|
||||
rmc->refresh_session.noreveal_index
|
||||
= GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
|
||||
TALER_CNC_KAPPA);
|
||||
|
||||
if (0 >
|
||||
(qs = TEH_plugin->insert_melt (TEH_plugin->cls,
|
||||
&rmc->refresh_session)))
|
||||
(qs = TEH_plugin->do_melt (TEH_plugin->cls,
|
||||
&rmc->refresh_session,
|
||||
rmc->known_coin_id,
|
||||
&rmc->zombie_required,
|
||||
&balance_ok)))
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
|
||||
{
|
||||
@ -161,64 +170,43 @@ melt_transaction (void *cls,
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
|
||||
if (rmc->zombie_required)
|
||||
{
|
||||
/* Check if we already created a matching refresh_session */
|
||||
qs = TEH_plugin->get_melt_index (TEH_plugin->cls,
|
||||
&rmc->refresh_session.rc,
|
||||
&noreveal_index);
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
|
||||
{
|
||||
TALER_LOG_DEBUG ("Coin was previously melted, returning old reply\n");
|
||||
*mhd_ret = reply_melt_success (connection,
|
||||
&rmc->refresh_session.rc,
|
||||
noreveal_index);
|
||||
/* 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;
|
||||
}
|
||||
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,
|
||||
"melt index");
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
/* Conflict on insert, but record does not exist?
|
||||
That makes no sense. */
|
||||
GNUNET_break (0);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
GNUNET_break_op (0);
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*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;
|
||||
}
|
||||
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);
|
||||
if (! balance_ok)
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*mhd_ret
|
||||
= TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
|
||||
&rmc->refresh_session.coin.coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
/* All good, commit, final response will be generated by caller */
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a "melt" request after the first parsing has
|
||||
* happened. We now need to validate the coins being melted and the
|
||||
* session signature and then hand things of to execute the melt
|
||||
* operation. This function parses the JSON arrays and then passes
|
||||
* processing on to #melt_transaction().
|
||||
* happened. Performs the database transactions.
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param[in,out] rmc details about the melt request
|
||||
* @return MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
handle_melt (struct MHD_Connection *connection,
|
||||
struct MeltContext *rmc)
|
||||
database_melt (struct MHD_Connection *connection,
|
||||
struct MeltContext *rmc)
|
||||
{
|
||||
if (GNUNET_SYSERR ==
|
||||
TEH_plugin->preflight (TEH_plugin->cls))
|
||||
@ -230,36 +218,6 @@ handle_melt (struct MHD_Connection *connection,
|
||||
"preflight failure");
|
||||
}
|
||||
|
||||
/* verify signature of coin for melt operation */
|
||||
{
|
||||
struct TALER_RefreshMeltCoinAffirmationPS body = {
|
||||
.purpose.size = htonl (sizeof (body)),
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
|
||||
.rc = rmc->refresh_session.rc,
|
||||
.h_denom_pub = rmc->refresh_session.coin.denom_pub_hash,
|
||||
.coin_pub = rmc->refresh_session.coin.coin_pub
|
||||
};
|
||||
|
||||
TALER_amount_hton (&body.amount_with_fee,
|
||||
&rmc->refresh_session.amount_with_fee);
|
||||
TALER_amount_hton (&body.melt_fee,
|
||||
&rmc->coin_refresh_fee);
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (
|
||||
TALER_SIGNATURE_WALLET_COIN_MELT,
|
||||
&body,
|
||||
&rmc->refresh_session.coin_sig.eddsa_signature,
|
||||
&rmc->refresh_session.coin.coin_pub.eddsa_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* first, make sure coin is known */
|
||||
if (! rmc->coin_is_dirty)
|
||||
{
|
||||
@ -268,6 +226,7 @@ handle_melt (struct MHD_Connection *connection,
|
||||
|
||||
qs = TEH_make_coin_known (&rmc->refresh_session.coin,
|
||||
connection,
|
||||
&rmc->known_coin_id,
|
||||
&mhd_ret);
|
||||
/* no transaction => no serialization failures should be possible */
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
|
||||
@ -305,8 +264,8 @@ handle_melt (struct MHD_Connection *connection,
|
||||
* @return MHD status code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
check_for_denomination_key (struct MHD_Connection *connection,
|
||||
struct MeltContext *rmc)
|
||||
check_melt_valid (struct MHD_Connection *connection,
|
||||
struct MeltContext *rmc)
|
||||
{
|
||||
/* Baseline: check if deposits/refreshs are generally
|
||||
simply still allowed for this denomination */
|
||||
@ -321,30 +280,64 @@ check_for_denomination_key (struct MHD_Connection *connection,
|
||||
return mret;
|
||||
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))
|
||||
{
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
/* Way too late now, even zombies have expired */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&rmc->refresh_session.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"MELT");
|
||||
}
|
||||
if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
|
||||
{
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
/* This denomination is not yet valid */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&rmc->refresh_session.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"MELT");
|
||||
}
|
||||
|
||||
rmc->coin_refresh_fee = dk->meta.fee_refresh;
|
||||
rmc->coin_value = dk->meta.value;
|
||||
/* sanity-check that "total melt amount > melt fee" */
|
||||
if (0 <
|
||||
TALER_amount_cmp (&rmc->coin_refresh_fee,
|
||||
&rmc->refresh_session.amount_with_fee))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
|
||||
NULL);
|
||||
}
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_test_coin_valid (&rmc->refresh_session.coin,
|
||||
&dk->denom_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* verify signature of coin for melt operation */
|
||||
if (GNUNET_OK !=
|
||||
TALER_wallet_melt_verify (&rmc->refresh_session.amount_with_fee,
|
||||
&rmc->coin_refresh_fee,
|
||||
&rmc->refresh_session.rc,
|
||||
&rmc->refresh_session.coin.denom_pub_hash,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
&rmc->refresh_session.coin_sig))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
|
||||
{
|
||||
/* We are past deposit expiration time, but maybe this is a zombie? */
|
||||
@ -357,6 +350,7 @@ check_for_denomination_key (struct MHD_Connection *connection,
|
||||
qs = TEH_plugin->get_coin_denomination (
|
||||
TEH_plugin->cls,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
&rmc->known_coin_id,
|
||||
&denom_hash);
|
||||
if (0 > qs)
|
||||
{
|
||||
@ -369,14 +363,10 @@ check_for_denomination_key (struct MHD_Connection *connection,
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
|
||||
{
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
/* We never saw this coin before, so _this_ justification is not OK */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&rmc->refresh_session.coin.denom_pub_hash,
|
||||
now,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"MELT");
|
||||
}
|
||||
@ -389,67 +379,25 @@ check_for_denomination_key (struct MHD_Connection *connection,
|
||||
&rmc->refresh_session.coin.denom_pub_hash))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
// => this is probably the wrong call, as this
|
||||
// is NOT about insufficient funds!
|
||||
// (see also taler-exchange-httpd_db.c for an equivalent issue)
|
||||
return TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
return TALER_MHD_reply_with_ec (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
|
||||
&rmc->refresh_session.coin.coin_pub,
|
||||
NULL);
|
||||
TALER_B2S (&denom_hash));
|
||||
}
|
||||
rmc->zombie_required = true; /* check later that zombie is satisfied */
|
||||
}
|
||||
|
||||
rmc->coin_refresh_fee = dk->meta.fee_refresh;
|
||||
rmc->coin_value = dk->meta.value;
|
||||
/* check coin is actually properly signed */
|
||||
if (GNUNET_OK !=
|
||||
TALER_test_coin_valid (&rmc->refresh_session.coin,
|
||||
&dk->denom_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* sanity-check that "total melt amount > melt fee" */
|
||||
if (0 <
|
||||
TALER_amount_cmp (&rmc->coin_refresh_fee,
|
||||
&rmc->refresh_session.amount_with_fee))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
|
||||
NULL);
|
||||
}
|
||||
return handle_melt (connection,
|
||||
rmc);
|
||||
return database_melt (connection,
|
||||
rmc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON
|
||||
* components and then hands things of to #check_for_denomination_key() to
|
||||
* validate the melted coins, the signature and execute the melt using
|
||||
* handle_melt().
|
||||
|
||||
* @param connection the MHD connection to handle
|
||||
* @param coin_pub public key of the coin
|
||||
* @param root uploaded JSON data
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_handler_melt (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const json_t *root)
|
||||
{
|
||||
struct MeltContext rmc;
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
MHD_RESULT res;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
TALER_JSON_spec_denom_sig ("denom_sig",
|
||||
&rmc.refresh_session.coin.denom_sig),
|
||||
@ -469,16 +417,24 @@ TEH_handler_melt (struct MHD_Connection *connection,
|
||||
0,
|
||||
sizeof (rmc));
|
||||
rmc.refresh_session.coin.coin_pub = *coin_pub;
|
||||
ret = TALER_MHD_parse_json_data (connection,
|
||||
root,
|
||||
spec);
|
||||
if (GNUNET_OK != ret)
|
||||
return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
|
||||
|
||||
res = check_for_denomination_key (connection,
|
||||
&rmc);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return res;
|
||||
{
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
ret = TALER_MHD_parse_json_data (connection,
|
||||
root,
|
||||
spec);
|
||||
if (GNUNET_OK != ret)
|
||||
return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
|
||||
}
|
||||
|
||||
{
|
||||
MHD_RESULT res;
|
||||
|
||||
res = check_melt_valid (connection,
|
||||
&rmc);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,18 +51,12 @@ TEH_handler_metrics (struct TEH_RequestContext *rc,
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
|
||||
"# HELP taler_exchange_received_requests "
|
||||
" number of received requests by type\n"
|
||||
"# TYPE taler_exchange_received_requests counter\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n"
|
||||
"taler_exchange_received_requests{type=\"%s\"} %llu\n",
|
||||
"other",
|
||||
TEH_METRICS_num_conflict[TEH_MT_OTHER],
|
||||
@ -72,12 +66,6 @@ TEH_handler_metrics (struct TEH_RequestContext *rc,
|
||||
TEH_METRICS_num_conflict[TEH_MT_WITHDRAW],
|
||||
"melt",
|
||||
TEH_METRICS_num_conflict[TEH_MT_MELT],
|
||||
"reveal-precheck",
|
||||
TEH_METRICS_num_conflict[TEH_MT_REVEAL_PRECHECK],
|
||||
"reveal",
|
||||
TEH_METRICS_num_conflict[TEH_MT_REVEAL],
|
||||
"reveal-persist",
|
||||
TEH_METRICS_num_conflict[TEH_MT_REVEAL_PERSIST],
|
||||
"other",
|
||||
TEH_METRICS_num_requests[TEH_MT_OTHER],
|
||||
"deposit",
|
||||
@ -85,13 +73,7 @@ TEH_handler_metrics (struct TEH_RequestContext *rc,
|
||||
"withdraw",
|
||||
TEH_METRICS_num_requests[TEH_MT_WITHDRAW],
|
||||
"melt",
|
||||
TEH_METRICS_num_requests[TEH_MT_MELT],
|
||||
"reveal-precheck",
|
||||
TEH_METRICS_num_requests[TEH_MT_REVEAL_PRECHECK],
|
||||
"reveal",
|
||||
TEH_METRICS_num_requests[TEH_MT_REVEAL],
|
||||
"reveal-persist",
|
||||
TEH_METRICS_num_requests[TEH_MT_REVEAL_PERSIST]);
|
||||
TEH_METRICS_num_requests[TEH_MT_MELT]);
|
||||
resp = MHD_create_response_from_buffer (strlen (reply),
|
||||
reply,
|
||||
MHD_RESPMEM_MUST_FREE);
|
||||
|
@ -35,10 +35,7 @@ enum TEH_MetricType
|
||||
TEH_MT_DEPOSIT = 1,
|
||||
TEH_MT_WITHDRAW = 2,
|
||||
TEH_MT_MELT = 3,
|
||||
TEH_MT_REVEAL_PRECHECK = 4,
|
||||
TEH_MT_REVEAL = 5,
|
||||
TEH_MT_REVEAL_PERSIST = 6,
|
||||
TEH_MT_COUNT = 7 /* MUST BE LAST! */
|
||||
TEH_MT_COUNT = 4 /* MUST BE LAST! */
|
||||
};
|
||||
|
||||
|
||||
|
411
src/exchange/taler-exchange-httpd_recoup-refresh.c
Normal file
411
src/exchange/taler-exchange-httpd_recoup-refresh.c
Normal file
@ -0,0 +1,411 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2017-2021 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file taler-exchange-httpd_recoup-refresh.c
|
||||
* @brief Handle /recoup-refresh requests; parses the POST and JSON and
|
||||
* verifies the coin signature before handing things off
|
||||
* to the database.
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
#include "platform.h"
|
||||
#include <gnunet/gnunet_util_lib.h>
|
||||
#include <gnunet/gnunet_json_lib.h>
|
||||
#include <jansson.h>
|
||||
#include <microhttpd.h>
|
||||
#include <pthread.h>
|
||||
#include "taler_json_lib.h"
|
||||
#include "taler_mhd_lib.h"
|
||||
#include "taler-exchange-httpd_db.h"
|
||||
#include "taler-exchange-httpd_recoup-refresh.h"
|
||||
#include "taler-exchange-httpd_responses.h"
|
||||
#include "taler-exchange-httpd_keys.h"
|
||||
#include "taler_exchangedb_lib.h"
|
||||
|
||||
|
||||
/**
|
||||
* Closure for #recoup_refresh_transaction().
|
||||
*/
|
||||
struct RecoupContext
|
||||
{
|
||||
|
||||
/**
|
||||
* Set by #recoup_transaction() to the old coin that will
|
||||
* receive the recoup.
|
||||
*/
|
||||
struct TALER_CoinSpendPublicKeyP old_coin_pub;
|
||||
|
||||
/**
|
||||
* Details about the coin.
|
||||
*/
|
||||
const struct TALER_CoinPublicInfo *coin;
|
||||
|
||||
/**
|
||||
* Key used to blind the coin.
|
||||
*/
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks;
|
||||
|
||||
/**
|
||||
* Signature of the coin requesting recoup.
|
||||
*/
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig;
|
||||
|
||||
/**
|
||||
* The amount requested to be recouped.
|
||||
*/
|
||||
const struct TALER_Amount *requested_amount;
|
||||
|
||||
/**
|
||||
* Unique ID of the coin in the known_coins table.
|
||||
*/
|
||||
uint64_t known_coin_id;
|
||||
|
||||
/**
|
||||
* Unique ID of the refresh reveal context of the melt for the new coin.
|
||||
*/
|
||||
uint64_t rrc_serial;
|
||||
|
||||
/**
|
||||
* Set by #recoup_transaction to the timestamp when the recoup
|
||||
* was accepted.
|
||||
*/
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute a "recoup-refresh". The validity of the coin and signature have
|
||||
* already been checked. The database must now check that the coin is not
|
||||
* (double) spent, and execute the transaction.
|
||||
*
|
||||
* IF it returns a non-error code, the transaction logic MUST
|
||||
* NOT queue a MHD response. IF it returns an hard error, the
|
||||
* transaction logic MUST queue a MHD response and set @a mhd_ret. IF
|
||||
* it returns the soft error code, the function MAY be called again to
|
||||
* retry and MUST not queue a MHD response.
|
||||
*
|
||||
* @param cls the `struct RecoupContext *`
|
||||
* @param connection MHD request which triggered the transaction
|
||||
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||
* if transaction failed (!)
|
||||
* @return transaction status code
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
recoup_refresh_transaction (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RecoupContext *pc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
bool recoup_ok;
|
||||
bool internal_failure;
|
||||
|
||||
/* Finally, store new refund data */
|
||||
pc->now = GNUNET_TIME_timestamp_get ();
|
||||
qs = TEH_plugin->do_recoup_refresh (TEH_plugin->cls,
|
||||
&pc->old_coin_pub,
|
||||
pc->rrc_serial,
|
||||
pc->requested_amount,
|
||||
pc->coin_bks,
|
||||
&pc->coin->coin_pub,
|
||||
pc->known_coin_id,
|
||||
pc->coin_sig,
|
||||
&pc->now,
|
||||
&recoup_ok,
|
||||
&internal_failure);
|
||||
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,
|
||||
"do_recoup_refresh");
|
||||
return qs;
|
||||
}
|
||||
|
||||
if (internal_failure)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
|
||||
"coin transaction history");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (! recoup_ok)
|
||||
{
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
|
||||
&pc->coin->coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We have parsed the JSON information about the recoup request. Do
|
||||
* some basic sanity checks (especially that the signature on the
|
||||
* request and coin is valid) and then execute the recoup operation.
|
||||
* Note that we need the DB to check the fee structure, so this is not
|
||||
* done here but during the recoup_transaction().
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param coin information about the coin
|
||||
* @param coin_bks blinding data of the coin (to be checked)
|
||||
* @param coin_sig signature of the coin
|
||||
* @param requested_amount requested amount to be recouped
|
||||
* @return MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
verify_and_execute_recoup_refresh (
|
||||
struct MHD_Connection *connection,
|
||||
const struct TALER_CoinPublicInfo *coin,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
const struct TALER_Amount *requested_amount)
|
||||
{
|
||||
struct RecoupContext pc;
|
||||
const struct TEH_DenominationKey *dk;
|
||||
MHD_RESULT mret;
|
||||
struct TALER_BlindedCoinHash h_blind;
|
||||
|
||||
/* check denomination exists and is in recoup mode */
|
||||
dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
|
||||
connection,
|
||||
&mret);
|
||||
if (NULL == dk)
|
||||
return mret;
|
||||
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
|
||||
{
|
||||
/* This denomination is past the expiration time for recoup */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"RECOUP-REFRESH");
|
||||
}
|
||||
if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
|
||||
{
|
||||
/* This denomination is not yet valid */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"RECOUP-REFRESH");
|
||||
}
|
||||
if (! dk->recoup_possible)
|
||||
{
|
||||
/* This denomination is not eligible for recoup */
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE,
|
||||
"RECOUP-REFRESH");
|
||||
}
|
||||
|
||||
/* check denomination signature */
|
||||
if (GNUNET_YES !=
|
||||
TALER_test_coin_valid (coin,
|
||||
&dk->denom_pub))
|
||||
{
|
||||
TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* check recoup request signature */
|
||||
if (GNUNET_OK !=
|
||||
TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash,
|
||||
coin_bks,
|
||||
requested_amount,
|
||||
&coin->coin_pub,
|
||||
coin_sig))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
{
|
||||
void *coin_ev;
|
||||
size_t coin_ev_size;
|
||||
struct TALER_CoinPubHash c_hash;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_denom_blind (&dk->denom_pub,
|
||||
coin_bks,
|
||||
NULL, /* FIXME-Oec: TALER_AgeHash * */
|
||||
&coin->coin_pub,
|
||||
&c_hash,
|
||||
&coin_ev,
|
||||
&coin_ev_size))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
|
||||
NULL);
|
||||
}
|
||||
TALER_coin_ev_hash (coin_ev,
|
||||
coin_ev_size,
|
||||
&h_blind);
|
||||
GNUNET_free (coin_ev);
|
||||
}
|
||||
|
||||
pc.coin_sig = coin_sig;
|
||||
pc.coin_bks = coin_bks;
|
||||
pc.coin = coin;
|
||||
pc.requested_amount = requested_amount;
|
||||
|
||||
{
|
||||
MHD_RESULT mhd_ret = MHD_NO;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* make sure coin is 'known' in database */
|
||||
qs = TEH_make_coin_known (coin,
|
||||
connection,
|
||||
&pc.known_coin_id,
|
||||
&mhd_ret);
|
||||
/* no transaction => no serialization failures should be possible */
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
|
||||
if (qs < 0)
|
||||
return mhd_ret;
|
||||
}
|
||||
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
|
||||
&h_blind,
|
||||
&pc.old_coin_pub,
|
||||
&pc.rrc_serial);
|
||||
if (0 > qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"get_old_coin_by_h_blind");
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Recoup-refresh requested for unknown envelope %s\n",
|
||||
GNUNET_h2s (&h_blind.hash));
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform actual recoup transaction */
|
||||
{
|
||||
MHD_RESULT mhd_ret;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_run_transaction (connection,
|
||||
"run recoup-refresh",
|
||||
TEH_MT_OTHER,
|
||||
&mhd_ret,
|
||||
&recoup_refresh_transaction,
|
||||
&pc))
|
||||
return mhd_ret;
|
||||
}
|
||||
/* Recoup succeeded, return result */
|
||||
return TALER_MHD_REPLY_JSON_PACK (connection,
|
||||
MHD_HTTP_OK,
|
||||
GNUNET_JSON_pack_data_auto (
|
||||
"old_coin_pub",
|
||||
&pc.old_coin_pub));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
|
||||
* successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
|
||||
* check the details of the operation specified. If everything checks out,
|
||||
* this will ultimately lead to the refund being executed, or rejected.
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param coin_pub public key of the coin
|
||||
* @param root uploaded JSON data
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_handler_recoup_refresh (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const json_t *root)
|
||||
{
|
||||
enum GNUNET_GenericReturnValue ret;
|
||||
struct TALER_CoinPublicInfo coin;
|
||||
union TALER_DenominationBlindingKeyP coin_bks;
|
||||
struct TALER_CoinSpendSignatureP coin_sig;
|
||||
struct TALER_Amount amount;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
|
||||
&coin.denom_pub_hash),
|
||||
TALER_JSON_spec_denom_sig ("denom_sig",
|
||||
&coin.denom_sig),
|
||||
GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
|
||||
&coin_bks),
|
||||
GNUNET_JSON_spec_fixed_auto ("coin_sig",
|
||||
&coin_sig),
|
||||
TALER_JSON_spec_amount ("amount",
|
||||
TEH_currency,
|
||||
&amount),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
memset (&coin,
|
||||
0,
|
||||
sizeof (coin));
|
||||
coin.coin_pub = *coin_pub;
|
||||
ret = TALER_MHD_parse_json_data (connection,
|
||||
root,
|
||||
spec);
|
||||
if (GNUNET_SYSERR == ret)
|
||||
return MHD_NO; /* hard failure */
|
||||
if (GNUNET_NO == ret)
|
||||
return MHD_YES; /* failure */
|
||||
{
|
||||
MHD_RESULT res;
|
||||
|
||||
res = verify_and_execute_recoup_refresh (connection,
|
||||
&coin,
|
||||
&coin_bks,
|
||||
&coin_sig,
|
||||
&amount);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* end of taler-exchange-httpd_recoup-refresh.c */
|
46
src/exchange/taler-exchange-httpd_recoup-refresh.h
Normal file
46
src/exchange/taler-exchange-httpd_recoup-refresh.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2017, 2021 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file taler-exchange-httpd_recoup_refresh.h
|
||||
* @brief Handle /recoup-refresh requests
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
#ifndef TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
|
||||
#define TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
|
||||
|
||||
#include <gnunet/gnunet_util_lib.h>
|
||||
#include <microhttpd.h>
|
||||
#include "taler-exchange-httpd.h"
|
||||
|
||||
|
||||
/**
|
||||
* Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
|
||||
* successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
|
||||
* check the details of the operation specified. If everything checks out,
|
||||
* this will ultimately lead to the refund being executed, or rejected.
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param coin_pub public key of the coin
|
||||
* @param root uploaded JSON data
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_handler_recoup_refresh (struct MHD_Connection *connection,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const json_t *root);
|
||||
|
||||
|
||||
#endif
|
@ -45,9 +45,10 @@ struct RecoupContext
|
||||
struct TALER_BlindedCoinHash h_blind;
|
||||
|
||||
/**
|
||||
* Full value of the coin.
|
||||
* Set by #recoup_transaction() to the reserve that will
|
||||
* receive the recoup, if #refreshed is #GNUNET_NO.
|
||||
*/
|
||||
struct TALER_Amount value;
|
||||
struct TALER_ReservePublicKeyP reserve_pub;
|
||||
|
||||
/**
|
||||
* Details about the coin.
|
||||
@ -65,29 +66,19 @@ struct RecoupContext
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig;
|
||||
|
||||
/**
|
||||
* Where does the value of the recouped coin go? Which member
|
||||
* of the union is valid depends on @e refreshed.
|
||||
* The amount requested to be recouped.
|
||||
*/
|
||||
union
|
||||
{
|
||||
/**
|
||||
* Set by #recoup_transaction() to the reserve that will
|
||||
* receive the recoup, if #refreshed is #GNUNET_NO.
|
||||
*/
|
||||
struct TALER_ReservePublicKeyP reserve_pub;
|
||||
|
||||
/**
|
||||
* Set by #recoup_transaction() to the old coin that will
|
||||
* receive the recoup, if #refreshed is #GNUNET_YES.
|
||||
*/
|
||||
struct TALER_CoinSpendPublicKeyP old_coin_pub;
|
||||
} target;
|
||||
const struct TALER_Amount *requested_amount;
|
||||
|
||||
/**
|
||||
* Set by #recoup_transaction() to the amount that will be paid back
|
||||
* Unique ID of the withdraw operation in the reserves_out table.
|
||||
*/
|
||||
struct TALER_Amount amount;
|
||||
const struct TALER_Amount *requested_amount;
|
||||
uint64_t reserve_out_serial_id;
|
||||
|
||||
/**
|
||||
* Unique ID of the coin in the known_coins table.
|
||||
*/
|
||||
uint64_t known_coin_id;
|
||||
|
||||
/**
|
||||
* Set by #recoup_transaction to the timestamp when the recoup
|
||||
@ -95,15 +86,9 @@ struct RecoupContext
|
||||
*/
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
/**
|
||||
* true if the client claims the coin originated from a refresh.
|
||||
*/
|
||||
bool refreshed;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// FIXME: this code should be simplified by using TEH_check_coin_balance()
|
||||
/**
|
||||
* Execute a "recoup". The validity of the coin and signature have
|
||||
* already been checked. The database must now check that the coin is
|
||||
@ -127,159 +112,53 @@ recoup_transaction (void *cls,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RecoupContext *pc = cls;
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
struct TALER_Amount spent;
|
||||
struct TALER_Amount recouped;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
bool existing_recoup_found;
|
||||
bool recoup_ok;
|
||||
bool internal_failure;
|
||||
|
||||
/* Check whether a recoup is allowed, and if so, to which
|
||||
reserve / account the money should go */
|
||||
|
||||
/* Calculate remaining balance, including recoups already applied. */
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
&pc->coin->coin_pub,
|
||||
GNUNET_YES,
|
||||
&tl);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"coin transaction list");
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (pc->value.currency,
|
||||
&spent));
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (pc->value.currency,
|
||||
&recouped));
|
||||
/* Check if this coin has been recouped already at least once */
|
||||
existing_recoup_found = false;
|
||||
for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
|
||||
NULL != pos;
|
||||
pos = pos->next)
|
||||
{
|
||||
if ( (TALER_EXCHANGEDB_TT_RECOUP == pos->type) ||
|
||||
(TALER_EXCHANGEDB_TT_RECOUP_REFRESH == pos->type) )
|
||||
{
|
||||
existing_recoup_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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_GENERIC_DB_INVARIANT_FAILURE,
|
||||
"coin transaction history");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Recoup: calculated spent %s\n",
|
||||
TALER_amount2s (&spent));
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Recoup: coin value %s\n",
|
||||
TALER_amount2s (&pc->value));
|
||||
if (0 >
|
||||
TALER_amount_subtract (&pc->amount,
|
||||
&pc->value,
|
||||
&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_RECOUP_COIN_BALANCE_NEGATIVE,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (TALER_amount_is_zero (&pc->amount))
|
||||
{
|
||||
/* Recoup has no effect: coin fully spent! */
|
||||
enum GNUNET_DB_QueryStatus ret;
|
||||
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
if (GNUNET_NO == existing_recoup_found)
|
||||
{
|
||||
/* Refuse: insufficient funds for recoup */
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
|
||||
TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO,
|
||||
&pc->coin->coin_pub,
|
||||
tl);
|
||||
ret = GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We didn't add any new recoup transaction, but there was at least
|
||||
one recoup before, so we give a success response (idempotency!) */
|
||||
ret = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
return ret;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
/* Finally, store new refund data */
|
||||
pc->now = GNUNET_TIME_timestamp_get ();
|
||||
if (0 != TALER_amount_cmp (&pc->amount,
|
||||
pc->requested_amount))
|
||||
{
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
|
||||
TALER_amount2s (&pc->amount));
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* add coin to list of wire transfers for recoup */
|
||||
if (pc->refreshed)
|
||||
{
|
||||
qs = TEH_plugin->insert_recoup_refresh_request (TEH_plugin->cls,
|
||||
pc->coin,
|
||||
pc->coin_sig,
|
||||
pc->coin_bks,
|
||||
&pc->amount,
|
||||
&pc->h_blind,
|
||||
pc->now);
|
||||
}
|
||||
else
|
||||
{
|
||||
qs = TEH_plugin->insert_recoup_request (TEH_plugin->cls,
|
||||
&pc->target.reserve_pub,
|
||||
pc->coin,
|
||||
pc->coin_sig,
|
||||
pc->coin_bks,
|
||||
&pc->amount,
|
||||
&pc->h_blind,
|
||||
pc->now);
|
||||
}
|
||||
qs = TEH_plugin->do_recoup (TEH_plugin->cls,
|
||||
&pc->reserve_pub,
|
||||
pc->reserve_out_serial_id,
|
||||
pc->requested_amount,
|
||||
pc->coin_bks,
|
||||
&pc->coin->coin_pub,
|
||||
pc->known_coin_id,
|
||||
pc->coin_sig,
|
||||
&pc->now,
|
||||
&recoup_ok,
|
||||
&internal_failure);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
TALER_LOG_WARNING ("Failed to store recoup information in database\n");
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"recoup request");
|
||||
}
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"do_recoup");
|
||||
return qs;
|
||||
}
|
||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||
|
||||
if (internal_failure)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
|
||||
"do_recoup");
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (! recoup_ok)
|
||||
{
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
|
||||
&pc->coin->coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
|
||||
@ -295,7 +174,6 @@ recoup_transaction (void *cls,
|
||||
* @param coin_bks blinding data of the coin (to be checked)
|
||||
* @param coin_sig signature of the coin
|
||||
* @param requested_amount requested amount to be recouped
|
||||
* @param refreshed true if the coin was refreshed
|
||||
* @return MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
@ -304,12 +182,10 @@ verify_and_execute_recoup (
|
||||
const struct TALER_CoinPublicInfo *coin,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
bool refreshed)
|
||||
const struct TALER_Amount *requested_amount)
|
||||
{
|
||||
struct RecoupContext pc;
|
||||
const struct TEH_DenominationKey *dk;
|
||||
struct TALER_CoinPubHash c_hash;
|
||||
MHD_RESULT mret;
|
||||
|
||||
/* check denomination exists and is in recoup mode */
|
||||
@ -324,7 +200,6 @@ verify_and_execute_recoup (
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"RECOUP");
|
||||
}
|
||||
@ -334,7 +209,6 @@ verify_and_execute_recoup (
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"RECOUP");
|
||||
}
|
||||
@ -344,23 +218,21 @@ verify_and_execute_recoup (
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&coin->denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
|
||||
"RECOUP");
|
||||
}
|
||||
|
||||
pc.value = dk->meta.value;
|
||||
|
||||
/* check denomination signature */
|
||||
if (GNUNET_YES !=
|
||||
TALER_test_coin_valid (coin,
|
||||
&dk->denom_pub))
|
||||
{
|
||||
TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* check recoup request signature */
|
||||
@ -372,15 +244,17 @@ verify_and_execute_recoup (
|
||||
coin_sig))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_FORBIDDEN,
|
||||
TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
|
||||
NULL);
|
||||
}
|
||||
|
||||
{
|
||||
void *coin_ev;
|
||||
size_t coin_ev_size;
|
||||
struct TALER_CoinPubHash c_hash;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_denom_blind (&dk->denom_pub,
|
||||
@ -392,10 +266,11 @@ verify_and_execute_recoup (
|
||||
&coin_ev_size))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
|
||||
NULL);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
|
||||
NULL);
|
||||
}
|
||||
TALER_coin_ev_hash (coin_ev,
|
||||
coin_ev_size,
|
||||
@ -406,7 +281,6 @@ verify_and_execute_recoup (
|
||||
pc.coin_sig = coin_sig;
|
||||
pc.coin_bks = coin_bks;
|
||||
pc.coin = coin;
|
||||
pc.refreshed = refreshed;
|
||||
pc.requested_amount = requested_amount;
|
||||
|
||||
{
|
||||
@ -416,6 +290,7 @@ verify_and_execute_recoup (
|
||||
/* make sure coin is 'known' in database */
|
||||
qs = TEH_make_coin_known (coin,
|
||||
connection,
|
||||
&pc.known_coin_id,
|
||||
&mhd_ret);
|
||||
/* no transaction => no serialization failures should be possible */
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
|
||||
@ -426,35 +301,18 @@ verify_and_execute_recoup (
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
if (pc.refreshed)
|
||||
qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
|
||||
&pc.h_blind,
|
||||
&pc.reserve_pub,
|
||||
&pc.reserve_out_serial_id);
|
||||
if (0 > qs)
|
||||
{
|
||||
qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
|
||||
&pc.h_blind,
|
||||
&pc.target.old_coin_pub);
|
||||
if (0 > qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"old coin by h_blind");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
|
||||
&pc.h_blind,
|
||||
&pc.target.reserve_pub);
|
||||
if (0 > qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"reserve by h_blind");
|
||||
}
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"get_reserve_by_h_blind");
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
@ -483,21 +341,11 @@ verify_and_execute_recoup (
|
||||
return mhd_ret;
|
||||
}
|
||||
/* Recoup succeeded, return result */
|
||||
return (refreshed)
|
||||
? TALER_MHD_REPLY_JSON_PACK (connection,
|
||||
MHD_HTTP_OK,
|
||||
GNUNET_JSON_pack_data_auto (
|
||||
"old_coin_pub",
|
||||
&pc.target.old_coin_pub),
|
||||
GNUNET_JSON_pack_bool ("refreshed",
|
||||
true))
|
||||
: TALER_MHD_REPLY_JSON_PACK (connection,
|
||||
MHD_HTTP_OK,
|
||||
GNUNET_JSON_pack_data_auto (
|
||||
"reserve_pub",
|
||||
&pc.target.reserve_pub),
|
||||
GNUNET_JSON_pack_bool ("refreshed",
|
||||
false));
|
||||
return TALER_MHD_REPLY_JSON_PACK (connection,
|
||||
MHD_HTTP_OK,
|
||||
GNUNET_JSON_pack_data_auto (
|
||||
"reserve_pub",
|
||||
&pc.reserve_pub));
|
||||
}
|
||||
|
||||
|
||||
@ -522,7 +370,6 @@ TEH_handler_recoup (struct MHD_Connection *connection,
|
||||
union TALER_DenominationBlindingKeyP coin_bks;
|
||||
struct TALER_CoinSpendSignatureP coin_sig;
|
||||
struct TALER_Amount amount;
|
||||
bool refreshed = false;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
|
||||
&coin.denom_pub_hash),
|
||||
@ -535,12 +382,12 @@ TEH_handler_recoup (struct MHD_Connection *connection,
|
||||
TALER_JSON_spec_amount ("amount",
|
||||
TEH_currency,
|
||||
&amount),
|
||||
GNUNET_JSON_spec_mark_optional (
|
||||
GNUNET_JSON_spec_bool ("refreshed",
|
||||
&refreshed)),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
memset (&coin,
|
||||
0,
|
||||
sizeof (coin));
|
||||
coin.coin_pub = *coin_pub;
|
||||
ret = TALER_MHD_parse_json_data (connection,
|
||||
root,
|
||||
@ -556,8 +403,7 @@ TEH_handler_recoup (struct MHD_Connection *connection,
|
||||
&coin,
|
||||
&coin_bks,
|
||||
&coin_sig,
|
||||
&amount,
|
||||
refreshed);
|
||||
&amount);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return res;
|
||||
}
|
||||
|
@ -36,13 +36,6 @@
|
||||
*/
|
||||
#define MAX_FRESH_COINS 256
|
||||
|
||||
/**
|
||||
* How often do we at most retry the reveal transaction sequence?
|
||||
* Twice should really suffice in all cases (as the possible conflict
|
||||
* cannot happen more than once).
|
||||
*/
|
||||
#define MAX_REVEAL_RETRIES 2
|
||||
|
||||
|
||||
/**
|
||||
* Send a response for "/refreshes/$RCH/reveal".
|
||||
@ -53,10 +46,10 @@
|
||||
* @return a MHD result code
|
||||
*/
|
||||
static MHD_RESULT
|
||||
reply_refreshes_reveal_success (struct MHD_Connection *connection,
|
||||
unsigned int num_freshcoins,
|
||||
const struct
|
||||
TALER_BlindedDenominationSignature *sigs)
|
||||
reply_refreshes_reveal_success (
|
||||
struct MHD_Connection *connection,
|
||||
unsigned int num_freshcoins,
|
||||
const struct TALER_BlindedDenominationSignature *sigs)
|
||||
{
|
||||
json_t *list;
|
||||
|
||||
@ -120,158 +113,35 @@ struct RevealContext
|
||||
*/
|
||||
const struct TALER_RefreshCoinData *rcds;
|
||||
|
||||
/**
|
||||
* Signatures over the link data (of type
|
||||
* #TALER_SIGNATURE_WALLET_COIN_LINK)
|
||||
*/
|
||||
const struct TALER_CoinSpendSignatureP *link_sigs;
|
||||
|
||||
/**
|
||||
* Envelopes with the signatures to be returned. Initially NULL.
|
||||
*/
|
||||
struct TALER_BlindedDenominationSignature *ev_sigs;
|
||||
|
||||
/**
|
||||
* Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL).
|
||||
*/
|
||||
unsigned int num_fresh_coins;
|
||||
|
||||
/**
|
||||
* Result from preflight checks. #GNUNET_NO for no result,
|
||||
* #GNUNET_YES if preflight found previous successful operation,
|
||||
* #GNUNET_SYSERR if prefight check failed hard (and generated
|
||||
* an MHD response already).
|
||||
*/
|
||||
int preflight_ok;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function called with information about a refresh order we already
|
||||
* persisted. Stores the result in @a cls so we don't do the calculation
|
||||
* again.
|
||||
*
|
||||
* @param cls closure with a `struct RevealContext`
|
||||
* @param num_freshcoins size of the @a rrcs array
|
||||
* @param rrcs array of @a num_freshcoins information about coins to be created
|
||||
* @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
|
||||
* @param tprivs array of @e num_tprivs transfer private keys
|
||||
* @param tp transfer public key information
|
||||
*/
|
||||
static void
|
||||
check_exists_cb (void *cls,
|
||||
uint32_t num_freshcoins,
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
|
||||
unsigned int num_tprivs,
|
||||
const struct TALER_TransferPrivateKeyP *tprivs,
|
||||
const struct TALER_TransferPublicKeyP *tp)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
|
||||
if (0 == num_freshcoins)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return;
|
||||
}
|
||||
/* This should be a database invariant for us */
|
||||
GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs);
|
||||
/* Given that the $RCH value matched, we don't actually need to check these
|
||||
values (we checked before). However, if a client repeats a request with
|
||||
invalid values the 2nd time, that's a protocol violation we should at least
|
||||
log (but it's safe to ignore it). */
|
||||
GNUNET_break_op (0 ==
|
||||
GNUNET_memcmp (tp,
|
||||
&rctx->gamma_tp));
|
||||
GNUNET_break_op (0 ==
|
||||
memcmp (tprivs,
|
||||
&rctx->transfer_privs,
|
||||
sizeof (struct TALER_TransferPrivateKeyP)
|
||||
* num_tprivs));
|
||||
/* We usually sign early (optimistic!), but in case we change that *and*
|
||||
we do find the operation in the database, we could use this: */
|
||||
if (NULL == rctx->ev_sigs)
|
||||
{
|
||||
rctx->ev_sigs = GNUNET_new_array (num_freshcoins,
|
||||
struct TALER_BlindedDenominationSignature);
|
||||
for (unsigned int i = 0; i<num_freshcoins; i++)
|
||||
TALER_blinded_denom_sig_deep_copy (&rctx->ev_sigs[i],
|
||||
&rrcs[i].coin_sig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the "/refreshes/$RCH/reveal" request was already successful
|
||||
* before. If so, just return the old result.
|
||||
*
|
||||
* @param cls closure of type `struct RevealContext`
|
||||
* @param connection MHD request which triggered the transaction
|
||||
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||
* if transaction failed (!)
|
||||
* @return transaction status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
refreshes_reveal_preflight (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* Try to see if we already have given an answer before. */
|
||||
qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls,
|
||||
&rctx->rc,
|
||||
&check_exists_cb,
|
||||
rctx);
|
||||
switch (qs)
|
||||
{
|
||||
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
|
||||
return qs; /* continue normal execution */
|
||||
case GNUNET_DB_STATUS_SOFT_ERROR:
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_HARD_ERROR:
|
||||
GNUNET_break (qs);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"refresh reveal");
|
||||
rctx->preflight_ok = GNUNET_SYSERR;
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
|
||||
default:
|
||||
/* Hossa, already found our reply! */
|
||||
GNUNET_assert (NULL != rctx->ev_sigs);
|
||||
rctx->preflight_ok = GNUNET_YES;
|
||||
return qs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a "/refreshes/$RCH/reveal". The client is revealing to us the
|
||||
* Check client's revelation against the original commitment.
|
||||
* The client is revealing to us the
|
||||
* transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the
|
||||
* revealed transfer keys would allow linkage to the blinded coins.
|
||||
*
|
||||
* IF it returns a non-error code, the transaction logic MUST
|
||||
* NOT queue a MHD response. IF it returns an hard error, the
|
||||
* transaction logic MUST queue a MHD response and set @a mhd_ret. IF
|
||||
* it returns the soft error code, the function MAY be called again to
|
||||
* retry and MUST not queue a MHD response.
|
||||
* IF it returns #GNUNET_OK, the transaction logic MUST
|
||||
* NOT queue a MHD response. IF it returns an error, the
|
||||
* transaction logic MUST queue a MHD response and set @a mhd_ret.
|
||||
*
|
||||
* @param cls closure of type `struct RevealContext`
|
||||
* @param rctx our operation context
|
||||
* @param connection MHD request which triggered the transaction
|
||||
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||
* if transaction failed (!)
|
||||
* @return transaction status
|
||||
* @return #GNUNET_OK if commitment was OK
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
refreshes_reveal_transaction (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
static enum GNUNET_GenericReturnValue
|
||||
check_commitment (struct RevealContext *rctx,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
|
||||
/* Verify commitment */
|
||||
{
|
||||
/* Note that the contents of rcs[melt.session.noreveal_index]
|
||||
@ -363,7 +233,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
TALER_EC_EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION),
|
||||
GNUNET_JSON_pack_data_auto ("rc_expected",
|
||||
&rc_expected));
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
} /* end of checking "rc_expected" */
|
||||
|
||||
@ -390,7 +260,7 @@ refreshes_reveal_transaction (void *cls,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
if (0 < TALER_amount_cmp (&refresh_cost,
|
||||
@ -401,60 +271,10 @@ refreshes_reveal_transaction (void *cls,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
TALER_EC_EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Persist result of a "/refreshes/$RCH/reveal" operation.
|
||||
*
|
||||
* @param cls closure of type `struct RevealContext`
|
||||
* @param connection MHD request which triggered the transaction
|
||||
* @param[out] mhd_ret set to MHD response status for @a connection,
|
||||
* if transaction failed (!)
|
||||
* @return transaction status
|
||||
*/
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
refreshes_reveal_persist (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
struct RevealContext *rctx = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/* Persist operation result in DB */
|
||||
{
|
||||
struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins];
|
||||
|
||||
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
|
||||
{
|
||||
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
|
||||
|
||||
rrc->denom_pub = rctx->dks[i]->denom_pub;
|
||||
rrc->orig_coin_link_sig = rctx->link_sigs[i];
|
||||
rrc->coin_ev = rctx->rcds[i].coin_ev;
|
||||
rrc->coin_ev_size = rctx->rcds[i].coin_ev_size;
|
||||
rrc->coin_sig = rctx->ev_sigs[i];
|
||||
}
|
||||
qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls,
|
||||
&rctx->rc,
|
||||
rctx->num_fresh_coins,
|
||||
rrcs,
|
||||
TALER_CNC_KAPPA - 1,
|
||||
rctx->transfer_privs,
|
||||
&rctx->gamma_tp);
|
||||
}
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"refresh_reveal");
|
||||
}
|
||||
return qs;
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -481,9 +301,18 @@ 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];
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
struct TALER_BlindedDenominationSignature ev_sigs[num_fresh_coins];
|
||||
MHD_RESULT ret;
|
||||
struct TEH_KeyStateHandle *ksh;
|
||||
uint64_t melt_serial_id;
|
||||
|
||||
rctx->num_fresh_coins = num_fresh_coins;
|
||||
memset (dks, 0, sizeof (dks));
|
||||
memset (rcds, 0, sizeof (rcds));
|
||||
memset (link_sigs, 0, sizeof (link_sigs));
|
||||
memset (ev_sigs, 0, sizeof (ev_sigs));
|
||||
rctx->dks = dks;
|
||||
rctx->rcds = rcds;
|
||||
|
||||
ksh = TEH_keys_get_state ();
|
||||
if (NULL == ksh)
|
||||
@ -501,7 +330,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
&dk_h[i]),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
MHD_RESULT mret;
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
new_denoms_h_json,
|
||||
@ -509,15 +338,13 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
i,
|
||||
-1);
|
||||
if (GNUNET_OK != res)
|
||||
{
|
||||
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
|
||||
}
|
||||
dks[i] = TEH_keys_denomination_by_hash2 (ksh,
|
||||
&dk_h[i],
|
||||
connection,
|
||||
&mret);
|
||||
&ret);
|
||||
if (NULL == dks[i])
|
||||
return mret;
|
||||
return ret;
|
||||
|
||||
if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time))
|
||||
{
|
||||
@ -525,7 +352,6 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&dk_h[i],
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"REVEAL");
|
||||
}
|
||||
@ -535,7 +361,6 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
connection,
|
||||
&dk_h[i],
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"REVEAL");
|
||||
}
|
||||
@ -560,6 +385,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
&rcd->coin_ev_size),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
coin_evs,
|
||||
@ -582,7 +408,8 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
|
||||
&rctx->rc,
|
||||
&rctx->melt)))
|
||||
&rctx->melt,
|
||||
&melt_serial_id)))
|
||||
{
|
||||
switch (qs)
|
||||
{
|
||||
@ -609,8 +436,6 @@ 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);
|
||||
@ -625,9 +450,11 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
||||
{
|
||||
struct GNUNET_JSON_Specification link_spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
|
||||
GNUNET_JSON_spec_fixed_auto (NULL,
|
||||
&link_sigs[i]),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
|
||||
res = TALER_MHD_parse_json_array (connection,
|
||||
link_sigs_json,
|
||||
@ -656,27 +483,26 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
}
|
||||
}
|
||||
|
||||
rctx->num_fresh_coins = num_fresh_coins;
|
||||
rctx->rcds = rcds;
|
||||
rctx->dks = dks;
|
||||
rctx->link_sigs = link_sigs;
|
||||
if (GNUNET_OK !=
|
||||
check_commitment (rctx,
|
||||
connection,
|
||||
&ret))
|
||||
goto cleanup;
|
||||
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||
"Optimistically creating %u signatures\n",
|
||||
"Creating %u signatures\n",
|
||||
(unsigned int) rctx->num_fresh_coins);
|
||||
/* sign _early_ (optimistic!) to keep out of transaction scope! */
|
||||
rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
|
||||
struct TALER_BlindedDenominationSignature);
|
||||
// FIXME: this is sequential, modify logic to enable parallel signing!
|
||||
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
|
||||
{
|
||||
enum TALER_ErrorCode ec = TALER_EC_NONE;
|
||||
|
||||
rctx->ev_sigs[i]
|
||||
ev_sigs[i]
|
||||
= TEH_keys_denomination_sign (
|
||||
&dk_h[i],
|
||||
rctx->rcds[i].coin_ev,
|
||||
rctx->rcds[i].coin_ev_size,
|
||||
rcds[i].coin_ev,
|
||||
rcds[i].coin_ev_size,
|
||||
&ec);
|
||||
if (TALER_EC_NONE != ec)
|
||||
{
|
||||
@ -687,81 +513,50 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||
"Signatures ready, starting DB interaction\n");
|
||||
/* We try the three transactions a few times, as theoretically
|
||||
the pre-check might be satisfied by a concurrent transaction
|
||||
voiding our final commit due to uniqueness violation; naturally,
|
||||
on hard errors we exit immediately */
|
||||
for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++)
|
||||
/* Persist operation result in DB */
|
||||
{
|
||||
/* do transactional work */
|
||||
rctx->preflight_ok = GNUNET_NO;
|
||||
if ( (GNUNET_OK ==
|
||||
TEH_DB_run_transaction (connection,
|
||||
"reveal pre-check",
|
||||
TEH_MT_REVEAL_PRECHECK,
|
||||
&ret,
|
||||
&refreshes_reveal_preflight,
|
||||
rctx)) &&
|
||||
(GNUNET_YES == rctx->preflight_ok) )
|
||||
struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins];
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
|
||||
{
|
||||
/* Generate final (positive) response */
|
||||
GNUNET_assert (NULL != rctx->ev_sigs);
|
||||
ret = reply_refreshes_reveal_success (connection,
|
||||
num_fresh_coins,
|
||||
rctx->ev_sigs);
|
||||
GNUNET_break (MHD_NO != ret);
|
||||
goto cleanup; /* aka 'break' */
|
||||
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
|
||||
|
||||
rrc->h_denom_pub = dk_h[i];
|
||||
rrc->orig_coin_link_sig = link_sigs[i];
|
||||
rrc->coin_ev = rcds[i].coin_ev;
|
||||
rrc->coin_ev_size = rcds[i].coin_ev_size;
|
||||
rrc->coin_sig = ev_sigs[i];
|
||||
}
|
||||
if (GNUNET_SYSERR == rctx->preflight_ok)
|
||||
qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls,
|
||||
melt_serial_id,
|
||||
num_fresh_coins,
|
||||
rrcs,
|
||||
TALER_CNC_KAPPA - 1,
|
||||
rctx->transfer_privs,
|
||||
&rctx->gamma_tp);
|
||||
if (0 > qs)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
goto cleanup; /* aka 'break' */
|
||||
ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"insert_refresh_reveal");
|
||||
goto cleanup;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_run_transaction (connection,
|
||||
"run reveal",
|
||||
TEH_MT_REVEAL,
|
||||
&ret,
|
||||
&refreshes_reveal_transaction,
|
||||
rctx))
|
||||
{
|
||||
/* reveal failed, too bad */
|
||||
GNUNET_break_op (0);
|
||||
goto cleanup; /* aka 'break' */
|
||||
}
|
||||
if (GNUNET_OK ==
|
||||
TEH_DB_run_transaction (connection,
|
||||
"persist reveal",
|
||||
TEH_MT_REVEAL_PERSIST,
|
||||
&ret,
|
||||
&refreshes_reveal_persist,
|
||||
rctx))
|
||||
{
|
||||
/* Generate final (positive) response */
|
||||
GNUNET_assert (NULL != rctx->ev_sigs);
|
||||
ret = reply_refreshes_reveal_success (connection,
|
||||
num_fresh_coins,
|
||||
rctx->ev_sigs);
|
||||
break;
|
||||
}
|
||||
/* If we get here, the final transaction failed, possibly
|
||||
due to a conflict between the pre-flight and us persisting
|
||||
the result, so we go again. */
|
||||
} /* end for (retries...) */
|
||||
}
|
||||
|
||||
/* Generate final (positive) response */
|
||||
ret = reply_refreshes_reveal_success (connection,
|
||||
num_fresh_coins,
|
||||
ev_sigs);
|
||||
cleanup:
|
||||
GNUNET_break (MHD_NO != ret);
|
||||
/* free resources */
|
||||
if (NULL != rctx->ev_sigs)
|
||||
{
|
||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
||||
TALER_blinded_denom_sig_free (&rctx->ev_sigs[i]);
|
||||
GNUNET_free (rctx->ev_sigs);
|
||||
rctx->ev_sigs = NULL; /* just to be safe... */
|
||||
}
|
||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
||||
TALER_blinded_denom_sig_free (&ev_sigs[i]);
|
||||
for (unsigned int i = 0; i<num_fresh_coins; i++)
|
||||
GNUNET_free (rcds[i].coin_ev);
|
||||
return ret;
|
||||
@ -828,7 +623,8 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
|
||||
for (unsigned int i = 0; i<num_tprivs; i++)
|
||||
{
|
||||
struct GNUNET_JSON_Specification trans_spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto (NULL, &rctx->transfer_privs[i]),
|
||||
GNUNET_JSON_spec_fixed_auto (NULL,
|
||||
&rctx->transfer_privs[i]),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
enum GNUNET_GenericReturnValue res;
|
||||
@ -861,11 +657,16 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
|
||||
json_t *new_denoms_h;
|
||||
struct RevealContext rctx;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp),
|
||||
GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
|
||||
GNUNET_JSON_spec_json ("link_sigs", &link_sigs),
|
||||
GNUNET_JSON_spec_json ("coin_evs", &coin_evs),
|
||||
GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h),
|
||||
GNUNET_JSON_spec_fixed_auto ("transfer_pub",
|
||||
&rctx.gamma_tp),
|
||||
GNUNET_JSON_spec_json ("transfer_privs",
|
||||
&transfer_privs),
|
||||
GNUNET_JSON_spec_json ("link_sigs",
|
||||
&link_sigs),
|
||||
GNUNET_JSON_spec_json ("coin_evs",
|
||||
&coin_evs),
|
||||
GNUNET_JSON_spec_json ("new_denoms_h",
|
||||
&new_denoms_h),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
|
@ -81,6 +81,28 @@ reply_refund_success (struct MHD_Connection *connection,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closure for refund_transaction().
|
||||
*/
|
||||
struct RefundContext
|
||||
{
|
||||
/**
|
||||
* Details about the deposit operation.
|
||||
*/
|
||||
const struct TALER_EXCHANGEDB_Refund *refund;
|
||||
|
||||
/**
|
||||
* Deposit fee of the coin.
|
||||
*/
|
||||
struct TALER_Amount deposit_fee;
|
||||
|
||||
/**
|
||||
* Unique ID of the coin in known_coins.
|
||||
*/
|
||||
uint64_t known_coin_id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute a "/refund" transaction. Returns a confirmation that the
|
||||
* refund was successful, or a failure if we are not aware of a
|
||||
@ -103,255 +125,67 @@ refund_transaction (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
MHD_RESULT *mhd_ret)
|
||||
{
|
||||
const struct TALER_EXCHANGEDB_Refund *refund = cls;
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl; /* head of original list */
|
||||
struct TALER_EXCHANGEDB_TransactionList *tlx; /* head of sublist that applies to merchant and contract */
|
||||
struct TALER_EXCHANGEDB_TransactionList *tln; /* next element, during iteration */
|
||||
struct TALER_EXCHANGEDB_TransactionList *tlp; /* previous element in 'tl' list, during iteration */
|
||||
struct RefundContext *rctx = cls;
|
||||
const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
bool deposit_found; /* deposit_total initialized? */
|
||||
bool refund_found; /* refund_total initialized? */
|
||||
struct TALER_Amount deposit_total;
|
||||
struct TALER_Amount refund_total;
|
||||
bool not_found;
|
||||
bool refund_ok;
|
||||
bool conflict;
|
||||
bool gone;
|
||||
|
||||
tl = NULL;
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
&refund->coin.coin_pub,
|
||||
GNUNET_NO,
|
||||
&tl);
|
||||
/* Finally, store new refund data */
|
||||
qs = TEH_plugin->do_refund (TEH_plugin->cls,
|
||||
refund,
|
||||
&rctx->deposit_fee,
|
||||
rctx->known_coin_id,
|
||||
¬_found,
|
||||
&refund_ok,
|
||||
&gone,
|
||||
&conflict);
|
||||
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 transactions");
|
||||
"do refund");
|
||||
return qs;
|
||||
}
|
||||
deposit_found = false;
|
||||
refund_found = false;
|
||||
tlx = NULL; /* relevant subset of transactions */
|
||||
tln = NULL;
|
||||
tlp = NULL;
|
||||
for (struct TALER_EXCHANGEDB_TransactionList *tli = tl;
|
||||
NULL != tli;
|
||||
tli = tln)
|
||||
|
||||
if (gone)
|
||||
{
|
||||
tln = tli->next;
|
||||
switch (tli->type)
|
||||
{
|
||||
case TALER_EXCHANGEDB_TT_DEPOSIT:
|
||||
{
|
||||
const struct TALER_EXCHANGEDB_DepositListEntry *dep;
|
||||
|
||||
dep = tli->details.deposit;
|
||||
if ( (0 == GNUNET_memcmp (&dep->merchant_pub,
|
||||
&refund->details.merchant_pub)) &&
|
||||
(0 == GNUNET_memcmp (&dep->h_contract_terms,
|
||||
&refund->details.h_contract_terms)) )
|
||||
{
|
||||
/* check if we already send the money for this /deposit */
|
||||
if (dep->done)
|
||||
{
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tln);
|
||||
/* money was already transferred to merchant, can no longer refund */
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_GONE,
|
||||
TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* deposit applies and was not yet wired; add to total (it is NOT
|
||||
the case that multiple deposits of the same coin for the same
|
||||
contract are really allowed (see UNIQUE constraint on 'deposits'
|
||||
table), but in case this changes we tolerate it with this code
|
||||
anyway). *///
|
||||
if (deposit_found)
|
||||
{
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_add (&deposit_total,
|
||||
&deposit_total,
|
||||
&dep->amount_with_fee));
|
||||
}
|
||||
else
|
||||
{
|
||||
deposit_total = dep->amount_with_fee;
|
||||
deposit_found = true;
|
||||
}
|
||||
/* move 'tli' from 'tl' to 'tlx' list */
|
||||
if (NULL == tlp)
|
||||
tl = tln;
|
||||
else
|
||||
tlp->next = tln;
|
||||
tli->next = tlx;
|
||||
tlx = tli;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
tlp = tli;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TALER_EXCHANGEDB_TT_MELT:
|
||||
/* Melts cannot be refunded, ignore here */
|
||||
break;
|
||||
case TALER_EXCHANGEDB_TT_REFUND:
|
||||
{
|
||||
const struct TALER_EXCHANGEDB_RefundListEntry *ref;
|
||||
|
||||
ref = tli->details.refund;
|
||||
if ( (0 != GNUNET_memcmp (&ref->merchant_pub,
|
||||
&refund->details.merchant_pub)) ||
|
||||
(0 != GNUNET_memcmp (&ref->h_contract_terms,
|
||||
&refund->details.h_contract_terms)) )
|
||||
{
|
||||
tlp = tli;
|
||||
break; /* refund does not apply to our transaction */
|
||||
}
|
||||
/* Check if existing refund request matches in everything but the amount */
|
||||
if ( (ref->rtransaction_id ==
|
||||
refund->details.rtransaction_id) &&
|
||||
(0 != TALER_amount_cmp (&ref->refund_amount,
|
||||
&refund->details.refund_amount)) )
|
||||
{
|
||||
/* Generate precondition failed response, with ONLY the conflicting entry */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tln);
|
||||
tli->next = NULL;
|
||||
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_PRECONDITION_FAILED,
|
||||
TALER_JSON_pack_amount ("detail",
|
||||
&ref->refund_amount),
|
||||
TALER_JSON_pack_ec (TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT),
|
||||
GNUNET_JSON_pack_array_steal ("history",
|
||||
TEH_RESPONSE_compile_transaction_history (
|
||||
&refund->coin.coin_pub,
|
||||
tli)));
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tli);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
/* Check if existing refund request matches in everything including the amount */
|
||||
if ( (ref->rtransaction_id ==
|
||||
refund->details.rtransaction_id) &&
|
||||
(0 == TALER_amount_cmp (&ref->refund_amount,
|
||||
&refund->details.refund_amount)) )
|
||||
{
|
||||
/* we can blanketly approve, as this request is identical to one
|
||||
we saw before */
|
||||
*mhd_ret = reply_refund_success (connection,
|
||||
&refund->coin.coin_pub,
|
||||
ref);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
/* we still abort the transaction, as there is nothing to be
|
||||
committed! */
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* We have another refund, that relates, add to total */
|
||||
if (refund_found)
|
||||
{
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_add (&refund_total,
|
||||
&refund_total,
|
||||
&ref->refund_amount));
|
||||
}
|
||||
else
|
||||
{
|
||||
refund_total = ref->refund_amount;
|
||||
refund_found = true;
|
||||
}
|
||||
/* move 'tli' from 'tl' to 'tlx' list */
|
||||
if (NULL == tlp)
|
||||
tl = tln;
|
||||
else
|
||||
tlp->next = tln;
|
||||
tli->next = tlx;
|
||||
tlx = tli;
|
||||
break;
|
||||
}
|
||||
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
|
||||
/* Recoups cannot be refunded, ignore here */
|
||||
break;
|
||||
case TALER_EXCHANGEDB_TT_RECOUP:
|
||||
/* Recoups cannot be refunded, ignore here */
|
||||
break;
|
||||
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
|
||||
/* Recoups cannot be refunded, ignore here */
|
||||
break;
|
||||
}
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_GONE,
|
||||
TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
/* no need for 'tl' anymore, everything we may still care about is in tlx now */
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
/* handle if deposit was NOT found */
|
||||
if (! deposit_found)
|
||||
if (conflict)
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
|
||||
&refund->coin.coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (not_found)
|
||||
{
|
||||
TALER_LOG_WARNING ("Deposit to /refund was not found\n");
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
|
||||
/* check total refund amount is sufficiently low */
|
||||
if (refund_found)
|
||||
GNUNET_break (0 <=
|
||||
TALER_amount_add (&refund_total,
|
||||
&refund_total,
|
||||
&refund->details.refund_amount));
|
||||
else
|
||||
refund_total = refund->details.refund_amount;
|
||||
|
||||
if (1 == TALER_amount_cmp (&refund_total,
|
||||
&deposit_total) )
|
||||
if (! refund_ok)
|
||||
{
|
||||
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
GNUNET_JSON_pack_string ("detail",
|
||||
"total amount refunded exceeds total amount deposited for this coin"),
|
||||
TALER_JSON_pack_ec (
|
||||
TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT),
|
||||
GNUNET_JSON_pack_array_steal ("history",
|
||||
TEH_RESPONSE_compile_transaction_history (
|
||||
&refund->coin.coin_pub,
|
||||
tlx)));
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
|
||||
&refund->coin.coin_pub);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tlx);
|
||||
|
||||
|
||||
/* Finally, store new refund data */
|
||||
qs = TEH_plugin->insert_refund (TEH_plugin->cls,
|
||||
refund);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
{
|
||||
TALER_LOG_WARNING ("Failed to store /refund information in database\n");
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"refund");
|
||||
return qs;
|
||||
}
|
||||
/* Success or soft failure */
|
||||
return qs;
|
||||
}
|
||||
|
||||
@ -371,7 +205,11 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
||||
struct TALER_EXCHANGEDB_Refund *refund)
|
||||
{
|
||||
struct TALER_DenominationHash denom_hash;
|
||||
struct RefundContext rctx = {
|
||||
.refund = refund
|
||||
};
|
||||
|
||||
// FIXME: move to libtalerutil!
|
||||
{
|
||||
struct TALER_RefundRequestPS rr = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
|
||||
@ -404,6 +242,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
||||
|
||||
qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
|
||||
&refund->coin.coin_pub,
|
||||
&rctx.known_coin_id,
|
||||
&denom_hash);
|
||||
if (0 > qs)
|
||||
{
|
||||
@ -438,6 +277,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
||||
return mret;
|
||||
}
|
||||
refund->details.refund_fee = dk->meta.fee_refund;
|
||||
rctx.deposit_fee = dk->meta.fee_deposit;
|
||||
}
|
||||
|
||||
/* Finally run the actual transaction logic */
|
||||
@ -450,7 +290,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
|
||||
TEH_MT_OTHER,
|
||||
&mhd_ret,
|
||||
&refund_transaction,
|
||||
(void *) refund))
|
||||
&rctx))
|
||||
{
|
||||
return mhd_ret;
|
||||
}
|
||||
|
@ -122,25 +122,15 @@ TEH_RESPONSE_compile_transaction_history (
|
||||
{
|
||||
const struct TALER_EXCHANGEDB_MeltListEntry *melt =
|
||||
pos->details.melt;
|
||||
struct TALER_RefreshMeltCoinAffirmationPS ms = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
|
||||
.purpose.size = htonl (sizeof (ms)),
|
||||
.rc = melt->rc,
|
||||
.h_denom_pub = melt->h_denom_pub,
|
||||
.coin_pub = *coin_pub
|
||||
};
|
||||
|
||||
TALER_amount_hton (&ms.amount_with_fee,
|
||||
&melt->amount_with_fee);
|
||||
TALER_amount_hton (&ms.melt_fee,
|
||||
&melt->melt_fee);
|
||||
#if ENABLE_SANITY_CHECKS
|
||||
/* internal sanity check before we hand out a bogus sig... */
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
|
||||
&ms,
|
||||
&melt->coin_sig.eddsa_signature,
|
||||
&coin_pub->eddsa_pub))
|
||||
TALER_wallet_melt_verify (&melt->amount_with_fee,
|
||||
&melt->melt_fee,
|
||||
&melt->rc,
|
||||
&melt->h_denom_pub,
|
||||
coin_pub,
|
||||
&melt->coin_sig))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
json_decref (history);
|
||||
@ -175,6 +165,7 @@ TEH_RESPONSE_compile_transaction_history (
|
||||
const struct TALER_EXCHANGEDB_RefundListEntry *refund =
|
||||
pos->details.refund;
|
||||
struct TALER_Amount value;
|
||||
// FIXME: move to libtalerutil!
|
||||
struct TALER_RefundRequestPS rr = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
|
||||
.purpose.size = htonl (sizeof (rr)),
|
||||
@ -461,13 +452,14 @@ MHD_RESULT
|
||||
TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
struct MHD_Connection *connection,
|
||||
const struct TALER_DenominationHash *dph,
|
||||
struct GNUNET_TIME_Timestamp now,
|
||||
enum TALER_ErrorCode ec,
|
||||
const char *oper)
|
||||
{
|
||||
struct TALER_ExchangePublicKeyP epub;
|
||||
struct TALER_ExchangeSignatureP esig;
|
||||
enum TALER_ErrorCode ecr;
|
||||
struct GNUNET_TIME_Timestamp now
|
||||
= GNUNET_TIME_timestamp_get ();
|
||||
struct TALER_DenominationExpiredAffirmationPS dua = {
|
||||
.purpose.size = htonl (sizeof (dua)),
|
||||
.purpose.purpose = htonl (
|
||||
@ -525,13 +517,31 @@ MHD_RESULT
|
||||
TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
struct MHD_Connection *connection,
|
||||
enum TALER_ErrorCode ec,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_EXCHANGEDB_TransactionList *tl)
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub)
|
||||
{
|
||||
struct TALER_EXCHANGEDB_TransactionList *tl;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
json_t *history;
|
||||
|
||||
// FIXME: maybe start read-committed transaction here?
|
||||
// => check all callers (that they aborted already!)
|
||||
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
|
||||
coin_pub,
|
||||
GNUNET_NO,
|
||||
&tl);
|
||||
if (0 > qs)
|
||||
{
|
||||
return TALER_MHD_reply_with_error (
|
||||
connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
NULL);
|
||||
}
|
||||
|
||||
history = TEH_RESPONSE_compile_transaction_history (coin_pub,
|
||||
tl);
|
||||
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
|
||||
tl);
|
||||
if (NULL == history)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
@ -542,7 +552,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
}
|
||||
return TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_CONFLICT,
|
||||
TALER_ErrorCode_get_http_status_safe (ec),
|
||||
TALER_JSON_pack_ec (ec),
|
||||
GNUNET_JSON_pack_array_steal ("history",
|
||||
history));
|
||||
|
@ -67,7 +67,6 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash (
|
||||
*
|
||||
* @param connection connection to the client
|
||||
* @param dph denomination public key hash
|
||||
* @param now timestamp to use
|
||||
* @param ec error code to use
|
||||
* @param oper name of the operation that is not allowed at this time
|
||||
* @return MHD result code
|
||||
@ -76,7 +75,6 @@ MHD_RESULT
|
||||
TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
struct MHD_Connection *connection,
|
||||
const struct TALER_DenominationHash *dph,
|
||||
struct GNUNET_TIME_Timestamp now,
|
||||
enum TALER_ErrorCode ec,
|
||||
const char *oper);
|
||||
|
||||
@ -90,15 +88,13 @@ TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
* @param connection connection to the client
|
||||
* @param ec error code to return
|
||||
* @param coin_pub public key of the coin
|
||||
* @param tl transaction list to use to build reply
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_RESPONSE_reply_coin_insufficient_funds (
|
||||
struct MHD_Connection *connection,
|
||||
enum TALER_ErrorCode ec,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_EXCHANGEDB_TransactionList *tl);
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -94,7 +94,7 @@ struct WithdrawContext
|
||||
/**
|
||||
* Blinded planchet.
|
||||
*/
|
||||
char *blinded_msg;
|
||||
void *blinded_msg;
|
||||
|
||||
/**
|
||||
* Number of bytes in @e blinded_msg.
|
||||
@ -141,6 +141,7 @@ withdraw_transaction (void *cls,
|
||||
bool found = false;
|
||||
bool balance_ok = false;
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
uint64_t ruuid;
|
||||
|
||||
now = GNUNET_TIME_timestamp_get ();
|
||||
wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
|
||||
@ -150,7 +151,8 @@ withdraw_transaction (void *cls,
|
||||
now,
|
||||
&found,
|
||||
&balance_ok,
|
||||
&wc->kyc);
|
||||
&wc->kyc,
|
||||
&ruuid);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
@ -174,6 +176,7 @@ withdraw_transaction (void *cls,
|
||||
struct TALER_Amount balance;
|
||||
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
// FIXME: maybe start read-committed here?
|
||||
if (GNUNET_OK !=
|
||||
TEH_plugin->start (TEH_plugin->cls,
|
||||
"get_reserve_history on insufficient balance"))
|
||||
@ -232,7 +235,7 @@ withdraw_transaction (void *cls,
|
||||
|
||||
qs2 = TEH_plugin->do_withdraw_limit_check (
|
||||
TEH_plugin->cls,
|
||||
&wc->collectable.reserve_pub,
|
||||
ruuid,
|
||||
GNUNET_TIME_absolute_subtract (now.abs_time,
|
||||
TEH_kyc_config.withdraw_period),
|
||||
&TEH_kyc_config.withdraw_limit,
|
||||
@ -249,6 +252,7 @@ withdraw_transaction (void *cls,
|
||||
}
|
||||
if (! below_limit)
|
||||
{
|
||||
TEH_plugin->rollback (TEH_plugin->cls);
|
||||
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_ACCEPTED,
|
||||
@ -313,7 +317,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
struct WithdrawContext wc;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_varsize ("coin_ev",
|
||||
(void **) &wc.blinded_msg,
|
||||
&wc.blinded_msg,
|
||||
&wc.blinded_msg_len),
|
||||
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
|
||||
&wc.collectable.reserve_sig),
|
||||
@ -398,7 +402,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
rc->connection,
|
||||
&wc.collectable.denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
|
||||
"WITHDRAW");
|
||||
}
|
||||
@ -413,7 +416,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
rc->connection,
|
||||
&wc.collectable.denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
|
||||
"WITHDRAW");
|
||||
}
|
||||
@ -428,7 +430,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
rc->connection,
|
||||
&wc.collectable.denom_pub_hash,
|
||||
GNUNET_TIME_timestamp_get (),
|
||||
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
|
||||
"WITHDRAW");
|
||||
}
|
||||
@ -437,22 +438,21 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
}
|
||||
}
|
||||
|
||||
if (0 >
|
||||
TALER_amount_add (&wc.collectable.amount_with_fee,
|
||||
&dk->meta.value,
|
||||
&dk->meta.fee_withdraw))
|
||||
{
|
||||
if (0 >
|
||||
TALER_amount_add (&wc.collectable.amount_with_fee,
|
||||
&dk->meta.value,
|
||||
&dk->meta.fee_withdraw))
|
||||
{
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (rc->connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
|
||||
NULL);
|
||||
}
|
||||
TALER_amount_hton (&wc.wsrd.amount_with_fee,
|
||||
&wc.collectable.amount_with_fee);
|
||||
GNUNET_JSON_parse_free (spec);
|
||||
return TALER_MHD_reply_with_error (rc->connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
|
||||
NULL);
|
||||
}
|
||||
TALER_amount_hton (&wc.wsrd.amount_with_fee,
|
||||
&wc.collectable.amount_with_fee);
|
||||
|
||||
// FIXME: move this logic into libtalerutil!
|
||||
/* verify signature! */
|
||||
wc.wsrd.purpose.size
|
||||
= htonl (sizeof (wc.wsrd));
|
||||
@ -495,7 +495,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* run transaction and sign (if not optimistically signed before) */
|
||||
/* run transaction */
|
||||
{
|
||||
MHD_RESULT mhd_ret;
|
||||
|
||||
|
@ -54,10 +54,21 @@ DROP TABLE IF EXISTS reserves CASCADE;
|
||||
DROP TABLE IF EXISTS denomination_revocations CASCADE;
|
||||
DROP TABLE IF EXISTS denominations CASCADE;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_withdraw(bigint,integer,bytea,bytea,bytea,bytea,bytea,bigint,bigint) ;
|
||||
DROP FUNCTION IF EXISTS exchange_do_withdraw(bigint,int,bytea,bytea,bytea,bytea,bytea,bigint,bigint) ;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_withdraw_limit_check(bigint,bigint,bigint,int) ;
|
||||
DROP FUNCTION IF EXISTS exchange_do_withdraw_limit_check(bytea,bigint,bigint,int) ;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_deposit;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_melt;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_refund;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_recoup_to_coin;
|
||||
|
||||
DROP FUNCTION IF EXISTS exchange_do_recoup_to_reserve;
|
||||
|
||||
-- FIXME: drop other stored functions!
|
||||
|
||||
-- And we're out of here...
|
||||
COMMIT;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -233,8 +233,8 @@ irbt_cb_table_reserves_out (struct PostgresClosure *pg,
|
||||
&td->details.reserves_out.denominations_serial),
|
||||
TALER_PQ_query_param_blinded_denom_sig (
|
||||
&td->details.reserves_out.denom_sig),
|
||||
GNUNET_PQ_query_param_auto_from_type (
|
||||
&td->details.reserves_out.reserve_pub),
|
||||
GNUNET_PQ_query_param_uint64 (
|
||||
&td->details.reserves_out.reserve_uuid),
|
||||
GNUNET_PQ_query_param_auto_from_type (
|
||||
&td->details.reserves_out.reserve_sig),
|
||||
GNUNET_PQ_query_param_timestamp (
|
||||
@ -404,8 +404,8 @@ irbt_cb_table_refresh_commitments (struct PostgresClosure *pg,
|
||||
&td->details.refresh_commitments.amount_with_fee),
|
||||
GNUNET_PQ_query_param_uint32 (
|
||||
&td->details.refresh_commitments.noreveal_index),
|
||||
GNUNET_PQ_query_param_uint64 (
|
||||
&td->details.refresh_commitments.old_known_coin_id),
|
||||
GNUNET_PQ_query_param_auto_from_type (
|
||||
&td->details.refresh_commitments.old_coin_pub),
|
||||
GNUNET_PQ_query_param_end
|
||||
};
|
||||
|
||||
|
@ -408,9 +408,9 @@ lrbt_cb_table_reserves_out (void *cls,
|
||||
TALER_PQ_result_spec_blinded_denom_sig (
|
||||
"denom_sig",
|
||||
&td.details.reserves_out.denom_sig),
|
||||
GNUNET_PQ_result_spec_auto_from_type (
|
||||
"reserve_pub",
|
||||
&td.details.reserves_out.reserve_pub),
|
||||
GNUNET_PQ_result_spec_uint64 (
|
||||
"reserve_uuid",
|
||||
&td.details.reserves_out.reserve_uuid),
|
||||
GNUNET_PQ_result_spec_auto_from_type (
|
||||
"reserve_sig",
|
||||
&td.details.reserves_out.reserve_sig),
|
||||
@ -732,9 +732,9 @@ lrbt_cb_table_refresh_commitments (void *cls,
|
||||
GNUNET_PQ_result_spec_uint32 (
|
||||
"noreveal_index",
|
||||
&td.details.refresh_commitments.noreveal_index),
|
||||
GNUNET_PQ_result_spec_uint64 (
|
||||
"old_known_coin_id",
|
||||
&td.details.refresh_commitments.old_known_coin_id),
|
||||
GNUNET_PQ_result_spec_auto_from_type (
|
||||
"old_coin_pub",
|
||||
&td.details.refresh_commitments.old_coin_pub),
|
||||
GNUNET_PQ_result_spec_end
|
||||
};
|
||||
|
||||
@ -1219,7 +1219,7 @@ lrbt_cb_table_recoup (void *cls,
|
||||
&td.details.recoup.coin_blind),
|
||||
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
|
||||
&td.details.recoup.amount),
|
||||
GNUNET_PQ_result_spec_timestamp ("timestamp",
|
||||
GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
|
||||
&td.details.recoup.timestamp),
|
||||
GNUNET_PQ_result_spec_uint64 ("known_coin_id",
|
||||
&td.details.recoup.known_coin_id),
|
||||
@ -1274,7 +1274,7 @@ lrbt_cb_table_recoup_refresh (void *cls,
|
||||
&td.details.recoup_refresh.coin_blind),
|
||||
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
|
||||
&td.details.recoup_refresh.amount),
|
||||
GNUNET_PQ_result_spec_timestamp ("timestamp",
|
||||
GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
|
||||
&td.details.recoup_refresh.timestamp),
|
||||
GNUNET_PQ_result_spec_uint64 ("known_coin_id",
|
||||
&td.details.recoup_refresh.known_coin_id),
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1860,6 +1860,43 @@ TALER_wallet_recoup_sign (
|
||||
struct TALER_CoinSpendSignatureP *coin_sig);
|
||||
|
||||
|
||||
/**
|
||||
* Verify recoup-refresh signature.
|
||||
*
|
||||
* @param h_denom_pub hash of the denomiantion public key of the coin
|
||||
* @param coin_bks blinding factor used when withdrawing the coin
|
||||
* @param requested_amount amount that is left to be recouped
|
||||
* @param coin_pub coin key of the coin to be recouped
|
||||
* @param coin_sig resulting signature
|
||||
* @return #GNUNET_OK if the signature is valid
|
||||
*/
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_wallet_recoup_refresh_verify (
|
||||
const struct TALER_DenominationHash *h_denom_pub,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig);
|
||||
|
||||
|
||||
/**
|
||||
* Create recoup-refresh signature.
|
||||
*
|
||||
* @param h_denom_pub hash of the denomiantion public key of the coin
|
||||
* @param coin_bks blinding factor used when withdrawing the coin
|
||||
* @param requested_amount amount that is left to be recouped
|
||||
* @param coin_priv coin key of the coin to be recouped
|
||||
* @param coin_sig resulting signature
|
||||
*/
|
||||
void
|
||||
TALER_wallet_recoup_refresh_sign (
|
||||
const struct TALER_DenominationHash *h_denom_pub,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
|
||||
struct TALER_CoinSpendSignatureP *coin_sig);
|
||||
|
||||
|
||||
/* ********************* offline signing ************************** */
|
||||
|
||||
|
||||
|
@ -2001,23 +2001,19 @@ struct TALER_EXCHANGE_RecoupHandle;
|
||||
|
||||
/**
|
||||
* Callbacks of this type are used to return the final result of
|
||||
* submitting a refresh request to a exchange. If the operation was
|
||||
* successful, this function returns the signatures over the coins
|
||||
* that were remelted. The @a coin_privs and @a sigs arrays give the
|
||||
* coins in the same order (and should have the same length) in which
|
||||
* the original request specified the respective denomination keys.
|
||||
* submitting a recoup request to a exchange. If the operation was
|
||||
* successful, this function returns the @a reserve_pub of the
|
||||
* reserve that was credited.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param hr HTTP response data
|
||||
* @param reserve_pub public key of the reserve receiving the recoup, NULL if refreshed or on error
|
||||
* @param old_coin_pub public key of the dirty coin, NULL if not refreshed or on error
|
||||
* @param reserve_pub public key of the reserve receiving the recoup
|
||||
*/
|
||||
typedef void
|
||||
(*TALER_EXCHANGE_RecoupResultCallback) (
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub);
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub);
|
||||
|
||||
|
||||
/**
|
||||
@ -2030,7 +2026,6 @@ typedef void
|
||||
* @param denom_sig signature over the coin by the exchange using @a pk
|
||||
* @param ps secret internals of the original planchet
|
||||
* @param amount value remaining on the coin that is being recouped
|
||||
* @param was_refreshed true if the coin in @a ps was refreshed
|
||||
* @param recoup_cb the callback to call when the final result for this request is available
|
||||
* @param recoup_cb_cls closure for @a recoup_cb
|
||||
* @return NULL
|
||||
@ -2043,7 +2038,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
|
||||
const struct TALER_DenominationSignature *denom_sig,
|
||||
const struct TALER_PlanchetSecretsP *ps,
|
||||
const struct TALER_Amount *amount,
|
||||
bool was_refreshed,
|
||||
TALER_EXCHANGE_RecoupResultCallback recoup_cb,
|
||||
void *recoup_cb_cls);
|
||||
|
||||
@ -2058,6 +2052,70 @@ void
|
||||
TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph);
|
||||
|
||||
|
||||
/* ********************* /recoup-refresh *********************** */
|
||||
|
||||
|
||||
/**
|
||||
* @brief A /recoup-refresh Handle
|
||||
*/
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle;
|
||||
|
||||
|
||||
/**
|
||||
* Callbacks of this type are used to return the final result of
|
||||
* submitting a recoup-refresh request to a exchange.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param hr HTTP response data
|
||||
* @param old_coin_pub public key of the dirty coin that was credited
|
||||
*/
|
||||
typedef void
|
||||
(*TALER_EXCHANGE_RecoupRefreshResultCallback) (
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub);
|
||||
|
||||
|
||||
/**
|
||||
* Ask the exchange to pay back a coin due to the exchange triggering
|
||||
* the emergency recoup protocol for a given denomination. The value
|
||||
* of the coin will be refunded to the original coin that the
|
||||
* revoked coin was refreshed from. The original coin is then
|
||||
* considered a zombie.
|
||||
*
|
||||
* @param exchange the exchange handle; the exchange must be ready to operate
|
||||
* @param pk kind of coin to pay back
|
||||
* @param denom_sig signature over the coin by the exchange using @a pk
|
||||
* @param ps secret internals of the original planchet
|
||||
* @param amount value remaining on the coin that is being recouped
|
||||
* @param recoup_cb the callback to call when the final result for this request is available
|
||||
* @param recoup_cb_cls closure for @a recoup_cb
|
||||
* @return NULL
|
||||
* if the inputs are invalid (i.e. denomination key not with this exchange).
|
||||
* In this case, the callback is not called.
|
||||
*/
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *
|
||||
TALER_EXCHANGE_recoup_refresh (
|
||||
struct TALER_EXCHANGE_Handle *exchange,
|
||||
const struct TALER_EXCHANGE_DenomPublicKey *pk,
|
||||
const struct TALER_DenominationSignature *denom_sig,
|
||||
const struct TALER_PlanchetSecretsP *ps,
|
||||
const struct TALER_Amount *amount,
|
||||
TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
|
||||
void *recoup_cb_cls);
|
||||
|
||||
|
||||
/**
|
||||
* Cancel a recoup-refresh request. This function cannot be used on a request
|
||||
* handle if the callback was already invoked.
|
||||
*
|
||||
* @param ph the recoup handle
|
||||
*/
|
||||
void
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *ph);
|
||||
|
||||
|
||||
/* ********************* /kyc* *********************** */
|
||||
|
||||
/**
|
||||
|
@ -257,7 +257,7 @@ struct TALER_EXCHANGEDB_TableData
|
||||
struct TALER_BlindedCoinHash h_blind_ev;
|
||||
uint64_t denominations_serial;
|
||||
struct TALER_BlindedDenominationSignature denom_sig;
|
||||
struct TALER_ReservePublicKeyP reserve_pub;
|
||||
uint64_t reserve_uuid;
|
||||
struct TALER_ReserveSignatureP reserve_sig;
|
||||
struct GNUNET_TIME_Timestamp execution_date;
|
||||
struct TALER_Amount amount_with_fee;
|
||||
@ -303,7 +303,7 @@ struct TALER_EXCHANGEDB_TableData
|
||||
struct
|
||||
{
|
||||
struct TALER_RefreshCommitmentP rc;
|
||||
uint64_t old_known_coin_id;
|
||||
struct TALER_CoinSpendPublicKeyP old_coin_pub;
|
||||
struct TALER_CoinSpendSignatureP old_coin_sig;
|
||||
struct TALER_Amount amount_with_fee;
|
||||
uint32_t noreveal_index;
|
||||
@ -1037,7 +1037,7 @@ struct TALER_EXCHANGEDB_Deposit
|
||||
|
||||
/**
|
||||
* Additional details for extensions relevant for this
|
||||
* deposit operation.
|
||||
* deposit operation, possibly NULL!
|
||||
*/
|
||||
json_t *extension_details;
|
||||
|
||||
@ -1625,9 +1625,9 @@ typedef enum GNUNET_GenericReturnValue
|
||||
struct TALER_EXCHANGEDB_RefreshRevealedCoin
|
||||
{
|
||||
/**
|
||||
* Public denomination key of the coin.
|
||||
* Hash of the public denomination key of the coin.
|
||||
*/
|
||||
struct TALER_DenominationPublicKey denom_pub;
|
||||
struct TALER_DenominationHash h_denom_pub;
|
||||
|
||||
/**
|
||||
* Signature of the original coin being refreshed over the
|
||||
@ -1725,18 +1725,12 @@ struct TALER_EXCHANGEDB_KycStatus
|
||||
* @param cls closure
|
||||
* @param num_freshcoins size of the @a rrcs array
|
||||
* @param rrcs array of @a num_freshcoins information about coins to be created
|
||||
* @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
|
||||
* @param tprivs array of @e num_tprivs transfer private keys
|
||||
* @param tp transfer public key information
|
||||
*/
|
||||
typedef void
|
||||
(*TALER_EXCHANGEDB_RefreshCallback)(
|
||||
void *cls,
|
||||
uint32_t num_freshcoins,
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
|
||||
unsigned int num_tprivs,
|
||||
const struct TALER_TransferPrivateKeyP *tprivs,
|
||||
const struct TALER_TransferPublicKeyP *tp);
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs);
|
||||
|
||||
|
||||
/**
|
||||
@ -2400,20 +2394,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
const char *id);
|
||||
|
||||
|
||||
/**
|
||||
* Get the KYC status for a bank account.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param payto_uri payto:// URI that identifies the bank account
|
||||
* @param[out] kyc set to the KYC status of the reserve
|
||||
* @return transaction status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_kyc_status)(void *cls,
|
||||
const char *payto_uri,
|
||||
struct TALER_EXCHANGEDB_KycStatus *kyc);
|
||||
|
||||
|
||||
/**
|
||||
* Get the @a kyc status and @a h_payto by UUID.
|
||||
*
|
||||
@ -2468,22 +2448,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
uint64_t wire_reference);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the most recent @a wire_reference that was inserted via @e reserves_in_insert.
|
||||
* Used by the wirewatch process when resuming.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param exchange_account_name name of the section in the exchange's configuration
|
||||
* for the account that we are tracking here
|
||||
* @param[out] wire_reference set to unique reference identifying the wire transfer
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_latest_reserve_in_reference)(void *cls,
|
||||
const char *exchange_account_name,
|
||||
uint64_t *wire_reference);
|
||||
|
||||
|
||||
/**
|
||||
* Locate the response for a withdraw request under the
|
||||
* key of the hash of the blinded message. Used to ensure
|
||||
@ -2502,30 +2466,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
|
||||
|
||||
|
||||
/**
|
||||
* Check coin balance is sufficient to satisfy balance
|
||||
* invariants.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param coin_pub coin to check
|
||||
* @param coin_value value of the coin's denomination (avoids internal lookup)
|
||||
* @param check_recoup include recoup and recoup_refresh tables in calculation
|
||||
* @param zombie_required additionally require coin to be a zombie coin
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @param[out] zombie_ok set to true if the zombie requirement was satisfied
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_check_coin_balance)(
|
||||
void *cls,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_Amount *coin_value,
|
||||
bool check_recoup,
|
||||
bool zombie_required,
|
||||
bool *balance_ok,
|
||||
bool *zombie_ok);
|
||||
|
||||
|
||||
/**
|
||||
* Perform withdraw operation, checking for sufficient balance
|
||||
* and possibly persisting the withdrawal details.
|
||||
@ -2537,6 +2477,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param[out] found set to true if the reserve was found
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @param[out] kyc set to the KYC status of the reserve
|
||||
* @param[out] ruuid set to the reserve's UUID (reserves table row)
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
@ -2546,7 +2487,8 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
struct GNUNET_TIME_Timestamp now,
|
||||
bool *found,
|
||||
bool *balance_ok,
|
||||
struct TALER_EXCHANGEDB_KycStatus *kyc_ok);
|
||||
struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
|
||||
uint64_t *ruuid);
|
||||
|
||||
|
||||
/**
|
||||
@ -2554,7 +2496,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* checks after withdraw operation.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param reserve_pub reserve to check
|
||||
* @param ruuid identifies the reserve to check
|
||||
* @param withdraw_start starting point to accumulate from
|
||||
* @param upper_limit maximum amount allowed
|
||||
* @param[out] below_limit set to true if the limit was not exceeded
|
||||
@ -2563,12 +2505,148 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_withdraw_limit_check)(
|
||||
void *cls,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
uint64_t ruuid,
|
||||
struct GNUNET_TIME_Absolute withdraw_start,
|
||||
const struct TALER_Amount *upper_limit,
|
||||
bool *below_limit);
|
||||
|
||||
|
||||
/**
|
||||
* Perform deposit operation, checking for sufficient balance
|
||||
* of the coin and possibly persisting the deposit details.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param deposit deposit operation details
|
||||
* @param known_coin_id row of the coin in the known_coins table
|
||||
* @param h_payto hash of the merchant's payto URI
|
||||
* @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @param[out] in_conflict set to true if the deposit conflicted
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_deposit)(
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit,
|
||||
uint64_t known_coin_id,
|
||||
const struct TALER_PaytoHash *h_payto,
|
||||
bool extension_blocked,
|
||||
struct GNUNET_TIME_Timestamp *exchange_timestamp,
|
||||
bool *balance_ok,
|
||||
bool *in_conflict);
|
||||
|
||||
|
||||
/**
|
||||
* Perform melt operation, checking for sufficient balance
|
||||
* of the coin and possibly persisting the melt details.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param[in,out] refresh refresh operation details; the noreveal_index
|
||||
* is set in case the coin was already melted before
|
||||
* @param known_coin_id row of the coin in the known_coins table
|
||||
* @param[in,out] zombie_required true if the melt must only succeed if the coin is a zombie, set to false if the requirement was satisfied
|
||||
* @param[out] balance_ok set to true if the balance was sufficient
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_melt)(
|
||||
void *cls,
|
||||
struct TALER_EXCHANGEDB_Refresh *refresh,
|
||||
uint64_t known_coin_id,
|
||||
bool *zombie_required,
|
||||
bool *balance_ok);
|
||||
|
||||
|
||||
/**
|
||||
* Perform refund operation, checking for sufficient deposits
|
||||
* of the coin and possibly persisting the refund details.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param refund refund operation details
|
||||
* @param deposit_fee deposit fee applicable for the coin, possibly refunded
|
||||
* @param known_coin_id row of the coin in the known_coins table
|
||||
* @param[out] not_found set if the deposit was not found
|
||||
* @param[out] refund_ok set if the refund succeeded (below deposit amount)
|
||||
* @param[out] gone if the merchant was already paid
|
||||
* @param[out] conflict set if the refund ID was re-used
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_refund)(
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGEDB_Refund *refund,
|
||||
const struct TALER_Amount *deposit_fee,
|
||||
uint64_t known_coin_id,
|
||||
bool *not_found,
|
||||
bool *refund_ok,
|
||||
bool *gone,
|
||||
bool *conflict);
|
||||
|
||||
|
||||
/**
|
||||
* Perform recoup operation, checking for sufficient deposits
|
||||
* of the coin and possibly persisting the recoup details.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param reserve_pub public key of the reserve to credit
|
||||
* @param reserve_out_serial_id row in the reserves_out table justifying the recoup
|
||||
* @param requested_amount the amount to be recouped
|
||||
* @param coin_bks coin blinding key secret to persist
|
||||
* @param coin_pub public key of the coin being recouped
|
||||
* @param known_coin_id row of the @a coin_pub in the known_coins table
|
||||
* @param coin_sig signature of the coin requesting the recoup
|
||||
* @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
|
||||
* @param[out] recoup_ok set if the recoup succeeded (balance ok)
|
||||
* @param[out] internal_failure set on internal failures
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_recoup)(
|
||||
void *cls,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
uint64_t reserve_out_serial_id,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
uint64_t known_coin_id,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
struct GNUNET_TIME_Timestamp *recoup_timestamp,
|
||||
bool *recoup_ok,
|
||||
bool *internal_failure);
|
||||
|
||||
|
||||
/**
|
||||
* Perform recoup-refresh operation, checking for sufficient deposits of the
|
||||
* coin and possibly persisting the recoup-refresh details.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param old_coin_pub public key of the old coin to credit
|
||||
* @param rrc_serial row in the refresh_revealed_coins table justifying the recoup-refresh
|
||||
* @param requested_amount the amount to be recouped
|
||||
* @param coin_bks coin blinding key secret to persist
|
||||
* @param coin_pub public key of the coin being recouped
|
||||
* @param known_coin_id row of the @a coin_pub in the known_coins table
|
||||
* @param coin_sig signature of the coin requesting the recoup
|
||||
* @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
|
||||
* @param[out] recoup_ok set if the recoup-refresh succeeded (balance ok)
|
||||
* @param[out] internal_failure set on internal failures
|
||||
* @return query execution status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*do_recoup_refresh)(
|
||||
void *cls,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
|
||||
uint64_t rrc_serial,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
uint64_t known_coin_id,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
struct GNUNET_TIME_Timestamp *recoup_timestamp,
|
||||
bool *recoup_ok,
|
||||
bool *internal_failure);
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the transaction history associated with the specified
|
||||
* reserve.
|
||||
@ -2586,27 +2664,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
struct TALER_EXCHANGEDB_ReserveHistory **rhp);
|
||||
|
||||
|
||||
/**
|
||||
* Find out all of the amounts that have been withdrawn
|
||||
* so far from the same bank account that created the
|
||||
* given reserve.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param reserve_pub reserve to select withdrawals by
|
||||
* @param duration how far back should we select withdrawals
|
||||
* @param cb function to call on each amount withdrawn
|
||||
* @param cb_cls closure for @a cb
|
||||
* @return transaction status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*select_withdraw_amounts_by_account)(
|
||||
void *cls,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
struct GNUNET_TIME_Relative duration,
|
||||
TALER_EXCHANGEDB_WithdrawHistoryCallback cb,
|
||||
void *cb_cls);
|
||||
|
||||
|
||||
/**
|
||||
* Free memory associated with the given reserve history.
|
||||
*
|
||||
@ -2635,6 +2692,9 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
*
|
||||
* @param cls database connection plugin state
|
||||
* @param coin the coin that must be made known
|
||||
* @param[out] known_coin_id set to the unique row of the coin
|
||||
* @param[out] denom_pub_hash set to the conflicting denomination hash on conflict
|
||||
* @param[out] age_hash set to the conflicting age hash on conflict
|
||||
* @return database transaction status, non-negative on success
|
||||
*/
|
||||
enum TALER_EXCHANGEDB_CoinKnownStatus
|
||||
@ -2662,10 +2722,18 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
/**
|
||||
* Conflicting coin (different denomination key) already in database.
|
||||
*/
|
||||
TALER_EXCHANGEDB_CKS_CONFLICT = -3,
|
||||
TALER_EXCHANGEDB_CKS_DENOM_CONFLICT = -3,
|
||||
|
||||
/**
|
||||
* Conflicting coin (different age hash) already in database.
|
||||
*/
|
||||
TALER_EXCHANGEDB_CKS_AGE_CONFLICT = -4,
|
||||
}
|
||||
(*ensure_coin_known)(void *cls,
|
||||
const struct TALER_CoinPublicInfo *coin);
|
||||
const struct TALER_CoinPublicInfo *coin,
|
||||
uint64_t *known_coin_id,
|
||||
struct TALER_DenominationHash *denom_pub_hash,
|
||||
struct TALER_AgeHash *age_hash);
|
||||
|
||||
|
||||
/**
|
||||
@ -2686,33 +2754,17 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
*
|
||||
* @param cls the plugin closure
|
||||
* @param coin_pub the public key of the coin to search for
|
||||
* @param[out] known_coin_id set to the ID of the coin in the known_coins table
|
||||
* @param[out] denom_hash where to store the hash of the coins denomination
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_coin_denomination)(void *cls,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
uint64_t *known_coin_id,
|
||||
struct TALER_DenominationHash *denom_hash);
|
||||
|
||||
|
||||
/**
|
||||
* Check if we have the specified deposit already in the database.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param deposit deposit to search for
|
||||
* @param[out] deposit_fee set to the deposit fee the exchange charged
|
||||
* @param[out] exchange_timestamp set to the time when the exchange received the deposit
|
||||
* @return 1 if we know this operation,
|
||||
* 0 if this exact deposit is unknown to us,
|
||||
* otherwise transaction error status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*have_deposit)(void *cls,
|
||||
const struct TALER_EXCHANGEDB_Deposit *deposit,
|
||||
struct TALER_Amount *deposit_fee,
|
||||
struct GNUNET_TIME_Timestamp *exchange_timestamp);
|
||||
|
||||
|
||||
/**
|
||||
* Check if we have the specified deposit already in the database.
|
||||
*
|
||||
@ -2728,6 +2780,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* 0 if this exact deposit is unknown to us,
|
||||
* otherwise transaction error status
|
||||
*/
|
||||
// FIXME: rename!
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*have_deposit2)(
|
||||
void *cls,
|
||||
@ -2742,6 +2795,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
|
||||
/**
|
||||
* Insert information about deposited coin into the database.
|
||||
* Used in tests and for benchmarking.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param exchange_timestamp time the exchange received the deposit request
|
||||
@ -2756,6 +2810,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
|
||||
/**
|
||||
* Insert information about refunded coin into the database.
|
||||
* Used in tests and for benchmarking.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param refund refund information to store
|
||||
@ -2873,18 +2928,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
uint32_t limit);
|
||||
|
||||
|
||||
/**
|
||||
* Store new melt commitment data.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param refresh_session operational data to store
|
||||
* @return query status for the transaction
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_melt)(void *cls,
|
||||
const struct TALER_EXCHANGEDB_Refresh *refresh_session);
|
||||
|
||||
|
||||
/**
|
||||
* Lookup melt commitment data under the given @a rc.
|
||||
*
|
||||
@ -2893,29 +2936,14 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param[out] melt where to store the result; note that
|
||||
* melt->session.coin.denom_sig will be set to NULL
|
||||
* and is not fetched by this routine (as it is not needed by the client)
|
||||
* @param[out] melt_serial_id set to the row ID of @a rc in the refresh_commitments table
|
||||
* @return transaction status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_melt)(void *cls,
|
||||
const struct TALER_RefreshCommitmentP *rc,
|
||||
struct TALER_EXCHANGEDB_Melt *melt);
|
||||
|
||||
|
||||
/**
|
||||
* Lookup noreveal index of a previous melt operation under the given
|
||||
* @a rc.
|
||||
*
|
||||
* @param cls the `struct PostgresClosure` with the plugin-specific state
|
||||
* @param rc commitment hash to use to locate the operation
|
||||
* @param[out] noreveal_index returns the "gamma" value selected by the
|
||||
* exchange which is the index of the transfer key that is
|
||||
* not to be revealed to the exchange
|
||||
* @return transaction status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_melt_index)(void *cls,
|
||||
const struct TALER_RefreshCommitmentP *rc,
|
||||
uint32_t *noreveal_index);
|
||||
struct TALER_EXCHANGEDB_Melt *melt,
|
||||
uint64_t *melt_serial_id);
|
||||
|
||||
|
||||
/**
|
||||
@ -2924,7 +2952,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* we learned or created in the reveal step.
|
||||
*
|
||||
* @param cls the @e cls of this struct with the plugin-specific state
|
||||
* @param rc identify commitment and thus refresh operation
|
||||
* @param melt_serial_id row ID of the commitment / melt operation in refresh_commitments
|
||||
* @param num_rrcs number of coins to generate, size of the @a rrcs array
|
||||
* @param rrcs information about the new coins
|
||||
* @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
|
||||
@ -2935,7 +2963,7 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_refresh_reveal)(
|
||||
void *cls,
|
||||
const struct TALER_RefreshCommitmentP *rc,
|
||||
uint64_t melt_serial_id,
|
||||
uint32_t num_rrcs,
|
||||
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
|
||||
unsigned int num_tprivs,
|
||||
@ -3460,58 +3488,6 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
void *cb_cls);
|
||||
|
||||
|
||||
/**
|
||||
* Function called to add a request for an emergency recoup for a
|
||||
* coin. The funds are to be added back to the reserve.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param reserve_pub public key of the reserve that is being refunded
|
||||
* @param coin public information about a coin
|
||||
* @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
|
||||
* @param coin_blind blinding key of the coin
|
||||
* @param h_blind_ev blinded envelope, as calculated by the exchange
|
||||
* @param amount total amount to be paid back
|
||||
* @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
|
||||
* @param timestamp the timestamp to store
|
||||
* @return transaction result status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_recoup_request)(
|
||||
void *cls,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
const struct TALER_CoinPublicInfo *coin,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
const union TALER_DenominationBlindingKeyP *coin_blind,
|
||||
const struct TALER_Amount *amount,
|
||||
const struct TALER_BlindedCoinHash *h_blind_ev,
|
||||
struct GNUNET_TIME_Timestamp timestamp);
|
||||
|
||||
|
||||
/**
|
||||
* Function called to add a request for an emergency recoup for a
|
||||
* refreshed coin. The funds are to be added back to the original coin.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param coin public information about the refreshed coin
|
||||
* @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
|
||||
* @param coin_blind blinding key of the coin
|
||||
* @param h_blind_ev blinded envelope, as calculated by the exchange
|
||||
* @param amount total amount to be paid back
|
||||
* @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
|
||||
* @param timestamp a timestamp to store
|
||||
* @return transaction result status
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*insert_recoup_refresh_request)(
|
||||
void *cls,
|
||||
const struct TALER_CoinPublicInfo *coin,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig,
|
||||
const union TALER_DenominationBlindingKeyP *coin_blind,
|
||||
const struct TALER_Amount *amount,
|
||||
const struct TALER_BlindedCoinHash *h_blind_ev,
|
||||
struct GNUNET_TIME_Timestamp timestamp);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain information about which reserve a coin was generated
|
||||
* from given the hash of the blinded coin.
|
||||
@ -3519,12 +3495,14 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param cls closure
|
||||
* @param h_blind_ev hash of the blinded coin
|
||||
* @param[out] reserve_pub set to information about the reserve (on success only)
|
||||
* @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_reserve_by_h_blind)(void *cls,
|
||||
const struct TALER_BlindedCoinHash *h_blind_ev,
|
||||
struct TALER_ReservePublicKeyP *reserve_pub);
|
||||
struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
uint64_t *reserve_out_serial_id);
|
||||
|
||||
|
||||
/**
|
||||
@ -3534,12 +3512,14 @@ struct TALER_EXCHANGEDB_Plugin
|
||||
* @param cls closure
|
||||
* @param h_blind_ev hash of the blinded coin
|
||||
* @param[out] old_coin_pub set to information about the old coin (on success only)
|
||||
* @param[out] rrc_serial set to the row of the @a h_blind_ev in the refresh_revealed_coins table
|
||||
* @return transaction status code
|
||||
*/
|
||||
enum GNUNET_DB_QueryStatus
|
||||
(*get_old_coin_by_h_blind)(void *cls,
|
||||
const struct TALER_BlindedCoinHash *h_blind_ev,
|
||||
struct TALER_CoinSpendPublicKeyP *old_coin_pub);
|
||||
struct TALER_CoinSpendPublicKeyP *old_coin_pub,
|
||||
uint64_t *rrc_serial);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -263,6 +263,11 @@
|
||||
*/
|
||||
#define TALER_SIGNATURE_WALLET_ACCOUNT_SETUP 1205
|
||||
|
||||
/**
|
||||
* Signature using a coin key requesting recoup-refresh.
|
||||
*/
|
||||
#define TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH 1206
|
||||
|
||||
|
||||
/******************************/
|
||||
/* Security module signatures */
|
||||
|
@ -1747,7 +1747,6 @@ TALER_TESTING_cmd_refund (const char *label,
|
||||
* offers a coin and reserve private key. May specify
|
||||
* the index of the coin using "$LABEL#$INDEX" syntax.
|
||||
* Here, $INDEX must be a non-negative number.
|
||||
* @param melt_reference NULL if coin was not refreshed, otherwise label of the melt operation
|
||||
* @param amount how much do we expect to recoup, NULL for nothing
|
||||
* @return the command.
|
||||
*/
|
||||
@ -1755,10 +1754,30 @@ struct TALER_TESTING_Command
|
||||
TALER_TESTING_cmd_recoup (const char *label,
|
||||
unsigned int expected_response_code,
|
||||
const char *coin_reference,
|
||||
const char *melt_reference,
|
||||
const char *amount);
|
||||
|
||||
|
||||
/**
|
||||
* Make a "recoup-refresh" command.
|
||||
*
|
||||
* @param label the command label
|
||||
* @param expected_response_code expected HTTP status code
|
||||
* @param coin_reference reference to any command which
|
||||
* offers a coin and reserve private key. May specify
|
||||
* the index of the coin using "$LABEL#$INDEX" syntax.
|
||||
* Here, $INDEX must be a non-negative number.
|
||||
* @param melt_reference label of the melt operation
|
||||
* @param amount how much do we expect to recoup, NULL for nothing
|
||||
* @return the command.
|
||||
*/
|
||||
struct TALER_TESTING_Command
|
||||
TALER_TESTING_cmd_recoup_refresh (const char *label,
|
||||
unsigned int expected_response_code,
|
||||
const char *coin_reference,
|
||||
const char *melt_reference,
|
||||
const char *amount);
|
||||
|
||||
|
||||
/**
|
||||
* Make a "revoke" command.
|
||||
*
|
||||
|
@ -43,6 +43,7 @@ libtalerexchange_la_SOURCES = \
|
||||
exchange_api_management_wire_enable.c \
|
||||
exchange_api_melt.c \
|
||||
exchange_api_recoup.c \
|
||||
exchange_api_recoup_refresh.c \
|
||||
exchange_api_refresh_common.c exchange_api_refresh_common.h \
|
||||
exchange_api_refreshes_reveal.c \
|
||||
exchange_api_refund.c \
|
||||
|
@ -546,17 +546,17 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
else if (0 == strcasecmp (type,
|
||||
"MELT"))
|
||||
{
|
||||
struct TALER_RefreshMeltCoinAffirmationPS rm;
|
||||
struct TALER_CoinSpendSignatureP sig;
|
||||
struct TALER_RefreshCommitmentP rc;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("coin_sig",
|
||||
&sig),
|
||||
GNUNET_JSON_spec_fixed_auto ("rc",
|
||||
&rm.rc),
|
||||
&rc),
|
||||
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
|
||||
&rm.h_denom_pub),
|
||||
TALER_JSON_spec_amount_any_nbo ("melt_fee",
|
||||
&rm.melt_fee),
|
||||
h_denom_pub),
|
||||
TALER_JSON_spec_amount_any ("melt_fee",
|
||||
&fee),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
@ -568,26 +568,9 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
rm.purpose.size = htonl (sizeof (rm));
|
||||
rm.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
|
||||
TALER_amount_hton (&rm.amount_with_fee,
|
||||
&amount);
|
||||
rm.coin_pub = *coin_pub;
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
|
||||
&rm,
|
||||
&sig.eddsa_signature,
|
||||
&coin_pub->eddsa_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
*h_denom_pub = rm.h_denom_pub;
|
||||
if (NULL != dk)
|
||||
{
|
||||
/* check that melt fee matches our expectations from /keys! */
|
||||
TALER_amount_ntoh (&fee,
|
||||
&rm.melt_fee);
|
||||
if ( (GNUNET_YES !=
|
||||
TALER_amount_cmp_currency (&fee,
|
||||
&dk->fee_refresh)) ||
|
||||
@ -599,6 +582,17 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_wallet_melt_verify (&amount,
|
||||
&fee,
|
||||
&rc,
|
||||
h_denom_pub,
|
||||
coin_pub,
|
||||
&sig))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
add = GNUNET_YES;
|
||||
}
|
||||
else if (0 == strcasecmp (type,
|
||||
|
@ -39,7 +39,7 @@
|
||||
* Which version of the Taler protocol is implemented
|
||||
* by this library? Used to determine compatibility.
|
||||
*/
|
||||
#define EXCHANGE_PROTOCOL_CURRENT 11
|
||||
#define EXCHANGE_PROTOCOL_CURRENT 12
|
||||
|
||||
/**
|
||||
* How many versions are we backwards compatible with?
|
||||
|
@ -213,15 +213,10 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
const json_t *json)
|
||||
{
|
||||
json_t *history;
|
||||
struct TALER_Amount original_value;
|
||||
struct TALER_Amount melt_value_with_fee;
|
||||
struct TALER_Amount total;
|
||||
struct TALER_CoinSpendPublicKeyP coin_pub;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_json ("history", &history),
|
||||
GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
|
||||
TALER_JSON_spec_amount_any ("original_value", &original_value),
|
||||
TALER_JSON_spec_amount_any ("requested_value", &melt_value_with_fee),
|
||||
GNUNET_JSON_spec_json ("history",
|
||||
&history),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
const struct MeltedCoin *mc;
|
||||
@ -240,25 +235,6 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
|
||||
/* Find out which coin was deemed problematic by the exchange */
|
||||
mc = &mh->md->melted_coin;
|
||||
|
||||
/* check basic coin properties */
|
||||
if (0 != TALER_amount_cmp (&original_value,
|
||||
&mc->original_value))
|
||||
{
|
||||
/* We disagree on the value of the coin */
|
||||
GNUNET_break_op (0);
|
||||
json_decref (history);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (0 != TALER_amount_cmp (&melt_value_with_fee,
|
||||
&mc->melt_amount_with_fee))
|
||||
{
|
||||
/* We disagree on the value of the coin */
|
||||
GNUNET_break_op (0);
|
||||
json_decref (history);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
/* verify coin history */
|
||||
memset (&h_denom_pub,
|
||||
0,
|
||||
@ -267,8 +243,8 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
"history");
|
||||
if (GNUNET_OK !=
|
||||
TALER_EXCHANGE_verify_coin_history (&mh->dki,
|
||||
original_value.currency,
|
||||
&coin_pub,
|
||||
mc->original_value.currency,
|
||||
&mh->coin_pub,
|
||||
history,
|
||||
&h_denom_pub,
|
||||
&total))
|
||||
@ -287,7 +263,7 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
if (0 >
|
||||
TALER_amount_add (&total,
|
||||
&total,
|
||||
&melt_value_with_fee))
|
||||
&mc->melt_amount_with_fee))
|
||||
{
|
||||
/* clearly not OK if our transaction would have caused
|
||||
the overflow... */
|
||||
@ -295,7 +271,7 @@ verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
|
||||
}
|
||||
|
||||
if (0 >= TALER_amount_cmp (&total,
|
||||
&original_value))
|
||||
&mc->original_value))
|
||||
{
|
||||
/* transaction should have still fit */
|
||||
GNUNET_break (0);
|
||||
@ -488,7 +464,7 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
|
||||
&coin_pub.eddsa_pub);
|
||||
melt_obj = GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_data_auto ("denom_pub_hash",
|
||||
&melt.h_denom_pub),
|
||||
&h_denom_pub),
|
||||
TALER_JSON_pack_denom_sig ("denom_sig",
|
||||
&md->melted_coin.sig),
|
||||
GNUNET_JSON_pack_data_auto ("confirm_sig",
|
||||
@ -496,13 +472,13 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
|
||||
TALER_JSON_pack_amount ("value_with_fee",
|
||||
&md->melted_coin.melt_amount_with_fee),
|
||||
GNUNET_JSON_pack_data_auto ("rc",
|
||||
&melt.rc));
|
||||
&md->rc));
|
||||
{
|
||||
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
|
||||
char *end;
|
||||
|
||||
end = GNUNET_STRINGS_data_to_string (
|
||||
&melt.coin_pub,
|
||||
&coin_pub,
|
||||
sizeof (struct TALER_CoinSpendPublicKeyP),
|
||||
pub_str,
|
||||
sizeof (pub_str));
|
||||
@ -520,7 +496,7 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
|
||||
/* and now we can at last begin the actual request handling */
|
||||
mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
|
||||
mh->exchange = exchange;
|
||||
mh->coin_pub = melt.coin_pub;
|
||||
mh->coin_pub = coin_pub;
|
||||
mh->dki = *dki;
|
||||
memset (&mh->dki.key,
|
||||
0,
|
||||
|
@ -79,11 +79,6 @@ struct TALER_EXCHANGE_RecoupHandle
|
||||
*/
|
||||
struct TALER_CoinSpendPublicKeyP coin_pub;
|
||||
|
||||
/**
|
||||
* #GNUNET_YES if the coin was refreshed
|
||||
*/
|
||||
int was_refreshed;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -99,17 +94,10 @@ static enum GNUNET_GenericReturnValue
|
||||
process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
|
||||
const json_t *json)
|
||||
{
|
||||
int refreshed;
|
||||
struct TALER_ReservePublicKeyP reserve_pub;
|
||||
struct TALER_CoinSpendPublicKeyP old_coin_pub;
|
||||
struct GNUNET_JSON_Specification spec_withdraw[] = {
|
||||
GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
|
||||
GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
struct GNUNET_JSON_Specification spec_refresh[] = {
|
||||
GNUNET_JSON_spec_boolean ("refreshed", &refreshed),
|
||||
GNUNET_JSON_spec_fixed_auto ("old_coin_pub", &old_coin_pub),
|
||||
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
|
||||
&reserve_pub),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
struct TALER_EXCHANGE_HttpResponse hr = {
|
||||
@ -119,21 +107,15 @@ process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (json,
|
||||
ph->was_refreshed ? spec_refresh : spec_withdraw,
|
||||
spec_withdraw,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (ph->was_refreshed != refreshed)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
ph->was_refreshed ? NULL : &reserve_pub,
|
||||
ph->was_refreshed ? &old_coin_pub : NULL);
|
||||
&reserve_pub);
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
@ -214,7 +196,7 @@ handle_recoup_finished (void *cls,
|
||||
ec = TALER_JSON_get_error_code (j);
|
||||
switch (ec)
|
||||
{
|
||||
case TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO:
|
||||
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||
if (0 > TALER_amount_cmp (&total,
|
||||
&dki->value))
|
||||
{
|
||||
@ -251,7 +233,6 @@ handle_recoup_finished (void *cls,
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
NULL,
|
||||
NULL);
|
||||
TALER_EXCHANGE_recoup_cancel (ph);
|
||||
return;
|
||||
@ -294,7 +275,6 @@ handle_recoup_finished (void *cls,
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
NULL,
|
||||
NULL);
|
||||
TALER_EXCHANGE_recoup_cancel (ph);
|
||||
}
|
||||
@ -306,7 +286,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
|
||||
const struct TALER_DenominationSignature *denom_sig,
|
||||
const struct TALER_PlanchetSecretsP *ps,
|
||||
const struct TALER_Amount *amount,
|
||||
bool was_refreshed,
|
||||
TALER_EXCHANGE_RecoupResultCallback recoup_cb,
|
||||
void *recoup_cb_cls)
|
||||
{
|
||||
@ -340,9 +319,8 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
|
||||
GNUNET_JSON_pack_data_auto ("coin_sig",
|
||||
&coin_sig),
|
||||
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
|
||||
&ps->blinding_key),
|
||||
GNUNET_JSON_pack_bool ("refreshed",
|
||||
was_refreshed));
|
||||
&ps->blinding_key));
|
||||
|
||||
{
|
||||
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
|
||||
char *end;
|
||||
@ -376,7 +354,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
|
||||
GNUNET_free (ph);
|
||||
return NULL;
|
||||
}
|
||||
ph->was_refreshed = was_refreshed;
|
||||
eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
|
||||
if ( (NULL == eh) ||
|
||||
(GNUNET_OK !=
|
||||
|
403
src/lib/exchange_api_recoup_refresh.c
Normal file
403
src/lib/exchange_api_recoup_refresh.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 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
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
TALER; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file lib/exchange_api_recoup_refresh.c
|
||||
* @brief Implementation of the /recoup-refresh request of the exchange's HTTP API
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
#include "platform.h"
|
||||
#include <jansson.h>
|
||||
#include <microhttpd.h> /* just for HTTP status codes */
|
||||
#include <gnunet/gnunet_util_lib.h>
|
||||
#include <gnunet/gnunet_json_lib.h>
|
||||
#include <gnunet/gnunet_curl_lib.h>
|
||||
#include "taler_json_lib.h"
|
||||
#include "taler_exchange_service.h"
|
||||
#include "exchange_api_handle.h"
|
||||
#include "taler_signatures.h"
|
||||
#include "exchange_api_curl_defaults.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief A Recoup Handle
|
||||
*/
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle
|
||||
{
|
||||
|
||||
/**
|
||||
* The connection to exchange this request handle will use
|
||||
*/
|
||||
struct TALER_EXCHANGE_Handle *exchange;
|
||||
|
||||
/**
|
||||
* The url for this request.
|
||||
*/
|
||||
char *url;
|
||||
|
||||
/**
|
||||
* Context for #TEH_curl_easy_post(). Keeps the data that must
|
||||
* persist for Curl to make the upload.
|
||||
*/
|
||||
struct TALER_CURL_PostContext ctx;
|
||||
|
||||
/**
|
||||
* Denomination key of the coin.
|
||||
*/
|
||||
struct TALER_EXCHANGE_DenomPublicKey pk;
|
||||
|
||||
/**
|
||||
* Handle for the request.
|
||||
*/
|
||||
struct GNUNET_CURL_Job *job;
|
||||
|
||||
/**
|
||||
* Function to call with the result.
|
||||
*/
|
||||
TALER_EXCHANGE_RecoupRefreshResultCallback cb;
|
||||
|
||||
/**
|
||||
* Closure for @a cb.
|
||||
*/
|
||||
void *cb_cls;
|
||||
|
||||
/**
|
||||
* Public key of the coin we are trying to get paid back.
|
||||
*/
|
||||
struct TALER_CoinSpendPublicKeyP coin_pub;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a recoup-refresh response. If it is valid, call the callback.
|
||||
*
|
||||
* @param ph recoup handle
|
||||
* @param json json reply with the signature
|
||||
* @return #GNUNET_OK if the signature is valid and we called the callback;
|
||||
* #GNUNET_SYSERR if not (callback must still be called)
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
process_recoup_response (
|
||||
const struct TALER_EXCHANGE_RecoupRefreshHandle *ph,
|
||||
const json_t *json)
|
||||
{
|
||||
struct TALER_CoinSpendPublicKeyP old_coin_pub;
|
||||
struct GNUNET_JSON_Specification spec_refresh[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
|
||||
&old_coin_pub),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
struct TALER_EXCHANGE_HttpResponse hr = {
|
||||
.reply = json,
|
||||
.http_status = MHD_HTTP_OK
|
||||
};
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (json,
|
||||
spec_refresh,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
&old_coin_pub);
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called when we're done processing the
|
||||
* HTTP /recoup-refresh request.
|
||||
*
|
||||
* @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle`
|
||||
* @param response_code HTTP response code, 0 on error
|
||||
* @param response parsed JSON result, NULL on error
|
||||
*/
|
||||
static void
|
||||
handle_recoup_refresh_finished (void *cls,
|
||||
long response_code,
|
||||
const void *response)
|
||||
{
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls;
|
||||
const json_t *j = response;
|
||||
struct TALER_EXCHANGE_HttpResponse hr = {
|
||||
.reply = j,
|
||||
.http_status = (unsigned int) response_code
|
||||
};
|
||||
|
||||
ph->job = NULL;
|
||||
switch (response_code)
|
||||
{
|
||||
case 0:
|
||||
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
|
||||
break;
|
||||
case MHD_HTTP_OK:
|
||||
if (GNUNET_OK !=
|
||||
process_recoup_response (ph,
|
||||
j))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||
hr.http_status = 0;
|
||||
break;
|
||||
}
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (ph);
|
||||
return;
|
||||
case MHD_HTTP_BAD_REQUEST:
|
||||
/* This should never happen, either us or the exchange is buggy
|
||||
(or API version conflict); just pass JSON reply to the application */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
case MHD_HTTP_CONFLICT:
|
||||
{
|
||||
/* Insufficient funds, proof attached */
|
||||
json_t *history;
|
||||
struct TALER_Amount total;
|
||||
struct TALER_DenominationHash h_denom_pub;
|
||||
const struct TALER_EXCHANGE_DenomPublicKey *dki;
|
||||
enum TALER_ErrorCode ec;
|
||||
|
||||
dki = &ph->pk;
|
||||
history = json_object_get (j,
|
||||
"history");
|
||||
if (GNUNET_OK !=
|
||||
TALER_EXCHANGE_verify_coin_history (dki,
|
||||
dki->fee_deposit.currency,
|
||||
&ph->coin_pub,
|
||||
history,
|
||||
&h_denom_pub,
|
||||
&total))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
hr.http_status = 0;
|
||||
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
}
|
||||
ec = TALER_JSON_get_error_code (j);
|
||||
switch (ec)
|
||||
{
|
||||
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
|
||||
if (0 > TALER_amount_cmp (&total,
|
||||
&dki->value))
|
||||
{
|
||||
/* recoup MAY have still been possible */
|
||||
/* FIXME: This code may falsely complain, as we do not
|
||||
know that the smallest denomination offered by the
|
||||
exchange is here. We should look at the key
|
||||
structure of ph->exchange, and find the smallest
|
||||
_currently withdrawable_ denomination and check
|
||||
if the value remaining would suffice... */
|
||||
GNUNET_break_op (0);
|
||||
hr.http_status = 0;
|
||||
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
|
||||
if (0 == GNUNET_memcmp (&ph->pk.h_key,
|
||||
&h_denom_pub))
|
||||
{
|
||||
/* invalid proof provided */
|
||||
GNUNET_break_op (0);
|
||||
hr.http_status = 0;
|
||||
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||
break;
|
||||
}
|
||||
/* valid error from exchange */
|
||||
break;
|
||||
default:
|
||||
GNUNET_break_op (0);
|
||||
hr.http_status = 0;
|
||||
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||
break;
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
NULL);
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (ph);
|
||||
return;
|
||||
}
|
||||
case MHD_HTTP_FORBIDDEN:
|
||||
/* Nothing really to verify, exchange says one of the signatures is
|
||||
invalid; as we checked them, this should never happen, we
|
||||
should pass the JSON reply to the application */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
case MHD_HTTP_NOT_FOUND:
|
||||
/* Nothing really to verify, this should never
|
||||
happen, we should pass the JSON reply to the application */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
case MHD_HTTP_GONE:
|
||||
/* Kind of normal: the money was already sent to the merchant
|
||||
(it was too late for the refund). */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
case MHD_HTTP_INTERNAL_SERVER_ERROR:
|
||||
/* Server had an internal issue; we should retry, but this API
|
||||
leaves this to the application */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
default:
|
||||
/* unexpected response code */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Unexpected response code %u/%d for exchange recoup\n",
|
||||
(unsigned int) response_code,
|
||||
(int) hr.ec);
|
||||
GNUNET_break (0);
|
||||
break;
|
||||
}
|
||||
ph->cb (ph->cb_cls,
|
||||
&hr,
|
||||
NULL);
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (ph);
|
||||
}
|
||||
|
||||
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *
|
||||
TALER_EXCHANGE_recoup_refresh (
|
||||
struct TALER_EXCHANGE_Handle *exchange,
|
||||
const struct TALER_EXCHANGE_DenomPublicKey *pk,
|
||||
const struct TALER_DenominationSignature *denom_sig,
|
||||
const struct TALER_PlanchetSecretsP *ps,
|
||||
const struct TALER_Amount *amount,
|
||||
TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
|
||||
void *recoup_cb_cls)
|
||||
{
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
|
||||
struct GNUNET_CURL_Context *ctx;
|
||||
struct TALER_CoinSpendSignatureP coin_sig;
|
||||
struct TALER_CoinSpendPublicKeyP coin_pub;
|
||||
struct TALER_DenominationHash h_denom_pub;
|
||||
json_t *recoup_obj;
|
||||
CURL *eh;
|
||||
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
|
||||
|
||||
GNUNET_assert (GNUNET_YES ==
|
||||
TEAH_handle_is_ready (exchange));
|
||||
GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
|
||||
&coin_pub.eddsa_pub);
|
||||
TALER_denom_pub_hash (&pk->key,
|
||||
&h_denom_pub);
|
||||
TALER_wallet_recoup_refresh_sign (&h_denom_pub,
|
||||
&ps->blinding_key,
|
||||
amount,
|
||||
&ps->coin_priv,
|
||||
&coin_sig);
|
||||
recoup_obj = GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_data_auto ("denom_pub_hash",
|
||||
&h_denom_pub),
|
||||
TALER_JSON_pack_denom_sig ("denom_sig",
|
||||
denom_sig),
|
||||
TALER_JSON_pack_amount ("amount",
|
||||
amount),
|
||||
GNUNET_JSON_pack_data_auto ("coin_sig",
|
||||
&coin_sig),
|
||||
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
|
||||
&ps->blinding_key));
|
||||
|
||||
{
|
||||
char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
|
||||
char *end;
|
||||
|
||||
end = GNUNET_STRINGS_data_to_string (
|
||||
&coin_pub,
|
||||
sizeof (struct TALER_CoinSpendPublicKeyP),
|
||||
pub_str,
|
||||
sizeof (pub_str));
|
||||
*end = '\0';
|
||||
GNUNET_snprintf (arg_str,
|
||||
sizeof (arg_str),
|
||||
"/coins/%s/recoup-refresh",
|
||||
pub_str);
|
||||
}
|
||||
|
||||
ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle);
|
||||
ph->coin_pub = coin_pub;
|
||||
ph->exchange = exchange;
|
||||
ph->pk = *pk;
|
||||
memset (&ph->pk.key,
|
||||
0,
|
||||
sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */
|
||||
ph->cb = recoup_cb;
|
||||
ph->cb_cls = recoup_cb_cls;
|
||||
ph->url = TEAH_path_to_url (exchange,
|
||||
arg_str);
|
||||
if (NULL == ph->url)
|
||||
{
|
||||
json_decref (recoup_obj);
|
||||
GNUNET_free (ph);
|
||||
return NULL;
|
||||
}
|
||||
eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
|
||||
if ( (NULL == eh) ||
|
||||
(GNUNET_OK !=
|
||||
TALER_curl_easy_post (&ph->ctx,
|
||||
eh,
|
||||
recoup_obj)) )
|
||||
{
|
||||
GNUNET_break (0);
|
||||
if (NULL != eh)
|
||||
curl_easy_cleanup (eh);
|
||||
json_decref (recoup_obj);
|
||||
GNUNET_free (ph->url);
|
||||
GNUNET_free (ph);
|
||||
return NULL;
|
||||
}
|
||||
json_decref (recoup_obj);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||
"URL for recoup-refresh: `%s'\n",
|
||||
ph->url);
|
||||
ctx = TEAH_handle_to_context (exchange);
|
||||
ph->job = GNUNET_CURL_job_add2 (ctx,
|
||||
eh,
|
||||
ph->ctx.headers,
|
||||
&handle_recoup_refresh_finished,
|
||||
ph);
|
||||
return ph;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *ph)
|
||||
{
|
||||
if (NULL != ph->job)
|
||||
{
|
||||
GNUNET_CURL_job_cancel (ph->job);
|
||||
ph->job = NULL;
|
||||
}
|
||||
GNUNET_free (ph->url);
|
||||
TALER_curl_easy_post_finished (&ph->ctx);
|
||||
GNUNET_free (ph);
|
||||
}
|
||||
|
||||
|
||||
/* end of exchange_api_recoup_refresh.c */
|
@ -70,6 +70,7 @@ libtalertesting_la_SOURCES = \
|
||||
testing_api_cmd_offline_sign_keys.c \
|
||||
testing_api_cmd_set_wire_fee.c \
|
||||
testing_api_cmd_recoup.c \
|
||||
testing_api_cmd_recoup_refresh.c \
|
||||
testing_api_cmd_refund.c \
|
||||
testing_api_cmd_refresh.c \
|
||||
testing_api_cmd_revoke.c \
|
||||
|
@ -410,7 +410,6 @@ run (void *cls,
|
||||
TALER_TESTING_cmd_recoup ("recoup-1",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-withdraw-coin-1",
|
||||
NULL,
|
||||
"EUR:5"),
|
||||
/**
|
||||
* Re-withdraw from this reserve
|
||||
@ -471,7 +470,6 @@ run (void *cls,
|
||||
TALER_TESTING_cmd_recoup ("recoup-2",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-withdraw-coin-2a",
|
||||
NULL,
|
||||
"EUR:0.5"),
|
||||
TALER_TESTING_cmd_end ()
|
||||
};
|
||||
|
@ -685,7 +685,6 @@ run (void *cls,
|
||||
TALER_TESTING_cmd_recoup ("recoup-1",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-withdraw-coin-1",
|
||||
NULL,
|
||||
"EUR:5"),
|
||||
/* Check the money is back with the reserve */
|
||||
TALER_TESTING_cmd_status ("recoup-reserve-status-1",
|
||||
@ -693,11 +692,11 @@ run (void *cls,
|
||||
"EUR:5.0",
|
||||
MHD_HTTP_OK),
|
||||
/* Recoup-refresh coin to 10 EUR coin */
|
||||
TALER_TESTING_cmd_recoup ("recoup-1b",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-reveal-coin-1b",
|
||||
"recoup-melt-coin-1b",
|
||||
"EUR:5"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1b",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-reveal-coin-1b",
|
||||
"recoup-melt-coin-1b",
|
||||
"EUR:5"),
|
||||
/* melt 10 EUR coin *again* to get 1 EUR refreshed coin */
|
||||
TALER_TESTING_cmd_melt ("recoup-remelt-coin-1a",
|
||||
"recoup-withdraw-coin-1b",
|
||||
@ -843,18 +842,15 @@ run (void *cls,
|
||||
TALER_TESTING_cmd_recoup ("recoup-2x",
|
||||
MHD_HTTP_CONFLICT,
|
||||
"withdraw-coin-1x",
|
||||
NULL,
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-2",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-withdraw-coin-2a",
|
||||
NULL,
|
||||
"EUR:0.5"),
|
||||
/* Idempotency of recoup (withdrawal variant) */
|
||||
TALER_TESTING_cmd_recoup ("recoup-2b",
|
||||
MHD_HTTP_OK,
|
||||
"recoup-withdraw-coin-2a",
|
||||
NULL,
|
||||
"EUR:0.5"),
|
||||
TALER_TESTING_cmd_deposit ("recoup-deposit-revoked",
|
||||
"recoup-withdraw-coin-2b",
|
||||
|
@ -135,11 +135,11 @@ run (void *cls,
|
||||
"refresh-melt-1",
|
||||
MHD_HTTP_OK),
|
||||
/* Try to recoup before it's allowed */
|
||||
TALER_TESTING_cmd_recoup ("recoup-not-allowed",
|
||||
MHD_HTTP_GONE,
|
||||
"refresh-reveal-1#0",
|
||||
"refresh-melt-1",
|
||||
"EUR:0.1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-not-allowed",
|
||||
MHD_HTTP_GONE,
|
||||
"refresh-reveal-1#0",
|
||||
"refresh-melt-1",
|
||||
"EUR:0.1"),
|
||||
/* Make refreshed coin invalid */
|
||||
TALER_TESTING_cmd_revoke ("revoke-2-EUR:5",
|
||||
MHD_HTTP_OK,
|
||||
@ -154,45 +154,44 @@ run (void *cls,
|
||||
TALER_TESTING_cmd_recoup ("recoup-fully-spent",
|
||||
MHD_HTTP_CONFLICT,
|
||||
"withdraw-revocation-coin-2",
|
||||
NULL,
|
||||
"EUR:0.1"),
|
||||
/* Refund coin to original coin */
|
||||
TALER_TESTING_cmd_recoup ("recoup-1a",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#0",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-1b",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#1",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1a",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#0",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1b",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#1",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
/* Repeat recoup to test idempotency */
|
||||
TALER_TESTING_cmd_recoup ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-1c",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-1#2",
|
||||
"refresh-melt-1",
|
||||
"EUR:1"),
|
||||
/* Now we have EUR:3.83 EUR back after 3x EUR:1 in recoups */
|
||||
/* Melt original coin AGAIN, but only create one 0.1 EUR coin;
|
||||
This costs EUR:0.03 in refresh and EUR:01 in withdraw fees,
|
||||
@ -219,17 +218,16 @@ run (void *cls,
|
||||
"withdraw-revocation-coin-1",
|
||||
CONFIG_FILE),
|
||||
/* Refund coin EUR:0.1 to original coin, creating zombie! */
|
||||
TALER_TESTING_cmd_recoup ("recoup-2",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-2",
|
||||
"refresh-melt-2",
|
||||
"EUR:0.1"),
|
||||
TALER_TESTING_cmd_recoup_refresh ("recoup-2",
|
||||
MHD_HTTP_OK,
|
||||
"refresh-reveal-2",
|
||||
"refresh-melt-2",
|
||||
"EUR:0.1"),
|
||||
/* Due to recoup, original coin is now at EUR:3.79 */
|
||||
/* Refund original (now zombie) coin to reserve */
|
||||
TALER_TESTING_cmd_recoup ("recoup-3",
|
||||
MHD_HTTP_OK,
|
||||
"withdraw-revocation-coin-1",
|
||||
NULL,
|
||||
"EUR:3.79"),
|
||||
/* Check the money is back with the reserve */
|
||||
TALER_TESTING_cmd_status ("recoup-reserve-status-1",
|
||||
|
@ -235,26 +235,35 @@ insert_deposit_run (void *cls,
|
||||
deposit.wire_deadline = GNUNET_TIME_relative_to_timestamp (
|
||||
ids->wire_deadline);
|
||||
/* finally, actually perform the DB operation */
|
||||
if ( (GNUNET_OK !=
|
||||
ids->dbc->plugin->start (ids->dbc->plugin->cls,
|
||||
"libtalertesting: insert deposit")) ||
|
||||
(0 >
|
||||
ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls,
|
||||
&deposit.coin)) ||
|
||||
(GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||
ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls,
|
||||
ids->exchange_timestamp,
|
||||
&deposit)) ||
|
||||
(GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
|
||||
ids->dbc->plugin->commit (ids->dbc->plugin->cls)) )
|
||||
{
|
||||
GNUNET_break (0);
|
||||
ids->dbc->plugin->rollback (ids->dbc->plugin->cls);
|
||||
GNUNET_free (deposit.receiver_wire_account);
|
||||
TALER_denom_pub_free (&dpk);
|
||||
TALER_denom_priv_free (&denom_priv);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
uint64_t known_coin_id;
|
||||
struct TALER_DenominationHash dph;
|
||||
struct TALER_AgeHash agh;
|
||||
|
||||
if ( (GNUNET_OK !=
|
||||
ids->dbc->plugin->start (ids->dbc->plugin->cls,
|
||||
"libtalertesting: insert deposit")) ||
|
||||
(0 >
|
||||
ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls,
|
||||
&deposit.coin,
|
||||
&known_coin_id,
|
||||
&dph,
|
||||
&agh)) ||
|
||||
(GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
|
||||
ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls,
|
||||
ids->exchange_timestamp,
|
||||
&deposit)) ||
|
||||
(GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
|
||||
ids->dbc->plugin->commit (ids->dbc->plugin->cls)) )
|
||||
{
|
||||
GNUNET_break (0);
|
||||
ids->dbc->plugin->rollback (ids->dbc->plugin->cls);
|
||||
GNUNET_free (deposit.receiver_wire_account);
|
||||
TALER_denom_pub_free (&dpk);
|
||||
TALER_denom_priv_free (&denom_priv);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TALER_denom_sig_free (&deposit.coin.denom_sig);
|
||||
|
@ -53,12 +53,6 @@ struct RecoupState
|
||||
*/
|
||||
struct TALER_EXCHANGE_RecoupHandle *ph;
|
||||
|
||||
/**
|
||||
* NULL if coin was not refreshed, otherwise reference
|
||||
* to the melt operation underlying @a coin_reference.
|
||||
*/
|
||||
const char *melt_reference;
|
||||
|
||||
/**
|
||||
* If the recoup filled a reserve, this is set to the reserve's public key.
|
||||
*/
|
||||
@ -124,14 +118,12 @@ parse_coin_reference (const char *coin_reference,
|
||||
*
|
||||
* @param cls closure
|
||||
* @param hr HTTP response details
|
||||
* @param reserve_pub public key of the reserve receiving the recoup, NULL if refreshed or on error
|
||||
* @param old_coin_pub public key of the dirty coin, NULL if not refreshed or on error
|
||||
* @param reserve_pub public key of the reserve receiving the recoup
|
||||
*/
|
||||
static void
|
||||
recoup_cb (void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub)
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub)
|
||||
{
|
||||
struct RecoupState *ps = cls;
|
||||
struct TALER_TESTING_Interpreter *is = ps->is;
|
||||
@ -183,44 +175,6 @@ recoup_cb (void *cls,
|
||||
{
|
||||
case MHD_HTTP_OK:
|
||||
/* check old_coin_pub or reserve_pub, respectively */
|
||||
if (NULL != ps->melt_reference)
|
||||
{
|
||||
const struct TALER_TESTING_Command *melt_cmd;
|
||||
const struct TALER_CoinSpendPrivateKeyP *dirty_priv;
|
||||
struct TALER_CoinSpendPublicKeyP oc;
|
||||
|
||||
melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
|
||||
ps->melt_reference);
|
||||
if (NULL == melt_cmd)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_coin_priv (melt_cmd,
|
||||
0,
|
||||
&dirty_priv))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Coin %u not found in command %s\n",
|
||||
0,
|
||||
ps->melt_reference);
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
|
||||
&oc.eddsa_pub);
|
||||
if (0 != GNUNET_memcmp (&oc,
|
||||
old_coin_pub))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const struct TALER_ReservePrivateKeyP *reserve_priv;
|
||||
|
||||
@ -365,8 +319,7 @@ recoup_run (void *cls,
|
||||
coin_sig,
|
||||
&planchet,
|
||||
&ps->reserve_history.amount,
|
||||
NULL != ps->melt_reference,
|
||||
recoup_cb,
|
||||
&recoup_cb,
|
||||
ps);
|
||||
GNUNET_assert (NULL != ps->ph);
|
||||
}
|
||||
@ -432,7 +385,6 @@ struct TALER_TESTING_Command
|
||||
TALER_TESTING_cmd_recoup (const char *label,
|
||||
unsigned int expected_response_code,
|
||||
const char *coin_reference,
|
||||
const char *melt_reference,
|
||||
const char *amount)
|
||||
{
|
||||
struct RecoupState *ps;
|
||||
@ -440,7 +392,6 @@ TALER_TESTING_cmd_recoup (const char *label,
|
||||
ps = GNUNET_new (struct RecoupState);
|
||||
ps->expected_response_code = expected_response_code;
|
||||
ps->coin_reference = coin_reference;
|
||||
ps->melt_reference = melt_reference;
|
||||
if (GNUNET_OK !=
|
||||
TALER_string_to_amount (amount,
|
||||
&ps->reserve_history.amount))
|
||||
|
373
src/testing/testing_api_cmd_recoup_refresh.c
Normal file
373
src/testing/testing_api_cmd_recoup_refresh.c
Normal file
@ -0,0 +1,373 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2018 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 Foundation; either version 3, or
|
||||
(at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public
|
||||
License along with TALER; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/**
|
||||
* @file testing/testing_api_cmd_recoup_refresh.c
|
||||
* @brief Implement the /recoup-refresh test command.
|
||||
* @author Marcello Stanisci
|
||||
*/
|
||||
#include "platform.h"
|
||||
#include "taler_json_lib.h"
|
||||
#include <gnunet/gnunet_curl_lib.h>
|
||||
#include "taler_testing_lib.h"
|
||||
|
||||
|
||||
/**
|
||||
* State for a "pay back" CMD.
|
||||
*/
|
||||
struct RecoupRefreshState
|
||||
{
|
||||
/**
|
||||
* Expected HTTP status code.
|
||||
*/
|
||||
unsigned int expected_response_code;
|
||||
|
||||
/**
|
||||
* Command that offers a reserve private key,
|
||||
* plus a coin to be paid back.
|
||||
*/
|
||||
const char *coin_reference;
|
||||
|
||||
/**
|
||||
* Amount to be recouped.
|
||||
*/
|
||||
struct TALER_Amount amount;
|
||||
|
||||
/**
|
||||
* The interpreter state.
|
||||
*/
|
||||
struct TALER_TESTING_Interpreter *is;
|
||||
|
||||
/**
|
||||
* Handle to the ongoing operation.
|
||||
*/
|
||||
struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
|
||||
|
||||
/**
|
||||
* NULL if coin was not refreshed, otherwise reference
|
||||
* to the melt operation underlying @a coin_reference.
|
||||
*/
|
||||
const char *melt_reference;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parser reference to a coin.
|
||||
*
|
||||
* @param coin_reference of format $LABEL['#' $INDEX]?
|
||||
* @param[out] cref where we return a copy of $LABEL
|
||||
* @param[out] idx where we set $INDEX
|
||||
* @return #GNUNET_SYSERR if $INDEX is present but not numeric
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
parse_coin_reference (const char *coin_reference,
|
||||
char **cref,
|
||||
unsigned int *idx)
|
||||
{
|
||||
const char *index;
|
||||
|
||||
/* We allow command references of the form "$LABEL#$INDEX" or
|
||||
just "$LABEL", which implies the index is 0. Figure out
|
||||
which one it is. */
|
||||
index = strchr (coin_reference, '#');
|
||||
if (NULL == index)
|
||||
{
|
||||
*idx = 0;
|
||||
*cref = GNUNET_strdup (coin_reference);
|
||||
return GNUNET_OK;
|
||||
}
|
||||
*cref = GNUNET_strndup (coin_reference,
|
||||
index - coin_reference);
|
||||
if (1 != sscanf (index + 1,
|
||||
"%u",
|
||||
idx))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
|
||||
index,
|
||||
__FILE__,
|
||||
__LINE__);
|
||||
GNUNET_free (*cref);
|
||||
*cref = NULL;
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the result of the recoup_refresh request: checks whether
|
||||
* the HTTP response code is good, and that the coin that
|
||||
* was paid back belonged to the right old coin.
|
||||
*
|
||||
* @param cls closure
|
||||
* @param hr HTTP response details
|
||||
* @param old_coin_pub public key of the dirty coin
|
||||
*/
|
||||
static void
|
||||
recoup_refresh_cb (void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub)
|
||||
{
|
||||
struct RecoupRefreshState *ps = cls;
|
||||
struct TALER_TESTING_Interpreter *is = ps->is;
|
||||
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
|
||||
char *cref;
|
||||
unsigned int idx;
|
||||
|
||||
ps->ph = NULL;
|
||||
if (ps->expected_response_code != hr->http_status)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Unexpected response code %u/%d to command %s in %s:%u\n",
|
||||
hr->http_status,
|
||||
(int) hr->ec,
|
||||
cmd->label,
|
||||
__FILE__,
|
||||
__LINE__);
|
||||
json_dumpf (hr->reply,
|
||||
stderr,
|
||||
0);
|
||||
fprintf (stderr, "\n");
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GNUNET_OK !=
|
||||
parse_coin_reference (ps->coin_reference,
|
||||
&cref,
|
||||
&idx))
|
||||
{
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
(void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
|
||||
|
||||
GNUNET_free (cref);
|
||||
switch (hr->http_status)
|
||||
{
|
||||
case MHD_HTTP_OK:
|
||||
/* check old_coin_pub */
|
||||
{
|
||||
const struct TALER_TESTING_Command *melt_cmd;
|
||||
const struct TALER_CoinSpendPrivateKeyP *dirty_priv;
|
||||
struct TALER_CoinSpendPublicKeyP oc;
|
||||
|
||||
melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
|
||||
ps->melt_reference);
|
||||
if (NULL == melt_cmd)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_coin_priv (melt_cmd,
|
||||
0,
|
||||
&dirty_priv))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Coin %u not found in command %s\n",
|
||||
0,
|
||||
ps->melt_reference);
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
|
||||
&oc.eddsa_pub);
|
||||
if (0 != GNUNET_memcmp (&oc,
|
||||
old_coin_pub))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MHD_HTTP_NOT_FOUND:
|
||||
break;
|
||||
case MHD_HTTP_CONFLICT:
|
||||
break;
|
||||
default:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
||||
"Unmanaged HTTP status code %u/%d.\n",
|
||||
hr->http_status,
|
||||
(int) hr->ec);
|
||||
break;
|
||||
}
|
||||
TALER_TESTING_interpreter_next (is);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
*
|
||||
* @param cls closure.
|
||||
* @param cmd the command to execute.
|
||||
* @param is the interpreter state.
|
||||
*/
|
||||
static void
|
||||
recoup_refresh_run (void *cls,
|
||||
const struct TALER_TESTING_Command *cmd,
|
||||
struct TALER_TESTING_Interpreter *is)
|
||||
{
|
||||
struct RecoupRefreshState *ps = cls;
|
||||
const struct TALER_TESTING_Command *coin_cmd;
|
||||
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
|
||||
const union TALER_DenominationBlindingKeyP *blinding_key;
|
||||
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
|
||||
const struct TALER_DenominationSignature *coin_sig;
|
||||
struct TALER_PlanchetSecretsP planchet;
|
||||
char *cref;
|
||||
unsigned int idx;
|
||||
|
||||
ps->is = is;
|
||||
if (GNUNET_OK !=
|
||||
parse_coin_reference (ps->coin_reference,
|
||||
&cref,
|
||||
&idx))
|
||||
{
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
|
||||
cref);
|
||||
GNUNET_free (cref);
|
||||
|
||||
if (NULL == coin_cmd)
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_coin_priv (coin_cmd,
|
||||
idx,
|
||||
&coin_priv))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_blinding_key (coin_cmd,
|
||||
idx,
|
||||
&blinding_key))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
planchet.coin_priv = *coin_priv;
|
||||
planchet.blinding_key = *blinding_key;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_denom_pub (coin_cmd,
|
||||
idx,
|
||||
&denom_pub))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_TESTING_get_trait_denom_sig (coin_cmd,
|
||||
idx,
|
||||
&coin_sig))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
TALER_TESTING_interpreter_fail (is);
|
||||
return;
|
||||
}
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Trying to recoup_refresh denomination '%s'\n",
|
||||
TALER_B2S (&denom_pub->h_key));
|
||||
|
||||
ps->ph = TALER_EXCHANGE_recoup_refresh (is->exchange,
|
||||
denom_pub,
|
||||
coin_sig,
|
||||
&planchet,
|
||||
&ps->amount,
|
||||
recoup_refresh_cb,
|
||||
ps);
|
||||
GNUNET_assert (NULL != ps->ph);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cleanup the "recoup_refresh" CMD state, and possibly cancel
|
||||
* a pending operation thereof.
|
||||
*
|
||||
* @param cls closure.
|
||||
* @param cmd the command which is being cleaned up.
|
||||
*/
|
||||
static void
|
||||
recoup_refresh_cleanup (void *cls,
|
||||
const struct TALER_TESTING_Command *cmd)
|
||||
{
|
||||
struct RecoupRefreshState *ps = cls;
|
||||
if (NULL != ps->ph)
|
||||
{
|
||||
TALER_EXCHANGE_recoup_refresh_cancel (ps->ph);
|
||||
ps->ph = NULL;
|
||||
}
|
||||
GNUNET_free (ps);
|
||||
}
|
||||
|
||||
|
||||
struct TALER_TESTING_Command
|
||||
TALER_TESTING_cmd_recoup_refresh (const char *label,
|
||||
unsigned int expected_response_code,
|
||||
const char *coin_reference,
|
||||
const char *melt_reference,
|
||||
const char *amount)
|
||||
{
|
||||
struct RecoupRefreshState *ps;
|
||||
|
||||
ps = GNUNET_new (struct RecoupRefreshState);
|
||||
ps->expected_response_code = expected_response_code;
|
||||
ps->coin_reference = coin_reference;
|
||||
ps->melt_reference = melt_reference;
|
||||
if (GNUNET_OK !=
|
||||
TALER_string_to_amount (amount,
|
||||
&ps->amount))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Failed to parse amount `%s' at %s\n",
|
||||
amount,
|
||||
label);
|
||||
GNUNET_assert (0);
|
||||
}
|
||||
{
|
||||
struct TALER_TESTING_Command cmd = {
|
||||
.cls = ps,
|
||||
.label = label,
|
||||
.run = &recoup_refresh_run,
|
||||
.cleanup = &recoup_refresh_cleanup
|
||||
};
|
||||
|
||||
return cmd;
|
||||
}
|
||||
}
|
@ -573,7 +573,6 @@ link_cb (void *cls,
|
||||
const struct TALER_DenominationSignature *sigs,
|
||||
const struct TALER_DenominationPublicKey *pubs)
|
||||
{
|
||||
|
||||
struct RefreshLinkState *rls = cls;
|
||||
const struct TALER_TESTING_Command *reveal_cmd;
|
||||
struct TALER_TESTING_Command *link_cmd = &rls->is->commands[rls->is->ip];
|
||||
|
@ -107,6 +107,7 @@ TALER_wallet_deposit_verify (
|
||||
void
|
||||
TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
|
||||
const struct TALER_TransferPublicKeyP *transfer_pub,
|
||||
// FIXME: consider passing hash!
|
||||
const void *coin_ev,
|
||||
size_t coin_ev_size,
|
||||
const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
|
||||
@ -132,6 +133,7 @@ enum GNUNET_GenericReturnValue
|
||||
TALER_wallet_link_verify (
|
||||
const struct TALER_DenominationHash *h_denom_pub,
|
||||
const struct TALER_TransferPublicKeyP *transfer_pub,
|
||||
// FIXME: consider passing hash!
|
||||
const void *coin_ev,
|
||||
size_t coin_ev_size,
|
||||
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
|
||||
@ -202,6 +204,53 @@ TALER_wallet_recoup_sign (
|
||||
}
|
||||
|
||||
|
||||
enum GNUNET_GenericReturnValue
|
||||
TALER_wallet_recoup_refresh_verify (
|
||||
const struct TALER_DenominationHash *h_denom_pub,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const struct TALER_CoinSpendPublicKeyP *coin_pub,
|
||||
const struct TALER_CoinSpendSignatureP *coin_sig)
|
||||
{
|
||||
struct TALER_RecoupRequestPS pr = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
|
||||
.purpose.size = htonl (sizeof (pr)),
|
||||
.h_denom_pub = *h_denom_pub,
|
||||
.coin_blind = *coin_bks
|
||||
};
|
||||
|
||||
TALER_amount_hton (&pr.recoup_amount,
|
||||
requested_amount);
|
||||
return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH,
|
||||
&pr,
|
||||
&coin_sig->eddsa_signature,
|
||||
&coin_pub->eddsa_pub);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TALER_wallet_recoup_refresh_sign (
|
||||
const struct TALER_DenominationHash *h_denom_pub,
|
||||
const union TALER_DenominationBlindingKeyP *coin_bks,
|
||||
const struct TALER_Amount *requested_amount,
|
||||
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
|
||||
struct TALER_CoinSpendSignatureP *coin_sig)
|
||||
{
|
||||
struct TALER_RecoupRequestPS pr = {
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
|
||||
.purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)),
|
||||
.h_denom_pub = *h_denom_pub,
|
||||
.coin_blind = *coin_bks
|
||||
};
|
||||
|
||||
TALER_amount_hton (&pr.recoup_amount,
|
||||
requested_amount);
|
||||
GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
|
||||
&pr,
|
||||
&coin_sig->eddsa_signature);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TALER_wallet_melt_sign (
|
||||
const struct TALER_Amount *amount_with_fee,
|
||||
|
Loading…
Reference in New Issue
Block a user