summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_recoup.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_recoup.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_recoup.c')
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c320
1 files changed, 83 insertions, 237 deletions
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;
}