diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_db.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_db.c | 159 |
1 files changed, 81 insertions, 78 deletions
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 3c6936497..6fec3fee4 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017 Taler Systems SA + Copyright (C) 2014-2017, 2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -19,45 +19,35 @@ * @author Christian Grothoff */ #include "platform.h" +#include <gnunet/gnunet_db_lib.h> #include <pthread.h> #include <jansson.h> #include <gnunet/gnunet_json_lib.h> +#include "taler_error_codes.h" +#include "taler_exchangedb_plugin.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler_exchangedb_lib.h" +#include "taler-exchange-httpd_db.h" #include "taler-exchange-httpd_responses.h" -/** - * How often should we retry a transaction before giving up - * (for transactions resulting in serialization/dead locks only). - * - * The current value is likely too high for production. We might want to - * benchmark good values once we have a good database setup. The code is - * expected to work correctly with any positive value, albeit inefficiently if - * we too aggressively force clients to retry the HTTP request merely because - * we have database serialization issues. - */ -#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_DenominationHashP h_denom_pub; + struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}}; /* make sure coin is 'known' in database */ cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls, - coin); + coin, + known_coin_id, + &h_denom_pub, + &h_age_commitment); switch (cks) { case TALER_EXCHANGEDB_CKS_ADDED: @@ -73,60 +63,62 @@ 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; - } + case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT: + /* The exchange has a seen this coin before, but with a different denomination. + * Get the corresponding signature and sent it to the client as proof */ + { + struct + { + struct TALER_DenominationPublicKey pub; + struct TALER_DenominationSignature sig; + } prev_denom = {0}; - { - struct TALER_EXCHANGEDB_TransactionList *tl; - enum GNUNET_DB_QueryStatus qs; + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + TEH_plugin->get_signature_for_known_coin (TEH_plugin->cls, + &coin->coin_pub, + &prev_denom.pub, + &prev_denom.sig)) + { + /* There _should_ have been a result, because + * we ended here due to a conflict! */ + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } - 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; + *mhd_ret = TEH_RESPONSE_reply_coin_denomination_conflict ( + connection, + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY, + &coin->coin_pub, + &prev_denom.pub, + &prev_denom.sig); + + return GNUNET_DB_STATUS_HARD_ERROR; } - *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); + case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL: + case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL: + case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS: + *mhd_ret = TEH_RESPONSE_reply_coin_age_commitment_conflict ( + connection, + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + cks, + &h_denom_pub, + &coin->coin_pub, + &h_age_commitment); return GNUNET_DB_STATUS_HARD_ERROR; } + GNUNET_assert (0); + return GNUNET_DB_STATUS_HARD_ERROR; } -/** - * Run a database transaction for @a connection. - * Starts a transaction and calls @a cb. Upon success, - * attempts to commit the transaction. Upon soft failures, - * retries @a cb a few times. Upon hard or persistent soft - * errors, generates an error message for @a connection. - * - * @param connection MHD connection to run @a cb for, can be NULL - * @param name name of the transaction (for debugging) - * @param[out] mhd_ret set to MHD response code, if transaction failed; - * NULL if we are not running with a @a connection and thus - * must not queue MHD replies - * @param cb callback implementing transaction logic - * @param cb_cls closure for @a cb, must be read-only! - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ enum GNUNET_GenericReturnValue TEH_DB_run_transaction (struct MHD_Connection *connection, const char *name, + enum TEH_MetricTypeRequest mt, MHD_RESULT *mhd_ret, TEH_DB_TransactionCallback cb, void *cb_cls) @@ -144,6 +136,8 @@ TEH_DB_run_transaction (struct MHD_Connection *connection, NULL); return GNUNET_SYSERR; } + GNUNET_assert (mt < TEH_MT_REQUEST_COUNT); + TEH_METRICS_num_requests[mt]++; for (unsigned int retries = 0; retries < MAX_TRANSACTION_COMMIT_RETRIES; retries++) @@ -166,26 +160,35 @@ TEH_DB_run_transaction (struct MHD_Connection *connection, connection, mhd_ret); if (0 > qs) + { TEH_plugin->rollback (TEH_plugin->cls); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - return GNUNET_SYSERR; - if (0 <= qs) - qs = TEH_plugin->commit (TEH_plugin->cls); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + return GNUNET_SYSERR; + } + else { - if (NULL != mhd_ret) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - NULL); - return GNUNET_SYSERR; + qs = TEH_plugin->commit (TEH_plugin->cls); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + TEH_plugin->rollback (TEH_plugin->cls); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); + return GNUNET_SYSERR; + } + if (0 > qs) + TEH_plugin->rollback (TEH_plugin->cls); } /* make sure callback did not violate invariants! */ GNUNET_assert ( (NULL == mhd_ret) || - (-1 == *mhd_ret) ); + (-1 == (int) *mhd_ret) ); if (0 <= qs) return GNUNET_OK; + TEH_METRICS_num_conflict[mt]++; } + TEH_plugin->rollback (TEH_plugin->cls); TALER_LOG_ERROR ("Transaction `%s' commit failed %u times\n", name, MAX_TRANSACTION_COMMIT_RETRIES); |