diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-02-29 16:42:10 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-02-29 16:42:10 +0100 |
commit | 0a2b049864c8dae0c53c203d46fca89e0e66849d (patch) | |
tree | 3e836be37902320a4a3a099ee62d960198057952 /src/exchange/taler-exchange-httpd_refresh_melt.c | |
parent | de9ab28ab9e55597baf2ca32194ec65b441f0f36 (diff) | |
download | exchange-0a2b049864c8dae0c53c203d46fca89e0e66849d.tar.gz exchange-0a2b049864c8dae0c53c203d46fca89e0e66849d.tar.bz2 exchange-0a2b049864c8dae0c53c203d46fca89e0e66849d.zip |
big rename fest related to #6067 API renaming
Diffstat (limited to 'src/exchange/taler-exchange-httpd_refresh_melt.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_refresh_melt.c | 628 |
1 files changed, 0 insertions, 628 deletions
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c deleted file mode 100644 index 9d92a4cee..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_melt.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2019 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 - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_refresh_melt.c - * @brief Handle /refresh/melt requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <jansson.h> -#include <microhttpd.h> -#include "taler_json_lib.h" -#include "taler_mhd_lib.h" -#include "taler-exchange-httpd_mhd.h" -#include "taler-exchange-httpd_refresh_melt.h" -#include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keystate.h" - - -/** - * Send a response for a failed "/refresh/melt" 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 melt. Thus, the exchange - * refuses the melt 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 int -reply_refresh_melt_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_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, - "Failed to compile transaction history"); - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", - "hint", - "insufficient funds", - "code", - (json_int_t) - TALER_EC_REFRESH_MELT_INSUFFICIENT_FUNDS, - "coin_pub", - GNUNET_JSON_from_data_auto (coin_pub), - "original_value", - TALER_JSON_from_amount (coin_value), - "residual_value", - TALER_JSON_from_amount (residual), - "requested_value", - TALER_JSON_from_amount (requested), - "history", - history); -} - - -/** - * Send a response to a "/refresh/melt" request. - * - * @param connection the connection to send the response to - * @param rc value the client commited to - * @param noreveal_index which index will the client not have to reveal - * @return a MHD status code - */ -static int -reply_refresh_melt_success (struct MHD_Connection *connection, - const struct TALER_RefreshCommitmentP *rc, - uint32_t noreveal_index) -{ - struct TALER_RefreshMeltConfirmationPS body; - struct TALER_ExchangePublicKeyP pub; - struct TALER_ExchangeSignatureP sig; - json_t *sig_json; - - body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); - body.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); - body.rc = *rc; - body.noreveal_index = htonl (noreveal_index); - if (GNUNET_OK != - TEH_KS_sign (&body.purpose, - &pub, - &sig)) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } - sig_json = GNUNET_JSON_from_data_auto (&sig); - GNUNET_assert (NULL != sig_json); - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:i, s:o, s:o}", - "noreveal_index", (int) noreveal_index, - "exchange_sig", sig_json, - "exchange_pub", - GNUNET_JSON_from_data_auto (&pub)); -} - - -/** - * Context for the /refresh/melt operation. - */ -struct RefreshMeltContext -{ - - /** - * noreveal_index is only initialized during - * #refresh_melt_transaction(). - */ - struct TALER_EXCHANGEDB_RefreshSession refresh_session; - - /** - * Information about the @e coin's value. - */ - struct TALER_Amount coin_value; - - /** - * Information about the @e coin's refresh fee. - */ - struct TALER_Amount coin_refresh_fee; - - /** - * Set to #GNUNET_YES if this coin's denomination was revoked and the operation - * is thus only allowed for zombie coins where the transaction - * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP. - */ - int zombie_required; - -}; - - -/** - * Check that the coin has sufficient funds left for the selected - * melt operation. - * - * @param connection the connection to send errors to - * @param session the database connection - * @param[in,out] rmc melt context - * @param[out] mhd_ret status code to return to MHD on hard error - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -refresh_check_melt (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - struct RefreshMeltContext *rmc, - int *mhd_ret) -{ - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount spent; - enum GNUNET_DB_QueryStatus qs; - - /* Start with cost of this melt transaction */ - spent = rmc->refresh_session.amount_with_fee; - - /* add historic transaction costs of this coin, including recoups as - we might be a zombie coin */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &rmc->refresh_session.coin.coin_pub, - GNUNET_YES, - &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_REFRESH_MELT_DB_FETCH_ERROR, - "failed to fetch old coin history"); - return qs; - } - if (rmc->zombie_required) - { - for (struct TALER_EXCHANGEDB_TransactionList *tp = tl; - NULL != tp; - tp = tp->next) - { - if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type) - { - rmc->zombie_required = GNUNET_NO; /* was satisfied! */ - break; - } - } - if (rmc->zombie_required) - { - /* zombie status not satisfied */ - GNUNET_break (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_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE, - "denomination expired"); - 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_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED, - "failed to compute coin transaction history"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Refuse to refresh when the coin's value is insufficient - for the cost of all transactions. */ - if (TALER_amount_cmp (&rmc->coin_value, - &spent) < 0) - { - struct TALER_Amount coin_residual; - - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&coin_residual, - &spent, - &rmc->refresh_session.amount_with_fee)); - *mhd_ret = reply_refresh_melt_insufficient_funds (connection, - &rmc->refresh_session.coin - .coin_pub, - &rmc->coin_value, - tl, - &rmc->refresh_session. - amount_with_fee, - &coin_residual); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* we're good, coin has sufficient funds to be melted */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * Execute a "/refresh/melt". We have been given a list of valid - * coins and a request to melt them into the given @a - * refresh_session_pub. Check that the coins all have the required - * value left and if so, store that they have been melted and confirm - * the melting operation to the client. - * - * If it returns a non-error code, the transaction logic MUST NOT - * queue a MHD response. IF it returns an hard error, the transaction - * logic MUST queue a MHD response and set @a mhd_ret. If it returns - * the soft error code, the function MAY be called again to retry and - * MUST not queue a MHD response. - * - * @param cls our `struct RefreshMeltContext` - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_melt_transaction (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct RefreshMeltContext *rmc = cls; - enum GNUNET_DB_QueryStatus qs; - uint32_t noreveal_index; - - /* Check if we already created such a session */ - qs = TEH_plugin->get_melt_index (TEH_plugin->cls, - session, - &rmc->refresh_session.rc, - &noreveal_index); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - TALER_LOG_DEBUG ("Found already-melted coin\n"); - *mhd_ret = reply_refresh_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_REFRESH_MELT_DB_FETCH_ERROR, - "failed to fetch melt index"); - return qs; - } - - /* check coin has enough funds remaining on it to cover melt cost */ - qs = refresh_check_melt (connection, - session, - rmc, - mhd_ret); - if (0 > qs) - return qs; - - /* 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, - session, - &rmc->refresh_session))) - { - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR, - "failed to persist melt data"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * Handle a "/refresh/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 #refresh_melt_transaction(). - * - * @param connection the MHD connection to handle - * @param[in,out] rmc details about the melt request - * @return MHD result code - */ -static int -handle_refresh_melt (struct MHD_Connection *connection, - struct RefreshMeltContext *rmc) -{ - - /* verify signature of coin for melt operation */ - { - struct TALER_RefreshMeltCoinAffirmationPS body; - - body.purpose.size = htonl (sizeof (struct - TALER_RefreshMeltCoinAffirmationPS)); - body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - body.rc = rmc->refresh_session.rc; - TALER_amount_hton (&body.amount_with_fee, - &rmc->refresh_session.amount_with_fee); - TALER_amount_hton (&body.melt_fee, - &rmc->coin_refresh_fee); - body.coin_pub = rmc->refresh_session.coin.coin_pub; - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, - &body.purpose, - &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_REFRESH_MELT_COIN_SIGNATURE_INVALID, - "confirm_sig"); - } - } - - /* run transaction */ - { - int mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "run melt", - &mhd_ret, - &refresh_melt_transaction, - rmc)) - return mhd_ret; - } - - /* generate ordinary response */ - return reply_refresh_melt_success (connection, - &rmc->refresh_session.rc, - rmc->refresh_session.noreveal_index); -} - - -/** - * Check for information about the melted coin's denomination, - * extracting its validity status and fee structure. - * - * @param connection HTTP connection we are handling - * @param rmc parsed request information - * @return MHD status code - */ -static int -check_for_denomination_key (struct MHD_Connection *connection, - struct RefreshMeltContext *rmc) -{ - struct TEH_KS_StateHandle *key_state; - - key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); - if (NULL == key_state) - { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } - - { - /* Baseline: check if deposits/refreshs are generally - simply still allowed for this denomination */ - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - unsigned int hc; - enum TALER_ErrorCode ec; - - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_DEPOSIT, - &ec, - &hc); - /* Consider case that denomination was revoked but - this coin was already seen and thus refresh is OK. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_RECOUP, - &ec, - &hc); - if (NULL != dki) - { - struct GNUNET_HashCode denom_hash; - enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls, - NULL, - &rmc->refresh_session.coin. - coin_pub, - &denom_hash); - if (0 > qs) - { - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, - "failed to find information about old coin"); - } - GNUNET_break (0 == - GNUNET_memcmp (&denom_hash, - &rmc->refresh_session.coin.denom_pub_hash)); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - /* We never saw this coin before, so _this_ justification is not OK */ - dki = NULL; - } - } - } - - /* Consider the case that the denomination expired for deposits, - but /refresh/recoup refilled the balance of the 'zombie' coin - and we should thus allow the refresh during the legal period. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_ZOMBIE, - &ec, - &hc); - if (NULL != dki) - rmc->zombie_required = GNUNET_YES; - } - if (NULL == dki) - { - TEH_KS_release (key_state); - TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); - return TALER_MHD_reply_with_error (connection, - hc, - ec, - "unknown denomination"); - } - TALER_amount_ntoh (&rmc->coin_refresh_fee, - &dki->issue.properties.fee_refresh); - TALER_amount_ntoh (&rmc->coin_value, - &dki->issue.properties.value); - if (GNUNET_OK != - TALER_test_coin_valid (&rmc->refresh_session.coin, - &dki->denom_pub)) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, - "denom_sig"); - } - } - TEH_KS_release (key_state); - - /* run actual logic, now that the request was parsed */ - /* make sure coin is 'known' in database */ - { - struct TEH_DB_KnowCoinContext kcc; - int mhd_ret; - - kcc.coin = &rmc->refresh_session.coin; - kcc.connection = connection; - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "know coin for refresh-melt", - &mhd_ret, - &TEH_DB_know_coin_transaction, - &kcc)) - return mhd_ret; - } - - /* sanity-check that "total melt amount > melt fee" */ - if (TALER_amount_cmp (&rmc->coin_refresh_fee, - &rmc->refresh_session.amount_with_fee) > 0) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, - "melt amount smaller than melting fee"); - } - return handle_refresh_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_refresh_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 - */ -int -TEH_REFRESH_handler_melt (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *root) -{ - struct RefreshMeltContext rmc; - int res; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_signature ("denom_sig", - &rmc.refresh_session.coin.denom_sig), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &rmc.refresh_session.coin.denom_pub_hash), - GNUNET_JSON_spec_fixed_auto ("confirm_sig", - &rmc.refresh_session.coin_sig), - TALER_JSON_spec_amount ("value_with_fee", - &rmc.refresh_session.amount_with_fee), - GNUNET_JSON_spec_fixed_auto ("rc", - &rmc.refresh_session.rc), - GNUNET_JSON_spec_end () - }; - - memset (&rmc, - 0, - sizeof (rmc)); - rmc.refresh_session.coin.coin_pub = *coin_pub; - res = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - - res = check_for_denomination_key (connection, - &rmc); - GNUNET_JSON_parse_free (spec); - return res; -} - - -/* end of taler-exchange-httpd_refresh_melt.c */ |