summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <grothoff@gnunet.org>2023-10-15 23:30:51 +0200
committerChristian Grothoff <grothoff@gnunet.org>2023-10-15 23:30:51 +0200
commit917dd4d70ff2f38d475146b387e649a669996f10 (patch)
tree2550f23c5d9418bdaae7c5b7b328d9e5c60e226b
parentc72cf2ce10017dccc342c4ea86ac2b006aa54149 (diff)
downloadexchange-917dd4d70ff2f38d475146b387e649a669996f10.tar.gz
exchange-917dd4d70ff2f38d475146b387e649a669996f10.tar.bz2
exchange-917dd4d70ff2f38d475146b387e649a669996f10.zip
avoid extra transaction to fetch balance if reserve is out of funds, remove legacy /withdraw endpoint
-rw-r--r--src/exchange/Makefile.am3
-rw-r--r--src/exchange/taler-exchange-httpd.c5
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.c15
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.c5
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_open.c3
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c19
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h2
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c700
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.h47
-rw-r--r--src/exchangedb/exchange_do_age_withdraw.sql10
-rw-r--r--src/exchangedb/exchange_do_batch_withdraw.sql24
-rw-r--r--src/exchangedb/exchange_do_reserve_open.sql32
-rw-r--r--src/exchangedb/pg_do_age_withdraw.c8
-rw-r--r--src/exchangedb/pg_do_age_withdraw.h2
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.c4
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.h2
-rw-r--r--src/exchangedb/pg_do_reserve_open.c23
-rw-r--r--src/exchangedb/pg_do_reserve_open.h2
-rw-r--r--src/exchangedb/pg_do_withdraw.c2
-rw-r--r--src/include/taler_exchange_service.h138
-rw-r--r--src/include/taler_exchangedb_plugin.h6
-rw-r--r--src/lib/Makefile.am4
-rw-r--r--src/lib/exchange_api_age_withdraw.c42
-rw-r--r--src/lib/exchange_api_batch_withdraw2.c45
-rw-r--r--src/lib/exchange_api_withdraw.c364
-rw-r--r--src/lib/exchange_api_withdraw2.c389
-rw-r--r--src/testing/testing_api_cmd_withdraw.c19
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)