summaryrefslogtreecommitdiff
path: root/src/exchange
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-12-25 13:56:33 +0100
committerChristian Grothoff <christian@grothoff.org>2021-12-25 13:56:40 +0100
commit87376e02eba3f5c2cf83a493446dee0c300565a4 (patch)
tree18103edb2bdf2b29a773cce2de596b06d8265abb /src/exchange
parent2c14d338704f4574055c4b5c51d8a79dd2e22345 (diff)
downloadexchange-87376e02eba3f5c2cf83a493446dee0c300565a4.tar.gz
exchange-87376e02eba3f5c2cf83a493446dee0c300565a4.tar.bz2
exchange-87376e02eba3f5c2cf83a493446dee0c300565a4.zip
protocol v12 changes (/recoup split, signature changes) plus database sharding plus O(n^2)=>O(n) worst-case complexity reduction on coin balance checks
Diffstat (limited to 'src/exchange')
-rw-r--r--src/exchange/Makefile.am1
-rw-r--r--src/exchange/taler-exchange-httpd.c5
-rw-r--r--src/exchange/taler-exchange-httpd_db.c313
-rw-r--r--src/exchange/taler-exchange-httpd_db.h31
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c149
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c2
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c254
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.c20
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.h5
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.c411
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.h46
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c320
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c383
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c296
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c50
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h6
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c42
17 files changed, 934 insertions, 1400 deletions
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index c20378c22..e7688f735 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -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 \
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 0535a54ea..526c93588 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -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"
@@ -257,6 +258,10 @@ handle_post_coins (struct TEH_RequestContext *rc,
.handler = &TEH_handler_recoup
},
{
+ .op = "recoup-refresh",
+ .handler = &TEH_handler_recoup_refresh
+ },
+ {
.op = "refund",
.handler = &TEH_handler_refund
},
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 388679c38..3600d7931 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -30,55 +30,6 @@
/**
- * 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 (
- connection,
- coin_pub,
- coin_value,
- tl,
- op_cost,
- &coin_residual);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* 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 (
+ case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
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");
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ &coin->coin_pub);
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 (
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT:
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
- NULL);
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH,
+ &coin->coin_pub);
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;
}
diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h
index 5ee3b41d5..7c954ffe1 100644
--- a/src/exchange/taler-exchange-httpd_db.h
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -32,45 +32,18 @@
*
* @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
* NOT queue a MHD response. IF it returns an hard error, the
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 11094d11c..84741b5c3 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -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;
-
- /* begin optimistically: assume this is a new deposit */
- qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
- dc->exchange_timestamp,
- deposit);
+ bool balance_ok;
+ bool in_conflict;
+
+ 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,
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
index a9cd864aa..5d7476771 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -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"
/**
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index e4b2af290..54f1385d7 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -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
@@ -90,6 +90,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.
*/
struct TALER_Amount coin_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;
+ }
+ 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;
}
- 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);
+ /* 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;
+ }
}
diff --git a/src/exchange/taler-exchange-httpd_metrics.c b/src/exchange/taler-exchange-httpd_metrics.c
index 2ea889ff0..8c8cd343a 100644
--- a/src/exchange/taler-exchange-httpd_metrics.c
+++ b/src/exchange/taler-exchange-httpd_metrics.c
@@ -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);
diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h
index 39e463169..55e5372a7 100644
--- a/src/exchange/taler-exchange-httpd_metrics.h
+++ b/src/exchange/taler-exchange-httpd_metrics.h
@@ -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! */
};
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c
new file mode 100644
index 000000000..acadc999e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -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 */
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.h b/src/exchange/taler-exchange-httpd_recoup-refresh.h
new file mode 100644
index 000000000..25c12fac3
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.h
@@ -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
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index 58495e530..28e81f9ec 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -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,45 +66,29 @@ 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;
/**
- * Set by #recoup_transaction to the timestamp when the recoup
- * was accepted.
+ * Unique ID of the coin in the known_coins table.
*/
- struct GNUNET_TIME_Timestamp now;
+ uint64_t known_coin_id;
/**
- * true if the client claims the coin originated from a refresh.
+ * Set by #recoup_transaction to the timestamp when the recoup
+ * was accepted.
*/
- bool refreshed;
+ struct GNUNET_TIME_Timestamp now;
};
-// 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);
+ /* Finally, store new refund data */
+ pc->now = GNUNET_TIME_timestamp_get ();
+ 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)
- {
- 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");
- }
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_recoup");
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))
+ if (internal_failure)
{
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);
+ *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 (TALER_amount_is_zero (&pc->amount))
+ if (! recoup_ok)
{
- /* 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);
- 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));
+ *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;
}
-
- /* 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);
- }
- 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");
- }
- return qs;
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ 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_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.reserve_pub,
+ &pc.reserve_out_serial_id);
+ if (0 > qs)
{
- 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;
}
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 36362a234..1cd28048b 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -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;
@@ -121,157 +114,34 @@ 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_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];
+ return GNUNET_SYSERR;
}
- 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' */
- }
- 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;
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_refresh_reveal");
+ goto cleanup;
}
- /* 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 ()
};
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index a1ef50a32..a3b066280 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -82,6 +82,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
* matching /deposit or if it is too late to do the refund.
@@ -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;
-
- tl = NULL;
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- &refund->coin.coin_pub,
- GNUNET_NO,
- &tl);
+ bool not_found;
+ bool refund_ok;
+ bool conflict;
+ bool gone;
+
+ /* Finally, store new refund data */
+ qs = TEH_plugin->do_refund (TEH_plugin->cls,
+ refund,
+ &rctx->deposit_fee,
+ rctx->known_coin_id,
+ &not_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;
}
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 8c24efc9d..5739e6709 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -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));
diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h
index e15901120..db2286ffa 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -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);
/**
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
index dfd10b5e4..2e53803a9 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -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;