summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_melt.c
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/taler-exchange-httpd_melt.c
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/taler-exchange-httpd_melt.c')
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c254
1 files changed, 105 insertions, 149 deletions
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;
+ }
}