aboutsummaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_db.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_db.c
parent2c14d338704f4574055c4b5c51d8a79dd2e22345 (diff)
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_db.c')
-rw-r--r--src/exchange/taler-exchange-httpd_db.c313
1 files changed, 16 insertions, 297 deletions
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 388679c3..3600d793 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;
}