diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-12-25 13:56:33 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-12-25 13:56:40 +0100 |
commit | 87376e02eba3f5c2cf83a493446dee0c300565a4 (patch) | |
tree | 18103edb2bdf2b29a773cce2de596b06d8265abb /src/exchange/taler-exchange-httpd_db.c | |
parent | 2c14d338704f4574055c4b5c51d8a79dd2e22345 (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.c | 313 |
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; } |