diff options
Diffstat (limited to 'src')
27 files changed, 175 insertions, 1740 deletions
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 86829edda..12ea34e13 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -182,8 +182,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \ - taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \ - taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h + taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h taler_exchange_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 5bc118e3e..46d5833a9 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -69,7 +69,6 @@ #include "taler-exchange-httpd_reserves_purse.h" #include "taler-exchange-httpd_terms.h" #include "taler-exchange-httpd_transfers_get.h" -#include "taler-exchange-httpd_withdraw.h" #include "taler_exchangedb_lib.h" #include "taler_exchangedb_plugin.h" #include "taler_extensions.h" @@ -747,10 +746,6 @@ handle_post_reserves (struct TEH_RequestContext *rc, .handler = &TEH_handler_age_withdraw }, { - .op = "withdraw", - .handler = &TEH_handler_withdraw - }, - { .op = "purse", .handler = &TEH_handler_reserves_purse }, diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c index 47cff626f..4a7d6b1a8 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw.c @@ -399,8 +399,8 @@ denomination_is_valid ( * @param[out] denom_serials On success, will be filled with the serial-id's of the denomination keys. Caller must deallocate. * @param[out] amount_with_fee On success, will contain the committed amount including fees * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. - * @return GNUNET_OK if the denominations are valid and support age-restriction - * GNUNET_SYSERR otherwise + * @return #GNUNET_OK if the denominations are valid and support age-restriction + * #GNUNET_SYSERR otherwise */ static enum GNUNET_GenericReturnValue are_denominations_valid ( @@ -737,12 +737,14 @@ age_withdraw_transaction (void *cls, bool conflict = false; uint16_t allowed_maximum_age = 0; uint32_t reserve_birthday = 0; + struct TALER_Amount reserve_balance; qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls, &awc->commitment, awc->now, &found, &balance_ok, + &reserve_balance, &age_ok, &allowed_maximum_age, &reserve_birthday, @@ -755,14 +757,14 @@ age_withdraw_transaction (void *cls, "do_age_withdraw"); return qs; } - else if (! found) + if (! found) { *mhd_ret = TALER_MHD_reply_with_ec (connection, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - else if (! age_ok) + if (! age_ok) { enum TALER_ErrorCode ec = TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE; @@ -779,19 +781,20 @@ age_withdraw_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } - else if (! balance_ok) + if (! balance_ok) { TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS, + &reserve_balance, &awc->commitment.amount_with_fee, &awc->commitment.reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - else if (conflict) + if (conflict) { /* do_age_withdraw signaled a conflict, so there MUST be an entry * in the DB. Put that into the response */ diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c index 38a7f43c5..fe2108763 100644 --- a/src/exchange/taler-exchange-httpd_batch-withdraw.c +++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c @@ -312,6 +312,7 @@ batch_withdraw_transaction (void *cls, bool balance_ok = false; bool age_ok = false; uint16_t allowed_maximum_age = 0; + struct TALER_Amount reserve_balance; char *kyc_required; struct TALER_PaytoHashP reserve_h_payto; @@ -476,6 +477,7 @@ batch_withdraw_transaction (void *cls, TEH_age_restriction_enabled, &found, &balance_ok, + &reserve_balance, &age_ok, &allowed_maximum_age, &ruuid); @@ -521,6 +523,7 @@ batch_withdraw_transaction (void *cls, *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, + &reserve_balance, &wc->batch_total, wc->reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; @@ -553,7 +556,7 @@ batch_withdraw_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "do_withdraw"); + "do_batch_withdraw_insert"); return qs; } if (denom_unknown) diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index 50487990a..5aadc9e40 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -188,6 +188,7 @@ reserve_open_transaction (void *cls, { struct ReserveOpenContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount reserve_balance; for (unsigned int i = 0; i<rsc->payments_len; i++) { @@ -258,6 +259,7 @@ reserve_open_transaction (void *cls, &rsc->gf->fees.account, /* outputs */ &rsc->no_funds, + &reserve_balance, &rsc->open_cost, &rsc->reserve_expiration); switch (qs) @@ -289,6 +291,7 @@ reserve_open_transaction (void *cls, = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS, + &reserve_balance, &rsc->reserve_payment, rsc->reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 2d5d8dce9..322da3877 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -181,31 +181,16 @@ MHD_RESULT TEH_RESPONSE_reply_reserve_insufficient_balance ( struct MHD_Connection *connection, enum TALER_ErrorCode ec, + const struct TALER_Amount *reserve_balance, const struct TALER_Amount *balance_required, const struct TALER_ReservePublicKeyP *reserve_pub) { - struct TALER_Amount balance; - enum GNUNET_DB_QueryStatus qs; - - // FIXME: pass balance as argument to this function, - // instead of getting it in another transaction! - qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, - reserve_pub, - &balance); - if (qs < 0) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserve balance"); - } return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_CONFLICT, TALER_JSON_pack_ec (ec), TALER_JSON_pack_amount ("balance", - &balance), + reserve_balance), TALER_JSON_pack_amount ("requested_amount", balance_required)); } diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 877e8878f..8adf1136b 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -53,6 +53,7 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash ( * * @param connection connection to the client * @param ec specific error code to return with the reserve history + * @param reserve_balance balance remaining in the reserve * @param balance_required the balance required for the operation * @param reserve_pub the reserve with insufficient balance * @return MHD result code @@ -61,6 +62,7 @@ MHD_RESULT TEH_RESPONSE_reply_reserve_insufficient_balance ( struct MHD_Connection *connection, enum TALER_ErrorCode ec, + const struct TALER_Amount *reserve_balance, const struct TALER_Amount *balance_required, const struct TALER_ReservePublicKeyP *reserve_pub); diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c deleted file mode 100644 index 07fcc8464..000000000 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2023 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_withdraw.c - * @brief Handle /reserves/$RESERVE_PUB/withdraw requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <jansson.h> -#include "taler-exchange-httpd.h" -#include "taler_json_lib.h" -#include "taler_kyclogic_lib.h" -#include "taler_mhd_lib.h" -#include "taler-exchange-httpd_withdraw.h" -#include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keys.h" - - -/** - * Context for #withdraw_transaction. - */ -struct WithdrawContext -{ - - /** - * Hash of the (blinded) message to be signed by the Exchange. - */ - struct TALER_BlindedCoinHashP h_coin_envelope; - - /** - * Blinded planchet. - */ - struct TALER_BlindedPlanchet blinded_planchet; - - /** - * Set to the resulting signed coin data to be returned to the client. - */ - struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; - - /** - * KYC status for the operation. - */ - struct TALER_EXCHANGEDB_KycStatus kyc; - - /** - * Hash of the payto-URI representing the account - * from which the money was put into the reserve. - */ - struct TALER_PaytoHashP h_account_payto; - - /** - * Current time for the DB transaction. - */ - struct GNUNET_TIME_Timestamp now; - - /** - * AML decision, #TALER_AML_NORMAL if we may proceed. - */ - enum TALER_AmlDecisionState aml_decision; - -}; - - -/** - * Function called to iterate over KYC-relevant - * transaction amounts for a particular time range. - * Called within a database transaction, so must - * not start a new one. - * - * @param cls closure, identifies the event type and - * account to iterate over events for - * @param limit maximum time-range for which events - * should be fetched (timestamp in the past) - * @param cb function to call on each event found, - * events must be returned in reverse chronological - * order - * @param cb_cls closure for @a cb - */ -static void -withdraw_amount_cb (void *cls, - struct GNUNET_TIME_Absolute limit, - TALER_EXCHANGEDB_KycAmountCallback cb, - void *cb_cls) -{ - struct WithdrawContext *wc = cls; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Signaling amount %s for KYC check\n", - TALER_amount2s (&wc->collectable.amount_with_fee)); - if (GNUNET_OK != - cb (cb_cls, - &wc->collectable.amount_with_fee, - wc->now.abs_time)) - return; - qs = TEH_plugin->select_withdraw_amounts_for_kyc_check ( - TEH_plugin->cls, - &wc->h_account_payto, - limit, - cb, - cb_cls); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Got %d additional transactions for this withdrawal and limit %llu\n", - qs, - (unsigned long long) limit.abs_value_us); - GNUNET_break (qs >= 0); -} - - -/** - * Function called on each @a amount that was found to - * be relevant for the AML check as it was merged into - * the reserve. - * - * @param cls `struct TALER_Amount *` to total up the amounts - * @param amount encountered transaction amount - * @param date when was the amount encountered - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to abort iteration - * #GNUNET_SYSERR on internal error (also abort itaration) - */ -static enum GNUNET_GenericReturnValue -aml_amount_cb ( - void *cls, - const struct TALER_Amount *amount, - struct GNUNET_TIME_Absolute date) -{ - struct TALER_Amount *total = cls; - - GNUNET_assert (0 <= - TALER_amount_add (total, - total, - amount)); - return GNUNET_OK; -} - - -/** - * Function implementing withdraw transaction. Runs the - * transaction logic; 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. - * - * Note that "wc->collectable.sig" is set before entering this function as we - * signed before entering the transaction. - * - * @param cls a `struct WithdrawContext *` - * @param connection MHD request which triggered the transaction - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -withdraw_transaction (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) -{ - struct WithdrawContext *wc = cls; - enum GNUNET_DB_QueryStatus qs; - bool found = false; - bool balance_ok = false; - bool nonce_ok = false; - bool age_ok = false; - uint16_t allowed_maximum_age = 0; - uint64_t ruuid; - const struct TALER_CsNonce *nonce; - const struct TALER_BlindedPlanchet *bp; - struct TALER_PaytoHashP reserve_h_payto; - - wc->now = GNUNET_TIME_timestamp_get (); - /* Do AML check: compute total merged amount and check - against applicable AML threshold */ - { - char *reserve_payto; - - reserve_payto = TALER_reserve_make_payto (TEH_base_url, - &wc->collectable.reserve_pub); - TALER_payto_hash (reserve_payto, - &reserve_h_payto); - GNUNET_free (reserve_payto); - } - { - struct TALER_Amount merge_amount; - struct TALER_Amount threshold; - struct GNUNET_TIME_Absolute now_minus_one_month; - - now_minus_one_month - = GNUNET_TIME_absolute_subtract (wc->now.abs_time, - GNUNET_TIME_UNIT_MONTHS); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (TEH_currency, - &merge_amount)); - qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls, - &reserve_h_payto, - now_minus_one_month, - &aml_amount_cb, - &merge_amount); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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, - "select_merge_amounts_for_kyc_check"); - return qs; - } - qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls, - &reserve_h_payto, - &wc->aml_decision, - &wc->kyc, - &threshold); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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, - "select_aml_threshold"); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - threshold = TEH_aml_threshold; /* use default */ - wc->aml_decision = TALER_AML_NORMAL; - } - - switch (wc->aml_decision) - { - case TALER_AML_NORMAL: - if (0 >= TALER_amount_cmp (&merge_amount, - &threshold)) - { - /* merge_amount <= threshold, continue withdraw below */ - break; - } - wc->aml_decision = TALER_AML_PENDING; - qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls, - &reserve_h_payto, - &merge_amount); - if (qs <= 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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_STORE_FAILED, - "trigger_aml_process"); - return qs; - } - return qs; - case TALER_AML_PENDING: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "AML already pending, doing nothing\n"); - return qs; - case TALER_AML_FROZEN: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Account frozen, doing nothing\n"); - return qs; - } - } - - /* Check if the money came from a wire transfer */ - qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls, - &wc->collectable.reserve_pub, - &wc->h_account_payto); - if (qs < 0) - return qs; - /* If no results, reserve was created by merge, in which case no KYC check - is required as the merge already did that. */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - char *kyc_required; - - qs = TALER_KYCLOGIC_kyc_test_required ( - TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, - &wc->h_account_payto, - TEH_plugin->select_satisfied_kyc_processes, - TEH_plugin->cls, - &withdraw_amount_cb, - wc, - &kyc_required); - if (qs < 0) - { - 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, - "kyc_test_required"); - } - return qs; - } - if (NULL != kyc_required) - { - /* insert KYC requirement into DB! */ - wc->kyc.ok = false; - qs = TEH_plugin->insert_kyc_requirement_for_account ( - TEH_plugin->cls, - kyc_required, - &wc->h_account_payto, - &wc->collectable.reserve_pub, - &wc->kyc.requirement_row); - GNUNET_free (kyc_required); - 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_STORE_FAILED, - "insert_kyc_requirement_for_account"); - } - return qs; - } - } - wc->kyc.ok = true; - bp = &wc->blinded_planchet; - nonce = (TALER_DENOMINATION_CS == bp->cipher) - ? &bp->details.cs_blinded_planchet.nonce - : NULL; - qs = TEH_plugin->do_withdraw (TEH_plugin->cls, - nonce, - &wc->collectable, - wc->now, - TEH_age_restriction_enabled, - &found, - &balance_ok, - &nonce_ok, - &age_ok, - &allowed_maximum_age, - &ruuid); - 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, - "do_withdraw"); - } - return qs; - } - if (! found) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! age_ok) - { - /* We respond with the lowest age in the corresponding age group - * of the required age */ - uint16_t lowest_age = TALER_get_lowest_age ( - &TEH_age_restriction_config.mask, - allowed_maximum_age); - - TEH_plugin->rollback (TEH_plugin->cls); - *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required ( - connection, - lowest_age); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! balance_ok) - { - TEH_plugin->rollback (TEH_plugin->cls); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Balance insufficient for /withdraw\n"); - *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( - connection, - TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, - &wc->collectable.amount_with_fee, - &wc->collectable.reserve_pub); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! nonce_ok) - { - TEH_plugin->rollback (TEH_plugin->cls); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++; - return qs; -} - - -/** - * Check if the @a rc is replayed and we already have an - * answer. If so, replay the existing answer and return the - * HTTP response. - * - * @param rc request context - * @param[in,out] wc parsed request data - * @param[out] mret HTTP status, set if we return true - * @return true if the request is idempotent with an existing request - * false if we did not find the request in the DB and did not set @a mret - */ -static bool -check_request_idempotent (struct TEH_RequestContext *rc, - struct WithdrawContext *wc, - MHD_RESULT *mret) -{ - enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &wc->h_coin_envelope, - &wc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mret = TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_withdraw_info"); - return true; /* well, kind-of */ - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - return false; - /* generate idempotent reply */ - TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++; - *mret = TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - TALER_JSON_pack_blinded_denom_sig ("ev_sig", - &wc->collectable.sig)); - TALER_blinded_denom_sig_free (&wc->collectable.sig); - return true; -} - - -MHD_RESULT -TEH_handler_withdraw (struct TEH_RequestContext *rc, - const struct TALER_ReservePublicKeyP *reserve_pub, - const json_t *root) -{ - struct WithdrawContext wc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &wc.collectable.reserve_sig), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &wc.collectable.denom_pub_hash), - TALER_JSON_spec_blinded_planchet ("coin_ev", - &wc.blinded_planchet), - GNUNET_JSON_spec_end () - }; - enum TALER_ErrorCode ec; - struct TEH_DenominationKey *dk; - - memset (&wc, - 0, - sizeof (wc)); - wc.collectable.reserve_pub = *reserve_pub; - { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (rc->connection, - root, - spec); - if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - } - { - MHD_RESULT mret; - struct TEH_KeyStateHandle *ksh; - - ksh = TEH_keys_get_state (); - if (NULL == ksh) - { - if (! check_request_idempotent (rc, - &wc, - &mret)) - { - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); - } - GNUNET_JSON_parse_free (spec); - return mret; - } - - dk = TEH_keys_denomination_by_hash_from_state ( - ksh, - &wc.collectable.denom_pub_hash, - NULL, - NULL); - - if (NULL == dk) - { - if (! check_request_idempotent (rc, - &wc, - &mret)) - { - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_unknown_denom_pub_hash ( - rc->connection, - &wc.collectable.denom_pub_hash); - } - GNUNET_JSON_parse_free (spec); - return mret; - } - if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time)) - { - /* This denomination is past the expiration time for withdraws */ - if (! check_request_idempotent (rc, - &wc, - &mret)) - { - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.collectable.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "WITHDRAW"); - } - GNUNET_JSON_parse_free (spec); - return mret; - } - if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) - { - /* This denomination is not yet valid, no need to check - for idempotency! */ - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.collectable.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, - "WITHDRAW"); - } - if (dk->recoup_possible) - { - /* This denomination has been revoked */ - if (! check_request_idempotent (rc, - &wc, - &mret)) - { - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_expired_denom_pub_hash ( - rc->connection, - &wc.collectable.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - "WITHDRAW"); - } - GNUNET_JSON_parse_free (spec); - return mret; - } - if (dk->denom_pub.cipher != wc.blinded_planchet.cipher) - { - /* denomination cipher and blinded planchet cipher not the same */ - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, - NULL); - } - } - - if (0 > - TALER_amount_add (&wc.collectable.amount_with_fee, - &dk->meta.value, - &dk->meta.fees.withdraw)) - { - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW, - NULL); - } - - if (GNUNET_OK != - TALER_coin_ev_hash (&wc.blinded_planchet, - &wc.collectable.denom_pub_hash, - &wc.collectable.h_coin_envelope)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); - } - - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; - if (GNUNET_OK != - TALER_wallet_withdraw_verify (&wc.collectable.denom_pub_hash, - &wc.collectable.amount_with_fee, - &wc.collectable.h_coin_envelope, - &wc.collectable.reserve_pub, - &wc.collectable.reserve_sig)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, - NULL); - } - - { - struct TEH_CoinSignData csd = { - .h_denom_pub = &wc.collectable.denom_pub_hash, - .bp = &wc.blinded_planchet - }; - - /* Sign before transaction! */ - ec = TEH_keys_denomination_sign ( - &csd, - false, - &wc.collectable.sig); - } - if (TALER_EC_NONE != ec) - { - GNUNET_break (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to sign coin: %d\n", - ec); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_ec (rc->connection, - ec, - NULL); - } - - /* run transaction */ - { - MHD_RESULT mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (rc->connection, - "run withdraw", - TEH_MT_REQUEST_WITHDRAW, - &mhd_ret, - &withdraw_transaction, - &wc)) - { - /* Even if #withdraw_transaction() failed, it may have created a signature - (or we might have done it optimistically above). */ - TALER_blinded_denom_sig_free (&wc.collectable.sig); - GNUNET_JSON_parse_free (spec); - return mhd_ret; - } - } - - /* Clean up and send back final response */ - GNUNET_JSON_parse_free (spec); - - if (! wc.kyc.ok) - return TEH_RESPONSE_reply_kyc_required (rc->connection, - &wc.h_account_payto, - &wc.kyc); - - if (TALER_AML_NORMAL != wc.aml_decision) - return TEH_RESPONSE_reply_aml_blocked (rc->connection, - wc.aml_decision); - - { - MHD_RESULT ret; - - ret = TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - TALER_JSON_pack_blinded_denom_sig ("ev_sig", - &wc.collectable.sig)); - TALER_blinded_denom_sig_free (&wc.collectable.sig); - return ret; - } -} - - -/* end of taler-exchange-httpd_withdraw.c */ diff --git a/src/exchange/taler-exchange-httpd_withdraw.h b/src/exchange/taler-exchange-httpd_withdraw.h deleted file mode 100644 index 2ec76bb92..000000000 --- a/src/exchange/taler-exchange-httpd_withdraw.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2021 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_withdraw.h - * @brief Handle /reserve/withdraw requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H -#define TALER_EXCHANGE_HTTPD_WITHDRAW_H - -#include <microhttpd.h> -#include "taler-exchange-httpd.h" - - -/** - * Handle a "/reserves/$RESERVE_PUB/withdraw" request. Parses the requested "denom_pub" which - * specifies the key/value of the coin to be withdrawn, and checks that the - * signature "reserve_sig" makes this a valid withdrawal request from the - * specified reserve. If so, the envelope with the blinded coin "coin_ev" is - * passed down to execute the withdrawal operation. - * - * @param rc request context - * @param root uploaded JSON data - * @param reserve_pub public key of the reserve - * @return MHD result code - */ -MHD_RESULT -TEH_handler_withdraw (struct TEH_RequestContext *rc, - const struct TALER_ReservePublicKeyP *reserve_pub, - const json_t *root); - -#endif diff --git a/src/exchangedb/exchange_do_age_withdraw.sql b/src/exchangedb/exchange_do_age_withdraw.sql index 184a3e49a..c696bd807 100644 --- a/src/exchangedb/exchange_do_age_withdraw.sql +++ b/src/exchangedb/exchange_do_age_withdraw.sql @@ -29,6 +29,7 @@ CREATE OR REPLACE FUNCTION exchange_do_age_withdraw( IN denom_sigs BYTEA[], OUT reserve_found BOOLEAN, OUT balance_ok BOOLEAN, + OUT reserve_balance taler_amount, OUT age_ok BOOLEAN, OUT required_age INT2, -- in years ϵ [0,1..) OUT reserve_birthday INT4, @@ -38,7 +39,7 @@ AS $$ DECLARE reserve RECORD; difference RECORD; - balance taler_amount; + balance taler_amount; not_before date; earliest_date date; BEGIN @@ -48,6 +49,7 @@ BEGIN -- reserves_in by reserve_pub (SELECT) -- wire_targets by wire_target_h_payto +-- FIXME-Oec: never select-*! SELECT * INTO reserve FROM exchange.reserves @@ -59,6 +61,8 @@ THEN age_ok = FALSE; required_age=-1; conflict=FALSE; + reserve_balance.val = 0; + reserve_balance.frac = 0; balance_ok=FALSE; RETURN; END IF; @@ -66,7 +70,7 @@ END IF; reserve_found = TRUE; conflict=FALSE; -- not really yet determined -balance = reserve.current_balance; +reserve_balance = reserve.current_balance; reserve_birthday = reserve.birthday; -- Check age requirements @@ -99,7 +103,7 @@ required_age=0; -- Check reserve balance is sufficient. SELECT * INTO difference -FROM amount_left_minus_right(balance +FROM amount_left_minus_right(reserve_balance ,amount_with_fee); balance_ok = difference.ok; diff --git a/src/exchangedb/exchange_do_batch_withdraw.sql b/src/exchangedb/exchange_do_batch_withdraw.sql index be279ab7c..a27f2348e 100644 --- a/src/exchangedb/exchange_do_batch_withdraw.sql +++ b/src/exchangedb/exchange_do_batch_withdraw.sql @@ -24,6 +24,7 @@ CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw( IN do_age_check BOOLEAN, OUT reserve_found BOOLEAN, OUT balance_ok BOOLEAN, + OUT reserve_balance taler_amount, OUT age_ok BOOLEAN, OUT allowed_maximum_age INT2, -- in years OUT ruuid INT8) @@ -40,7 +41,7 @@ BEGIN -- reserves_in by reserve_pub (SELECT) -- wire_targets by wire_target_h_payto - +-- FIXME-Oec: do not use select-*! SELECT * INTO reserve FROM exchange.reserves @@ -51,13 +52,15 @@ THEN -- reserve unknown reserve_found=FALSE; balance_ok=FALSE; + reserve_balance.frac = 0; + reserve_balance.val = 0; age_ok=FALSE; allowed_maximum_age=0; ruuid=2; RETURN; END IF; reserve_found=TRUE; - +reserve_balance = reserve.current_balance; ruuid = reserve.reserve_uuid; -- Check if age requirements are present @@ -79,24 +82,23 @@ ELSE RETURN; END IF; -balance = reserve.current_balance; -- Check reserve balance is sufficient. -IF (balance.val > amount.val) +IF (reserve_balance.val > amount.val) THEN - IF (balance.frac >= amount.frac) + IF (reserve_balance.frac >= amount.frac) THEN - balance.val=balance.val - amount.val; - balance.frac=balance.frac - amount.frac; + balance.val=reserve_balance.val - amount.val; + balance.frac=reserve_balance.frac - amount.frac; ELSE - balance.val=balance.val - amount.val - 1; - balance.frac=balance.frac + 100000000 - amount.frac; + balance.val=reserve_balance.val - amount.val - 1; + balance.frac=reserve_balance.frac + 100000000 - amount.frac; END IF; ELSE - IF (balance.val = amount.val) AND (balance.frac >= amount.frac) + IF (reserve_balance.val = amount.val) AND (reserve_balance.frac >= amount.frac) THEN balance.val=0; - balance.frac=balance.frac - amount.frac; + balance.frac=reserve_balance.frac - amount.frac; ELSE balance_ok=FALSE; RETURN; diff --git a/src/exchangedb/exchange_do_reserve_open.sql b/src/exchangedb/exchange_do_reserve_open.sql index 7aca78b8c..5d36d1af7 100644 --- a/src/exchangedb/exchange_do_reserve_open.sql +++ b/src/exchangedb/exchange_do_reserve_open.sql @@ -27,7 +27,9 @@ CREATE OR REPLACE FUNCTION exchange_do_reserve_open( IN in_open_fee taler_amount, OUT out_open_cost taler_amount, OUT out_final_expiration INT8, - OUT out_no_funds BOOLEAN) + OUT out_no_reserve BOOLEAN, + OUT out_no_funds BOOLEAN, + OUT out_reserve_balance taler_amount) LANGUAGE plpgsql AS $$ DECLARE @@ -41,6 +43,7 @@ DECLARE reserve RECORD; BEGIN +-- FIXME: do not use SELECT-* -- FIXME: use SELECT FOR UPDATE? SELECT * INTO reserve @@ -49,12 +52,19 @@ SELECT * IF NOT FOUND THEN - -- FIXME: do we need to set a 'not found'? RAISE NOTICE 'reserve not found'; + out_no_reserve = TRUE; + out_no_funds = TRUE; + out_reserve_balance.val = 0; + out_reserve_balance.frac = 0; + out_open_cost.val = 0; + out_open_cost.frac = 0; + out_final_expiration = 0; RETURN; END IF; -my_balance = reserve.current_balance; +out_no_reserve = FALSE; +out_reserve_balance = reserve.current_balance; -- Do not allow expiration time to start in the past already IF (reserve.expiration_date < in_now) @@ -143,21 +153,21 @@ THEN END IF; -- Check reserve balance is sufficient. -IF (my_balance.val > in_reserve_payment.val) +IF (out_reserve_balance.val > in_reserve_payment.val) THEN - IF (my_balance.frac >= in_reserve_payment.frac) + IF (out_reserve_balance.frac >= in_reserve_payment.frac) THEN - my_balance.val=my_balance.val - in_reserve_payment.val; - my_balance.frac=my_balance.frac - in_reserve_payment.frac; + my_balance.val=out_reserve_balance.val - in_reserve_payment.val; + my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac; ELSE - my_balance.val=my_balance.val - in_reserve_payment.val - 1; - my_balance.frac=my_balance.frac + 100000000 - in_reserve_payment.frac; + my_balance.val=out_reserve_balance.val - in_reserve_payment.val - 1; + my_balance.frac=out_reserve_balance.frac + 100000000 - in_reserve_payment.frac; END IF; ELSE - IF (my_balance.val = in_reserve_payment.val) AND (my_balance.frac >= in_reserve_payment.frac) + IF (out_reserve_balance.val = in_reserve_payment.val) AND (out_reserve_balance.frac >= in_reserve_payment.frac) THEN my_balance.val=0; - my_balance.frac=my_balance.frac - in_reserve_payment.frac; + my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac; ELSE out_final_expiration = reserve.expiration_date; out_open_cost.val = my_cost.val; diff --git a/src/exchangedb/pg_do_age_withdraw.c b/src/exchangedb/pg_do_age_withdraw.c index 93e15716b..99584098c 100644 --- a/src/exchangedb/pg_do_age_withdraw.c +++ b/src/exchangedb/pg_do_age_withdraw.c @@ -36,6 +36,7 @@ TEH_PG_do_age_withdraw ( struct GNUNET_TIME_Timestamp now, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *required_age, uint32_t *reserve_birthday, @@ -69,6 +70,8 @@ TEH_PG_do_age_withdraw ( found), GNUNET_PQ_result_spec_bool ("balance_ok", balance_ok), + TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance", + reserve_balance), GNUNET_PQ_result_spec_bool ("age_ok", age_ok), GNUNET_PQ_result_spec_uint16 ("required_age", @@ -83,15 +86,12 @@ TEH_PG_do_age_withdraw ( gc = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add (now.abs_time, pg->legal_reserve_expiration_time)); - - - /* Used in #postgres_do_age_withdraw() to - update the reserve balance and check its status */ PREPARE (pg, "call_age_withdraw", "SELECT " " reserve_found" ",balance_ok" + ",reserve_balance" ",age_ok" ",required_age" ",reserve_birthday" diff --git a/src/exchangedb/pg_do_age_withdraw.h b/src/exchangedb/pg_do_age_withdraw.h index 71376022d..fb435a309 100644 --- a/src/exchangedb/pg_do_age_withdraw.h +++ b/src/exchangedb/pg_do_age_withdraw.h @@ -34,6 +34,7 @@ * @param now current time (rounded) * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction) * @param[out] age_ok set to true if no age requirements are present on the reserve * @param[out] required_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve * @param[out] reserve_birthday if @e age_ok is false, set to the birthday of the reserve @@ -47,6 +48,7 @@ TEH_PG_do_age_withdraw ( const struct GNUNET_TIME_Timestamp now, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *required_age, uint32_t *reserve_birthday, diff --git a/src/exchangedb/pg_do_batch_withdraw.c b/src/exchangedb/pg_do_batch_withdraw.c index f89f32775..f5571ddbb 100644 --- a/src/exchangedb/pg_do_batch_withdraw.c +++ b/src/exchangedb/pg_do_batch_withdraw.c @@ -36,6 +36,7 @@ TEH_PG_do_batch_withdraw ( bool do_age_check, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *allowed_maximum_age, uint64_t *ruuid) @@ -56,6 +57,8 @@ TEH_PG_do_batch_withdraw ( found), GNUNET_PQ_result_spec_bool ("balance_ok", balance_ok), + TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance", + reserve_balance), GNUNET_PQ_result_spec_bool ("age_ok", age_ok), GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age", @@ -73,6 +76,7 @@ TEH_PG_do_batch_withdraw ( "SELECT " " reserve_found" ",balance_ok" + ",reserve_balance" ",age_ok" ",allowed_maximum_age" ",ruuid" diff --git a/src/exchangedb/pg_do_batch_withdraw.h b/src/exchangedb/pg_do_batch_withdraw.h index d0b865746..486f8d1b2 100644 --- a/src/exchangedb/pg_do_batch_withdraw.h +++ b/src/exchangedb/pg_do_batch_withdraw.h @@ -36,6 +36,7 @@ * @param age_check_required if true, fail if age requirements are set on the reserve * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction) * @param[out] age_ok set to true if no age requirements are present on the reserve * @param[out] allowed_maximum_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve (client needs to call age-withdraw) * @param[out] ruuid set to the reserve's UUID (reserves table row) @@ -50,6 +51,7 @@ TEH_PG_do_batch_withdraw ( bool age_check_required, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *allowed_maximum_age, uint64_t *ruuid); diff --git a/src/exchangedb/pg_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c index e82a3e9cf..b15c96dd1 100644 --- a/src/exchangedb/pg_do_reserve_open.c +++ b/src/exchangedb/pg_do_reserve_open.c @@ -38,6 +38,7 @@ TEH_PG_do_reserve_open ( struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, bool *no_funds, + struct TALER_Amount *reserve_balance, struct TALER_Amount *open_cost, struct GNUNET_TIME_Timestamp *final_expiration) { @@ -60,16 +61,23 @@ TEH_PG_do_reserve_open ( open_fee), GNUNET_PQ_query_param_end }; + bool no_reserve = true; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_amount ("out_open_cost", pg->currency, open_cost), + TALER_PQ_result_spec_amount ("out_reserve_balance", + pg->currency, + reserve_balance), GNUNET_PQ_result_spec_timestamp ("out_final_expiration", final_expiration), + GNUNET_PQ_result_spec_bool ("out_no_reserve", + &no_reserve), GNUNET_PQ_result_spec_bool ("out_no_funds", no_funds), GNUNET_PQ_result_spec_end }; + enum GNUNET_DB_QueryStatus qs; PREPARE (pg, "do_reserve_open", @@ -77,10 +85,17 @@ TEH_PG_do_reserve_open ( " out_open_cost" ",out_final_expiration" ",out_no_funds" + ",out_no_reserve" + ",out_reserve_balance" " FROM exchange_do_reserve_open" " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "do_reserve_open", - params, - rs); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "do_reserve_open", + params, + rs); + if (qs <= 0) + return qs; + if (no_reserve) + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + return qs; } diff --git a/src/exchangedb/pg_do_reserve_open.h b/src/exchangedb/pg_do_reserve_open.h index acf2d67ee..432f3f664 100644 --- a/src/exchangedb/pg_do_reserve_open.h +++ b/src/exchangedb/pg_do_reserve_open.h @@ -39,6 +39,7 @@ * @param now when did we the client initiate the action * @param open_fee annual fee to be charged for the open operation by the exchange * @param[out] no_funds set to true if reserve balance is insufficient + * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction) * @param[out] open_cost set to the actual cost * @param[out] final_expiration when will the reserve expire now * @return transaction status code @@ -55,6 +56,7 @@ TEH_PG_do_reserve_open ( struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, bool *no_funds, + struct TALER_Amount *reserve_balance, struct TALER_Amount *open_cost, struct GNUNET_TIME_Timestamp *final_expiration); diff --git a/src/exchangedb/pg_do_withdraw.c b/src/exchangedb/pg_do_withdraw.c index e32afcda1..cb511b108 100644 --- a/src/exchangedb/pg_do_withdraw.c +++ b/src/exchangedb/pg_do_withdraw.c @@ -26,6 +26,8 @@ #include "pg_helper.h" +// FIXME: this function is currently only used in tests. +// Replace by batch withdraw function in tests as well! enum GNUNET_DB_QueryStatus TEH_PG_do_withdraw ( void *cls, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 30c966d75..752524498 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -2449,15 +2449,6 @@ TALER_EXCHANGE_reserves_history_cancel ( struct TALER_EXCHANGE_ReservesHistoryHandle *rsh); -/* ********************* POST /reserves/$RESERVE_PUB/withdraw *********************** */ - - -/** - * @brief A /reserves/$RESERVE_PUB/withdraw Handle - */ -struct TALER_EXCHANGE_WithdrawHandle; - - /** * Information input into the withdraw process per coin. */ @@ -2512,119 +2503,6 @@ struct TALER_EXCHANGE_PrivateCoinDetails /** - * Details about a response for a withdraw request. - */ -struct TALER_EXCHANGE_WithdrawResponse -{ - /** - * HTTP response data. - */ - struct TALER_EXCHANGE_HttpResponse hr; - - /** - * Details about the response. - */ - union - { - /** - * Details if the status is #MHD_HTTP_OK. - */ - struct TALER_EXCHANGE_PrivateCoinDetails ok; - - /** - * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. - */ - struct - { - /** - * Requirement row that the merchant should use - * to check for its KYC status. - */ - uint64_t requirement_row; - - /** - * Hash of the payto-URI of the account to KYC; - */ - struct TALER_PaytoHashP h_payto; - - } unavailable_for_legal_reasons; - - /** - * Details if the status is #MHD_HTTP_CONFLICT. - */ - struct - { - /* TODO: returning full details is not implemented */ - } conflict; - - /** - * Details if the status is #MHD_HTTP_GONE. - */ - struct - { - /* TODO: returning full details is not implemented */ - } gone; - - } details; -}; - - -/** - * Callbacks of this type are used to serve the result of submitting a - * withdraw request to a exchange. - * - * @param cls closure - * @param wr response details - */ -typedef void -(*TALER_EXCHANGE_WithdrawCallback) ( - void *cls, - const struct TALER_EXCHANGE_WithdrawResponse *wr); - - -/** - * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw - * request. This API is typically used by a wallet to withdraw from a - * reserve. - * - * Note that to ensure that no money is lost in case of hardware - * failures, the caller must have committed (most of) the arguments to - * disk before calling, and be ready to repeat the request with the - * same arguments in case of failures. - * - * @param curl_ctx The curl context to use - * @param exchange_url The base-URL of the exchange - * @param keys The /keys material from the exchange - * @param reserve_priv private key of the reserve to withdraw from - * @param wci inputs that determine the planchet - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_EXCHANGE_WithdrawCoinInput *wci, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls); - - -/** - * Cancel a withdraw status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param wh the withdraw handle - */ -void -TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh); - - -/** * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle */ struct TALER_EXCHANGE_BatchWithdrawHandle; @@ -2883,6 +2761,21 @@ struct TALER_EXCHANGE_BatchWithdraw2Response unsigned int blind_sigs_length; } ok; + + struct + { + /** + * Hash of the payto-URI of the account to KYC; + */ + struct TALER_PaytoHashP h_payto; + + /** + * ID identifying the KYC requirement to withdraw. + */ + uint64_t kyc_requirement_id; + + } unavailable_for_legal_reasons; + } details; }; @@ -3084,6 +2977,7 @@ struct TALER_EXCHANGE_AgeWithdrawResponse } details; }; + typedef void (*TALER_EXCHANGE_AgeWithdrawCallback)( void *cls, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index f0f4d6aaa..20dca4951 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -3930,6 +3930,7 @@ struct TALER_EXCHANGEDB_Plugin * @param do_age_check if set, the batch-withdrawal can only succeed when the reserve has no age restriction (birthday) set. * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] reserve_balance set to original balance of the reserve * @param[out] age_ok set to true if no age requirements were defined on the reserve or @e do_age_check was false * @param[out] allowed_maximum_age when @e age_ok is false, set to the allowed maximum age for withdrawal from the reserve. The client MUST then use the age-withdraw endpoint * @param[out] ruuid set to the reserve's UUID (reserves table row) @@ -3944,6 +3945,7 @@ struct TALER_EXCHANGEDB_Plugin bool do_age_check, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *allowed_maximum_age, uint64_t *ruuid); @@ -4001,6 +4003,7 @@ struct TALER_EXCHANGEDB_Plugin * @param commitment corresponding commitment for the age-withdraw * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] reserve_balance set to original balance of the reserve * @param[out] age_ok set to true if age requirements were met * @param[out] allowed_maximum_age if @e age_ok is FALSE, this is set to the allowed maximum age * @param[out] reserve_birthday if @e age_ok is FALSE, this is set to the reserve's birthday @@ -4013,6 +4016,7 @@ struct TALER_EXCHANGEDB_Plugin struct GNUNET_TIME_Timestamp now, bool *found, bool *balance_ok, + struct TALER_Amount *reserve_balance, bool *age_ok, uint16_t *allowed_maximum_age, uint32_t *reserve_birthday, @@ -4923,6 +4927,7 @@ struct TALER_EXCHANGEDB_Plugin * @param now when did we the client initiate the action * @param open_fee annual fee to be charged for the open operation by the exchange * @param[out] no_funds set to true if reserve balance is insufficient + * @param[out] reserve_balance set to original balance of the reserve * @param[out] open_cost set to the actual cost * @param[out] final_expiration when will the reserve expire now * @return transaction status code @@ -4938,6 +4943,7 @@ struct TALER_EXCHANGEDB_Plugin struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, bool *no_funds, + struct TALER_Amount *reserve_balance, struct TALER_Amount *open_cost, struct GNUNET_TIME_Timestamp *final_expiration); diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 12f991d89..230dfba21 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -75,9 +75,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_reserves_history.c \ exchange_api_reserves_open.c \ exchange_api_stefan.c \ - exchange_api_transfers_get.c \ - exchange_api_withdraw.c \ - exchange_api_withdraw2.c + exchange_api_transfers_get.c libtalerexchange_la_LIBADD = \ libtalerauditor.la \ $(top_builddir)/src/json/libtalerjson.la \ diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c index 4092c5c2c..ea9c0371e 100644 --- a/src/lib/exchange_api_age_withdraw.c +++ b/src/lib/exchange_api_age_withdraw.c @@ -396,27 +396,6 @@ handle_reserve_age_withdraw_blinded_finished ( GNUNET_assert (NULL == awbh->callback); TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh); return; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - /* only validate reply is well-formed */ - { - uint64_t ptu; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("legitimization_uuid", - &ptu), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - awbr.hr.http_status = 0; - awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ @@ -452,6 +431,27 @@ handle_reserve_age_withdraw_blinded_finished ( awbr.hr.ec = TALER_JSON_get_error_code (j_response); awbr.hr.hint = TALER_JSON_get_error_hint (j_response); break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + /* only validate reply is well-formed */ + { + uint64_t ptu; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("requirement_row", + &ptu), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + awbr.hr.http_status = 0; + awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c index 12c6aeff3..b6f773197 100644 --- a/src/lib/exchange_api_batch_withdraw2.c +++ b/src/lib/exchange_api_batch_withdraw2.c @@ -193,28 +193,6 @@ handle_reserve_batch_withdraw_finished (void *cls, GNUNET_assert (NULL == wh->cb); TALER_EXCHANGE_batch_withdraw2_cancel (wh); return; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - /* only validate reply is well-formed */ - { - uint64_t ptu; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("legitimization_uuid", - &ptu), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - bwr.hr.http_status = 0; - bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ @@ -249,6 +227,29 @@ handle_reserve_batch_withdraw_finished (void *cls, bwr.hr.ec = TALER_JSON_get_error_code (j); bwr.hr.hint = TALER_JSON_get_error_hint (j); break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &bwr.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ("requirement_row", + &bwr.details.unavailable_for_legal_reasons. + kyc_requirement_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + bwr.hr.http_status = 0; + bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c deleted file mode 100644 index 87218989a..000000000 --- a/src/lib/exchange_api_withdraw.c +++ /dev/null @@ -1,364 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2022 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_withdraw.c - * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests with blinding/unblinding - * @author Christian Grothoff - */ -#include "platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Withdraw Handle - */ -struct TALER_EXCHANGE_WithdrawHandle -{ - - /** - * The curl context to use - */ - struct GNUNET_CURL_Context *curl_ctx; - - /** - * The base-URL to the exchange - */ - const char *exchange_url; - - /** - * The /keys material from the exchange - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * Handle for the actual (internal) withdraw operation. - */ - struct TALER_EXCHANGE_Withdraw2Handle *wh2; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_WithdrawCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reserve private key. - */ - const struct TALER_ReservePrivateKeyP *reserve_priv; - - /** - * Seed of the planchet. - */ - struct TALER_PlanchetMasterSecretP ps; - - /** - * blinding secret - */ - union TALER_DenominationBlindingKeyP bks; - - /** - * Private key of the coin we are withdrawing. - */ - struct TALER_CoinSpendPrivateKeyP priv; - - /** - * Details of the planchet. - */ - struct TALER_PlanchetDetail pd; - - /** - * Values of the @cipher selected - */ - struct TALER_ExchangeWithdrawValues alg_values; - - /** - * Hash of the age commitment for this coin, if applicable. Maybe NULL - */ - const struct TALER_AgeCommitmentHash *ach; - - /** - * Denomination key we are withdrawing. - */ - struct TALER_EXCHANGE_DenomPublicKey pk; - - /** - * Hash of the public key of the coin we are signing. - */ - struct TALER_CoinPubHashP c_hash; - - /** - * Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations) - */ - struct TALER_EXCHANGE_CsRWithdrawHandle *csrh; - -}; - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RESERVE_PUB/withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param w2r response data - */ -static void -handle_reserve_withdraw_finished ( - void *cls, - const struct TALER_EXCHANGE_Withdraw2Response *w2r) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh = cls; - struct TALER_EXCHANGE_WithdrawResponse wr = { - .hr = w2r->hr - }; - - wh->wh2 = NULL; - switch (w2r->hr.http_status) - { - case MHD_HTTP_OK: - { - struct TALER_FreshCoin fc; - - if (GNUNET_OK != - TALER_planchet_to_coin (&wh->pk.key, - &w2r->details.ok.blind_sig, - &wh->bks, - &wh->priv, - wh->ach, - &wh->c_hash, - &wh->alg_values, - &fc)) - { - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE; - break; - } - wr.details.ok.coin_priv = wh->priv; - wr.details.ok.bks = wh->bks; - wr.details.ok.sig = fc.sig; - wr.details.ok.exchange_vals = wh->alg_values; - break; - } - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &wr.details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &wr.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w2r->hr.reply, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - default: - break; - } - wh->cb (wh->cb_cls, - &wr); - if (MHD_HTTP_OK == w2r->hr.http_status) - TALER_denom_sig_free (&wr.details.ok.sig); - TALER_EXCHANGE_withdraw_cancel (wh); -} - - -/** - * Function called when stage 1 of CS withdraw is finished (request r_pub's) - * - * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param csrr replies from the /csr-withdraw request - */ -static void -withdraw_cs_stage_two_callback ( - void *cls, - const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh = cls; - struct TALER_EXCHANGE_WithdrawResponse wr = { - .hr = csrr->hr - }; - - wh->csrh = NULL; - GNUNET_assert (TALER_DENOMINATION_CS == wh->pk.key.cipher); - switch (csrr->hr.http_status) - { - case MHD_HTTP_OK: - wh->alg_values = csrr->details.ok.alg_values; - TALER_planchet_setup_coin_priv (&wh->ps, - &wh->alg_values, - &wh->priv); - TALER_planchet_blinding_secret_create (&wh->ps, - &wh->alg_values, - &wh->bks); - /* This initializes the 2nd half of the - wh->pd.blinded_planchet! */ - if (GNUNET_OK != - TALER_planchet_prepare (&wh->pk.key, - &wh->alg_values, - &wh->bks, - &wh->priv, - wh->ach, - &wh->c_hash, - &wh->pd)) - { - GNUNET_break (0); - break; - } - wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->curl_ctx, - wh->exchange_url, - wh->keys, - &wh->pd, - wh->reserve_priv, - &handle_reserve_withdraw_finished, - wh); - return; - default: - break; - } - wh->cb (wh->cb_cls, - &wr); - TALER_EXCHANGE_withdraw_cancel (wh); -} - - -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_EXCHANGE_WithdrawCoinInput *wci, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - - wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); - wh->keys = TALER_EXCHANGE_keys_incref (keys); - wh->exchange_url = exchange_url; - wh->curl_ctx = curl_ctx; - wh->cb = res_cb; - wh->cb_cls = res_cb_cls; - wh->reserve_priv = reserve_priv; - wh->ps = *wci->ps; - wh->ach = wci->ach; - wh->pk = *wci->pk; - TALER_denom_pub_deep_copy (&wh->pk.key, - &wci->pk->key); - - switch (wci->pk->key.cipher) - { - case TALER_DENOMINATION_RSA: - { - wh->alg_values.cipher = TALER_DENOMINATION_RSA; - TALER_planchet_setup_coin_priv (&wh->ps, - &wh->alg_values, - &wh->priv); - TALER_planchet_blinding_secret_create (&wh->ps, - &wh->alg_values, - &wh->bks); - if (GNUNET_OK != - TALER_planchet_prepare (&wh->pk.key, - &wh->alg_values, - &wh->bks, - &wh->priv, - wh->ach, - &wh->c_hash, - &wh->pd)) - { - GNUNET_break (0); - GNUNET_free (wh); - return NULL; - } - wh->wh2 = TALER_EXCHANGE_withdraw2 (curl_ctx, - exchange_url, - keys, - &wh->pd, - wh->reserve_priv, - &handle_reserve_withdraw_finished, - wh); - break; - } - case TALER_DENOMINATION_CS: - { - TALER_cs_withdraw_nonce_derive ( - &wh->ps, - &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce); - /* Note that we only initialize the first half - of the blinded_planchet here; the other part - will be done after the /csr-withdraw request! */ - wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS; - wh->csrh = TALER_EXCHANGE_csr_withdraw ( - curl_ctx, - exchange_url, - &wh->pk, - &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce, - &withdraw_cs_stage_two_callback, - wh); - break; - } - default: - GNUNET_break (0); - GNUNET_free (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh) -{ - TALER_blinded_planchet_free (&wh->pd.blinded_planchet); - if (NULL != wh->csrh) - { - TALER_EXCHANGE_csr_withdraw_cancel (wh->csrh); - wh->csrh = NULL; - } - if (NULL != wh->wh2) - { - TALER_EXCHANGE_withdraw2_cancel (wh->wh2); - wh->wh2 = NULL; - } - TALER_EXCHANGE_keys_decref (wh->keys); - TALER_denom_pub_free (&wh->pk.key); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c deleted file mode 100644 index 53a5934d8..000000000 --- a/src/lib/exchange_api_withdraw2.c +++ /dev/null @@ -1,389 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2023 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_withdraw2.c - * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests without blinding/unblinding - * @author Christian Grothoff - */ -#include "platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Withdraw Handle - */ -struct TALER_EXCHANGE_Withdraw2Handle -{ - - /** - * The /keys material from the exchange - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_Withdraw2Callback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Total amount requested (value plus withdraw fee). - */ - struct TALER_Amount requested_amount; - - /** - * Public key of the reserve we are withdrawing from. - */ - struct TALER_ReservePublicKeyP reserve_pub; - -}; - - -/** - * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation. - * Extract the coin's signature and return it to the caller. The signature we - * get from the exchange is for the blinded value. Thus, we first must - * unblind it and then should verify its validity against our coin's hash. - * - * If everything checks out, we return the unblinded signature - * to the application via the callback. - * - * @param wh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh, - const json_t *json) -{ - struct TALER_EXCHANGE_Withdraw2Response w2r = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_blinded_denom_sig ("ev_sig", - &w2r.details.ok.blind_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* signature is valid, return it to the application */ - wh->cb (wh->cb_cls, - &w2r); - /* make sure callback isn't called again after return */ - wh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RESERVE_PUB/withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_withdraw_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_Withdraw2Handle *wh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_Withdraw2Response w2r = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - w2r.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - reserve_withdraw_ok (wh, - j)) - { - GNUNET_break_op (0); - w2r.hr.http_status = 0; - w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == wh->cb); - TALER_EXCHANGE_withdraw2_cancel (wh); - return; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - GNUNET_break_op (0); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this reserve. Can happen if we - query before the wire transfer went through. - We should simply pass the JSON reply to the application. */ - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - /* only validate reply is well-formed */ - { - uint64_t ptu; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("requirement_row", - &ptu), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - w2r.hr.http_status = 0; - w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange withdraw\n", - (unsigned int) response_code, - (int) w2r.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &w2r); - wh->cb = NULL; - } - TALER_EXCHANGE_withdraw2_cancel (wh); -} - - -struct TALER_EXCHANGE_Withdraw2Handle * -TALER_EXCHANGE_withdraw2 ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_PlanchetDetail *pd, - const struct TALER_ReservePrivateKeyP *reserve_priv, - TALER_EXCHANGE_Withdraw2Callback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_Withdraw2Handle *wh; - const struct TALER_EXCHANGE_DenomPublicKey *dk; - struct TALER_ReserveSignatureP reserve_sig; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - struct TALER_BlindedCoinHashP bch; - - GNUNET_assert (NULL != keys); - dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys, - &pd->denom_pub_hash); - if (NULL == dk) - { - GNUNET_break (0); - return NULL; - } - wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle); - wh->keys = TALER_EXCHANGE_keys_incref (keys); - wh->cb = res_cb; - wh->cb_cls = res_cb_cls; - /* Compute how much we expected to charge to the reserve */ - if (0 > - TALER_amount_add (&wh->requested_amount, - &dk->value, - &dk->fees.withdraw)) - { - /* Overflow here? Very strange, our CPU must be fried... */ - GNUNET_break (0); - GNUNET_free (wh); - return NULL; - } - - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &wh->reserve_pub.eddsa_pub); - - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &wh->reserve_pub, - sizeof (struct TALER_ReservePublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/withdraw", - pub_str); - } - - if (GNUNET_OK != - TALER_coin_ev_hash (&pd->blinded_planchet, - &pd->denom_pub_hash, - &bch)) - { - GNUNET_break (0); - GNUNET_free (wh); - return NULL; - } - - TALER_wallet_withdraw_sign (&pd->denom_pub_hash, - &wh->requested_amount, - &bch, - reserve_priv, - &reserve_sig); - { - json_t *withdraw_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &pd->denom_pub_hash), - TALER_JSON_pack_blinded_planchet ("coin_ev", - &pd->blinded_planchet), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &reserve_sig)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Attempting to withdraw from reserve %s\n", - TALER_B2S (&wh->reserve_pub)); - wh->url = TALER_url_join (exchange_url, - arg_str, - NULL); - if (NULL == wh->url) - { - json_decref (withdraw_obj); - GNUNET_free (wh); - return NULL; - } - { - CURL *eh; - - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - withdraw_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (withdraw_obj); - GNUNET_free (wh->url); - GNUNET_free (wh); - return NULL; - } - json_decref (withdraw_obj); - wh->job = GNUNET_CURL_job_add2 (curl_ctx, - eh, - wh->post_ctx.headers, - &handle_reserve_withdraw_finished, - wh); - } - } - return wh; -} - - -void -TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - GNUNET_free (wh->url); - TALER_curl_easy_post_finished (&wh->post_ctx); - TALER_EXCHANGE_keys_decref (wh->keys); - GNUNET_free (wh); -} diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index c45e29ff9..8a88f60f7 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -158,7 +158,7 @@ struct WithdrawState /** * Withdraw handle (while operation is running). */ - struct TALER_EXCHANGE_WithdrawHandle *wsh; + struct TALER_EXCHANGE_BatchWithdrawHandle *wsh; /** * Task scheduled to try later. @@ -242,7 +242,7 @@ do_retry (void *cls) */ static void reserve_withdraw_cb (void *cls, - const struct TALER_EXCHANGE_WithdrawResponse *wr) + const struct TALER_EXCHANGE_BatchWithdrawResponse *wr) { struct WithdrawState *ws = cls; struct TALER_TESTING_Interpreter *is = ws->is; @@ -292,11 +292,12 @@ reserve_withdraw_cb (void *cls, switch (wr->hr.http_status) { case MHD_HTTP_OK: + GNUNET_assert (1 == wr->details.ok.num_coins); TALER_denom_sig_deep_copy (&ws->sig, - &wr->details.ok.sig); - ws->coin_priv = wr->details.ok.coin_priv; - ws->bks = wr->details.ok.bks; - ws->exchange_vals = wr->details.ok.exchange_vals; + &wr->details.ok.coins[0].sig); + ws->coin_priv = wr->details.ok.coins[0].coin_priv; + ws->bks = wr->details.ok.coins[0].bks; + ws->exchange_vals = wr->details.ok.coins[0].exchange_vals; if (0 != ws->total_backoff.rel_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -442,11 +443,13 @@ withdraw_run (void *cls, .ps = &ws->ps, .ach = 0 < ws->age ? &ws->h_age_commitment : NULL }; - ws->wsh = TALER_EXCHANGE_withdraw ( + + ws->wsh = TALER_EXCHANGE_batch_withdraw ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), TALER_TESTING_get_keys (is), rp, + 1, &wci, &reserve_withdraw_cb, ws); @@ -477,7 +480,7 @@ withdraw_cleanup (void *cls, { TALER_TESTING_command_incomplete (ws->is, cmd->label); - TALER_EXCHANGE_withdraw_cancel (ws->wsh); + TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh); ws->wsh = NULL; } if (NULL != ws->retry_task) |