summaryrefslogtreecommitdiff
path: root/src/exchange
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchange')
-rw-r--r--src/exchange/Makefile.am22
-rw-r--r--src/exchange/exchange.conf80
-rw-r--r--src/exchange/taler-exchange-aggregator.c190
-rw-r--r--src/exchange/taler-exchange-closer.c18
-rw-r--r--src/exchange/taler-exchange-expire.c32
-rw-r--r--src/exchange/taler-exchange-httpd.c1283
-rw-r--r--src/exchange/taler-exchange-httpd.h194
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.c1019
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.h (renamed from src/exchange/taler-exchange-httpd_withdraw.h)32
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw_reveal.c610
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw_reveal.h56
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision-get.c233
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision.c358
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision.h79
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decisions-get.c215
-rw-r--r--src/exchange/taler-exchange-httpd_batch-deposit.c632
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.c524
-rw-r--r--src/exchange/taler-exchange-httpd_coins_get.c709
-rw-r--r--src/exchange/taler-exchange-httpd_coins_get.h (renamed from src/exchange/taler-exchange-httpd_deposit.h)40
-rw-r--r--src/exchange/taler-exchange-httpd_common_deposit.c9
-rw-r--r--src/exchange/taler-exchange-httpd_common_kyc.c302
-rw-r--r--src/exchange/taler-exchange-httpd_common_kyc.h117
-rw-r--r--src/exchange/taler-exchange-httpd_config.c92
-rw-r--r--src/exchange/taler-exchange-httpd_config.h58
-rw-r--r--src/exchange/taler-exchange-httpd_csr.c85
-rw-r--r--src/exchange/taler-exchange-httpd_db.c61
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c569
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.c340
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.h7
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c138
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c1573
-rw-r--r--src/exchange/taler-exchange-httpd_keys.h77
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.c166
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.c321
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.h2
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-wallet.c45
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-webhook.c144
-rw-r--r--src/exchange/taler-exchange-httpd_link.c25
-rw-r--r--src/exchange/taler-exchange-httpd_link.h4
-rw-r--r--src/exchange/taler-exchange-httpd_management.h26
-rw-r--r--src/exchange/taler-exchange-httpd_management_aml-officers.c142
-rw-r--r--src/exchange/taler-exchange-httpd_management_auditors.c2
-rw-r--r--src/exchange/taler-exchange-httpd_management_drain.c4
-rw-r--r--src/exchange/taler-exchange-httpd_management_extensions.c30
-rw-r--r--src/exchange/taler-exchange-httpd_management_partners.c132
-rw-r--r--src/exchange/taler-exchange-httpd_management_post_keys.c318
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_disable.c16
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_enable.c95
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_fees.c2
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c8
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.h36
-rw-r--r--src/exchange/taler-exchange-httpd_purses_create.c28
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.c141
-rw-r--r--src/exchange/taler-exchange-httpd_purses_delete.h (renamed from src/exchange/taler-exchange-httpd_reserves_status.h)27
-rw-r--r--src/exchange/taler-exchange-httpd_purses_deposit.c46
-rw-r--r--src/exchange/taler-exchange-httpd_purses_get.c116
-rw-r--r--src/exchange/taler-exchange-httpd_purses_merge.c137
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.c33
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c45
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c146
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c1
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_attest.c124
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_close.c73
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.c285
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.h9
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get_attest.c83
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_history.c654
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_history.h13
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_open.c10
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_purse.c53
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_status.c243
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c1017
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h139
-rw-r--r--src/exchange/taler-exchange-httpd_spa.c362
-rw-r--r--src/exchange/taler-exchange-httpd_spa.h49
-rw-r--r--src/exchange/taler-exchange-httpd_transfers_get.c146
-rw-r--r--src/exchange/taler-exchange-httpd_wire.c648
-rw-r--r--src/exchange/taler-exchange-httpd_wire.h84
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c516
-rwxr-xr-xsrc/exchange/taler-exchange-kyc-aml-pep-trigger.sh7
-rw-r--r--src/exchange/taler-exchange-transfer.c37
-rw-r--r--src/exchange/taler-exchange-wirewatch.c286
-rw-r--r--src/exchange/test_taler_exchange_httpd.conf4
-rwxr-xr-xsrc/exchange/test_taler_exchange_httpd.sh6
84 files changed, 10626 insertions, 6214 deletions
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index b2365eecb..1c0c2c684 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -15,6 +15,8 @@ pkgcfg_DATA = \
exchange.conf
# Programs
+bin_SCRIPTS = \
+ taler-exchange-kyc-aml-pep-trigger.sh
bin_PROGRAMS = \
taler-exchange-aggregator \
@@ -122,14 +124,21 @@ taler_exchange_wirewatch_LDADD = \
taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.h \
+ taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \
+ taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \
taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \
+ taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \
+ taler-exchange-httpd_aml-decision-get.c \
+ taler-exchange-httpd_aml-decisions-get.c \
taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \
taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \
+ taler-exchange-httpd_coins_get.c taler-exchange-httpd_coins_get.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
+ taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.h \
+ taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \
taler-exchange-httpd_db.c taler-exchange-httpd_db.h \
- taler-exchange-httpd_deposit.c taler-exchange-httpd_deposit.h \
taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \
taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \
taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \
@@ -139,12 +148,14 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
taler-exchange-httpd_management.h \
+ taler-exchange-httpd_management_aml-officers.c \
taler-exchange-httpd_management_auditors.c \
taler-exchange-httpd_management_auditors_AP_disable.c \
taler-exchange-httpd_management_denominations_HDP_revoke.c \
taler-exchange-httpd_management_drain.c \
taler-exchange-httpd_management_extensions.c \
taler-exchange-httpd_management_global_fees.c \
+ taler-exchange-httpd_management_partners.c \
taler-exchange-httpd_management_post_keys.c \
taler-exchange-httpd_management_signkey_EP_revoke.c \
taler-exchange-httpd_management_wire_enable.c \
@@ -155,6 +166,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
taler-exchange-httpd_purses_create.c taler-exchange-httpd_purses_create.h \
taler-exchange-httpd_purses_deposit.c taler-exchange-httpd_purses_deposit.h \
+ taler-exchange-httpd_purses_delete.c taler-exchange-httpd_purses_delete.h \
taler-exchange-httpd_purses_get.c taler-exchange-httpd_purses_get.h \
taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \
taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \
@@ -168,12 +180,10 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \
taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \
taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \
- taler-exchange-httpd_reserves_status.c taler-exchange-httpd_reserves_status.h \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
+ taler-exchange-httpd_spa.c taler-exchange-httpd_spa.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_wire.c taler-exchange-httpd_wire.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) \
@@ -205,7 +215,6 @@ check_SCRIPTS += \
test_taler_exchange_httpd_afl.sh
endif
-.NOTPARALLEL:
TESTS = \
$(check_SCRIPTS)
@@ -218,4 +227,5 @@ EXTRA_DIST = \
test_taler_exchange_httpd.get \
test_taler_exchange_httpd.post \
exchange.conf \
+ $(bin_SCRIPTS) \
$(check_SCRIPTS)
diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf
index 30e5b0b90..ce471a292 100644
--- a/src/exchange/exchange.conf
+++ b/src/exchange/exchange.conf
@@ -6,6 +6,33 @@
# This must be adjusted to your actual installation.
# MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+# Must be set to the threshold above which transactions
+# are flagged for AML review.
+# AML_THRESHOLD =
+
+# How many digits does the currency use by default on displays.
+# Hint provided to wallets. Should be 2 for EUR/USD/CHF,
+# and 0 for JPY. Default is 2 as that is most common.
+# Maximum value is 8. Note that this is the number of
+# fractions shown in the wallet by default, it is still
+# possible to configure denominations with more digits
+# and those will then be rendered using 'tiny' fraction
+# capitals (like at gas stations) when present.
+CURRENCY_FRACTION_DIGITS = 2
+
+# Specifies a program (binary) to run on KYC attribute data to decide
+# whether we should immediately flag an account for AML review.
+# The KYC attribute data will be passed on standard-input.
+# Return non-zero to trigger AML review of the new user.
+KYC_AML_TRIGGER = true
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# Set to NO to disable rewards.
+ENABLE_REWARDS = YES
+
# How long do we allow /keys to be cached at most? The actual
# limit is the minimum of this value and the first expected
# significant change in /keys based on the expiration times.
@@ -15,7 +42,7 @@ MAX_KEYS_CACHING = forever
# After how many requests should the exchange auto-restart
# (to address potential issues with memory fragmentation)?
# If this option is not specified, auto-restarting is disabled.
-# MAX_REQUESTS = 10000000
+# MAX_REQUESTS = 100000
# How to access our database
DB = postgres
@@ -40,13 +67,17 @@ PORT = 8081
# transfers to enable tracking.
BASE_URL = http://localhost:8081/
-# Maximum number of requests this process should handle before
-# committing suicide.
-# MAX_REQUESTS =
-
# How long should the aggregator sleep if it has nothing to do?
AGGREGATOR_IDLE_SLEEP_INTERVAL = 60 s
+# What type of asset is the exchange managing? Used to adjust
+# the user-interface of the wallet.
+# Possibilities include: "fiat", "regional" and "crypto".
+# In the future (and already permitted but not yet supported by wallets)
+# we also expect to have "stock" and "future" (and more).
+# Default is "fiat".
+ASSET_TYPE = "fiat"
+
# FIXME: document!
ROUTER_IDLE_SLEEP_INTERVAL = 60 s
@@ -54,7 +85,7 @@ ROUTER_IDLE_SLEEP_INTERVAL = 60 s
# by taler-exchange-expire (in time). It may take
# this much time for an expired purse to be really
# cleaned up and the coins refunded.
-EXPIRE_SHARD_SIZE = 1 h
+EXPIRE_SHARD_SIZE = 60 s
# How long should the transfer tool
# sleep if it has nothing to do?
@@ -95,42 +126,13 @@ WIREWATCH_IDLE_SLEEP_INTERVAL = 1 s
SIGNKEY_LEGAL_DURATION = 2 years
# Directory with our terms of service.
-TERMS_DIR = $DATADIR/exchange/tos/
+TERMS_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the terms of service.
-TERMS_ETAG = tos-v0
+TERMS_ETAG = exchange-tos-v0
# Directory with our privacy policy.
-PRIVACY_DIR = $DATADIR/exchange/pp/
+PRIVACY_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the privacy policy.
-PRIVACY_ETAG = pp-v0
-
-# Set to NONE to disable KYC checks.
-# Set to "OAUTH2" to use OAuth 2.0 for KYC authorization.
-KYC_MODE = NONE
-
-# Balance threshold above which wallets are told
-# to undergo a KYC check at the exchange. Optional,
-# if not given there is no limit.
-# KYC_WALLET_BALANCE_LIMIT = CURRENCY:150
-#
-# KYC_WITHDRAW_PERIOD = 1 month
-
-[exchange-kyc-oauth2]
-
-# URL of the OAuth endpoint for KYC checks
-# KYC_OAUTH2_URL =
-
-# URL of the "information" endpoint for KYC checks
-# KYC_INFO_URL =
-
-# KYC Oauth client ID.
-# KYC_OAUTH2_CLIENT_ID =
-
-# KYC Client secret used to obtain access tokens.
-# KYC_OAUTH2_CLIENT_SECRET =
-
-# Where to redirect clients after successful
-# authorization?
-# KYC_OAUTH2_POST_URL = https://bank.com/
+PRIVACY_ETAG = exchange-pp-v0
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index dce6b2df4..691d65ae3 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -28,6 +28,7 @@
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_bank_service.h"
+#include "taler_dbevents.h"
/**
@@ -149,6 +150,12 @@ struct Shard
static struct TALER_Amount currency_round_unit;
/**
+ * What is the largest amount we transfer before triggering
+ * an AML check?
+ */
+static struct TALER_Amount aml_threshold;
+
+/**
* What is the base URL of this exchange? Used in the
* wire transfer subjects so that merchants and governments
* can ask for the list of aggregated deposits.
@@ -294,11 +301,20 @@ parse_aggregator_config (void)
"taler",
"CURRENCY_ROUND_UNIT",
&currency_round_unit)) ||
- ( (0 != currency_round_unit.fraction) &&
- (0 != currency_round_unit.value) ) )
+ (TALER_amount_is_zero (&currency_round_unit)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Need non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ "Need non-zero amount in section `taler' under `CURRENCY_ROUND_UNIT'\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ "exchange",
+ "AML_THRESHOLD",
+ &aml_threshold))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR;
}
@@ -367,6 +383,7 @@ release_shard (struct Shard *s)
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
@@ -481,16 +498,24 @@ return_relevant_amounts (void *cls,
static bool
kyc_satisfied (struct AggregationUnit *au_active)
{
- const char *requirement;
+ char *requirement;
enum GNUNET_DB_QueryStatus qs;
- requirement = TALER_KYCLOGIC_kyc_test_required (
+ if (kyc_off)
+ return true;
+ qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
&au_active->h_payto,
db_plugin->select_satisfied_kyc_processes,
db_plugin->cls,
&return_relevant_amounts,
- (void *) au_active);
+ (void *) au_active,
+ &requirement);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
if (NULL == requirement)
return true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -501,6 +526,7 @@ kyc_satisfied (struct AggregationUnit *au_active)
db_plugin->cls,
requirement,
&au_active->h_payto,
+ NULL, /* not a reserve */
&au_active->requirement_row);
if (qs < 0)
{
@@ -514,6 +540,114 @@ kyc_satisfied (struct AggregationUnit *au_active)
"Legitimization process %llu started\n",
(unsigned long long) au_active->requirement_row);
}
+ GNUNET_free (requirement);
+ return false;
+}
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for an AML check.
+ *
+ * @param cls closure with the `struct TALER_Amount *` where we store the sum
+ * @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
+sum_for_aml (
+ void *cls,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute date)
+{
+ struct TALER_Amount *sum = cls;
+
+ (void) date;
+ if (0 >
+ TALER_amount_add (sum,
+ sum,
+ amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Test if AML is required for a transfer to @a h_payto.
+ *
+ * @param[in,out] au_active aggregation unit to check for
+ * @return true if AML checks are satisfied
+ */
+static bool
+aml_satisfied (struct AggregationUnit *au_active)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount total;
+ struct TALER_Amount threshold;
+ enum TALER_AmlDecisionState decision;
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ total = au_active->final_amount;
+ qs = db_plugin->select_aggregation_amounts_for_kyc_check (
+ db_plugin->cls,
+ &au_active->h_payto,
+ GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_UNIT_MONTHS),
+ &sum_for_aml,
+ &total);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
+ qs = db_plugin->select_aml_threshold (db_plugin->cls,
+ &au_active->h_payto,
+ &decision,
+ &kyc,
+ &threshold);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ threshold = aml_threshold; /* use default */
+ decision = TALER_AML_NORMAL;
+ }
+ switch (decision)
+ {
+ case TALER_AML_NORMAL:
+ if (0 >= TALER_amount_cmp (&total,
+ &threshold))
+ {
+ /* total <= threshold, do nothing */
+ return true;
+ }
+ qs = db_plugin->trigger_aml_process (db_plugin->cls,
+ &au_active->h_payto,
+ &total);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
+ return false;
+ case TALER_AML_PENDING:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "AML already pending, doing nothing\n");
+ return false;
+ case TALER_AML_FROZEN:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Account frozen, doing nothing\n");
+ return false;
+ }
+ GNUNET_assert (0);
return false;
}
@@ -594,10 +728,16 @@ do_aggregate (struct AggregationUnit *au)
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&au->wtid,
sizeof (au->wtid));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No transient aggregation found, starting %s\n",
+ TALER_B2S (&au->wtid));
au->have_transient = false;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
au->have_transient = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transient aggregation found, resuming %s\n",
+ TALER_B2S (&au->wtid));
break;
}
qs = db_plugin->aggregate (db_plugin->cls,
@@ -619,7 +759,7 @@ do_aggregate (struct AggregationUnit *au)
"Serialization issue, trying again later!\n");
return GNUNET_NO;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Aggregation total is %s.\n",
TALER_amount2s (&au->total_amount));
/* Subtract wire transfer fee and round to the unit supported by the
@@ -643,7 +783,8 @@ do_aggregate (struct AggregationUnit *au)
TALER_amount_round_down (&au->final_amount,
&currency_round_unit)) ||
(TALER_amount_is_zero (&au->final_amount)) ||
- (! kyc_satisfied (au)) )
+ (! kyc_satisfied (au)) ||
+ (! aml_satisfied (au)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Not ready for wire transfer (%d/%s)\n",
@@ -684,15 +825,30 @@ do_aggregate (struct AggregationUnit *au)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Serialization issue during aggregation; trying again later!\n");
+ "Serialization issue during aggregation; trying again later!\n")
+ ;
return GNUNET_NO;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
default:
- return GNUNET_OK;
+ break;
+ }
+ {
+ struct TALER_CoinDepositEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+ .merchant_pub = au->merchant_pub
+ };
+
+ db_plugin->event_notify (db_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
}
+ return GNUNET_OK;
+
}
@@ -766,18 +922,28 @@ run_aggregation (void *cls)
(0 == counter) )
{
/* in test mode, shutdown after a shard is done with 0 work */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No work done and in test mode, shutting down\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_assert (NULL == task);
/* If we ended up doing zero work, sleep a bit */
if (0 == counter)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Going to sleep for %s before trying again\n",
+ GNUNET_TIME_relative2s (aggregator_idle_sleep_interval,
+ true));
task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
&drain_kyc_alerts,
NULL);
+ }
else
+ {
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
+ }
return;
}
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
@@ -795,6 +961,7 @@ run_aggregation (void *cls)
switch (ret)
{
case GNUNET_SYSERR:
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls);
release_shard (s);
@@ -891,6 +1058,7 @@ run_shard (void *cls)
GNUNET_free (s);
delay = GNUNET_TIME_randomized_backoff (delay,
GNUNET_TIME_UNIT_SECONDS);
+ GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_delayed (delay,
&run_shard,
NULL);
@@ -908,6 +1076,7 @@ run_shard (void *cls)
"Starting shard [%u:%u]!\n",
(unsigned int) s->shard_start,
(unsigned int) s->shard_end);
+ GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
}
@@ -1058,6 +1227,7 @@ drain_kyc_alerts (void *cls)
{
case GNUNET_SYSERR:
GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls); /* just in case */
return;
diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c
index 41c6436a1..779525c4e 100644
--- a/src/exchange/taler-exchange-closer.c
+++ b/src/exchange/taler-exchange-closer.c
@@ -312,10 +312,10 @@ expired_reserve_cb (void *cls,
memset (&wtid,
0,
sizeof (wtid));
- memcpy (&wtid,
- reserve_pub,
- GNUNET_MIN (sizeof (wtid),
- sizeof (*reserve_pub)));
+ GNUNET_memcpy (&wtid,
+ reserve_pub,
+ GNUNET_MIN (sizeof (wtid),
+ sizeof (*reserve_pub)));
qs = db_plugin->insert_reserve_closed (db_plugin->cls,
reserve_pub,
now,
@@ -469,13 +469,11 @@ run_reserve_closures (void *cls)
if (GNUNET_YES == test_mode)
{
GNUNET_SCHEDULER_shutdown ();
+ return;
}
- else
- {
- task = GNUNET_SCHEDULER_add_delayed (closer_idle_sleep_interval,
- &run_reserve_closures,
- NULL);
- }
+ task = GNUNET_SCHEDULER_add_delayed (closer_idle_sleep_interval,
+ &run_reserve_closures,
+ NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
(void) commit_or_warn ();
diff --git a/src/exchange/taler-exchange-expire.c b/src/exchange/taler-exchange-expire.c
index d99c430e0..b2d34ee1c 100644
--- a/src/exchange/taler-exchange-expire.c
+++ b/src/exchange/taler-exchange-expire.c
@@ -74,11 +74,6 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
static struct GNUNET_SCHEDULER_Task *task;
/**
- * How long should we sleep when idle before trying to find more work?
- */
-static struct GNUNET_TIME_Relative expire_idle_sleep_interval;
-
-/**
* How big are the shards we are processing? Is an inclusive offset, so every
* shard ranges from [X,X+shard_size) exclusive. So a shard covers
* shard_size slots.
@@ -141,17 +136,6 @@ shutdown_task (void *cls)
static enum GNUNET_GenericReturnValue
parse_expire_config (void)
{
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- "exchange",
- "EXPIRE_IDLE_SLEEP_INTERVAL",
- &expire_idle_sleep_interval))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "EXPIRE_IDLE_SLEEP_INTERVAL");
- return GNUNET_SYSERR;
- }
if (NULL ==
(db_plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
{
@@ -223,7 +207,12 @@ release_shard (struct Shard *s)
if ( (0 == wc) &&
(test_mode) &&
(! jump_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "In test-mode without work. Terminating.\n");
GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
}
@@ -283,9 +272,9 @@ run_expire (void *cls)
"expire-purse"))
{
GNUNET_break (0);
- global_ret = EXIT_FAILURE;
db_plugin->rollback (db_plugin->cls);
abort_shard (s);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -296,9 +285,9 @@ run_expire (void *cls)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- global_ret = EXIT_FAILURE;
db_plugin->rollback (db_plugin->cls);
abort_shard (s);
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
@@ -395,6 +384,13 @@ run_shard (void *cls)
if (GNUNET_TIME_absolute_is_future (s->shard_end))
{
abort_shard (s);
+ if (test_mode)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "In test-mode without work. Terminating.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_at (s->shard_end,
&run_shard,
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 691e1ef7d..36459fbd7 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -30,18 +30,23 @@
#include "taler_kyclogic_lib.h"
#include "taler_templating_lib.h"
#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_age-withdraw.h"
+#include "taler-exchange-httpd_age-withdraw_reveal.h"
+#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h"
#include "taler-exchange-httpd_batch-withdraw.h"
+#include "taler-exchange-httpd_coins_get.h"
+#include "taler-exchange-httpd_config.h"
#include "taler-exchange-httpd_contract.h"
#include "taler-exchange-httpd_csr.h"
-#include "taler-exchange-httpd_deposit.h"
#include "taler-exchange-httpd_deposits_get.h"
#include "taler-exchange-httpd_extensions.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_kyc-check.h"
#include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_link.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_melt.h"
@@ -50,6 +55,7 @@
#include "taler-exchange-httpd_purses_create.h"
#include "taler-exchange-httpd_purses_deposit.h"
#include "taler-exchange-httpd_purses_get.h"
+#include "taler-exchange-httpd_purses_delete.h"
#include "taler-exchange-httpd_purses_merge.h"
#include "taler-exchange-httpd_recoup.h"
#include "taler-exchange-httpd_recoup-refresh.h"
@@ -62,11 +68,9 @@
#include "taler-exchange-httpd_reserves_history.h"
#include "taler-exchange-httpd_reserves_open.h"
#include "taler-exchange-httpd_reserves_purse.h"
-#include "taler-exchange-httpd_reserves_status.h"
+#include "taler-exchange-httpd_spa.h"
#include "taler-exchange-httpd_terms.h"
#include "taler-exchange-httpd_transfers_get.h"
-#include "taler-exchange-httpd_wire.h"
-#include "taler-exchange-httpd_withdraw.h"
#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"
#include "taler_extensions.h"
@@ -78,6 +82,11 @@
#define UNIX_BACKLOG 50
/**
+ * How often will we try to connect to the database before giving up?
+ */
+#define MAX_DB_RETRIES 5
+
+/**
* Above what request latency do we start to log?
*/
#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
@@ -115,11 +124,6 @@ struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0};
static struct MHD_Daemon *mhd;
/**
- * Our KYC configuration.
- */
-struct TEH_KycOptions TEH_kyc_config;
-
-/**
* How long is caching /keys allowed at most? (global)
*/
struct GNUNET_TIME_Relative TEH_max_keys_caching;
@@ -136,16 +140,58 @@ struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
* Our DB plugin. (global)
*/
struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
/**
+ * Absolute STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+float TEH_stefan_lin;
+
+/**
+ * Where to redirect users from "/"?
+ */
+static char *toplevel_redirect_url;
+
+/**
* Our currency.
*/
char *TEH_currency;
/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+char *TEH_kyc_aml_trigger;
+
+/**
+ * Option set to #GNUNET_YES if rewards are enabled.
+ */
+int TEH_enable_rewards;
+
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+struct TALER_Amount TEH_aml_threshold;
+
+/**
* Our base URL.
*/
char *TEH_base_url;
@@ -207,6 +253,22 @@ static unsigned long long active_connections;
static unsigned long long req_max;
/**
+ * Length of the cspecs array.
+ */
+static unsigned int num_cspecs;
+
+/**
+ * Rendering specs for currencies.
+ */
+static struct TALER_CurrencySpecification *cspecs;
+
+/**
+ * Rendering spec for our currency.
+ */
+const struct TALER_CurrencySpecification *TEH_cspec;
+
+
+/**
* Context for all CURL operations (useful to the event loop)
*/
struct GNUNET_CURL_Context *TEH_curl_ctx;
@@ -278,10 +340,6 @@ handle_post_coins (struct TEH_RequestContext *rc,
} h[] = {
{
- .op = "deposit",
- .handler = &TEH_handler_deposit
- },
- {
.op = "melt",
.handler = &TEH_handler_melt
},
@@ -327,6 +385,317 @@ handle_post_coins (struct TEH_RequestContext *rc,
/**
+ * Handle a GET "/coins/$COIN_PUB[/$OP]" request. Parses the "coin_pub"
+ * EdDSA key of the coin and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_coins (struct TEH_RequestContext *rc,
+ const char *const args[2])
+{
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ if (NULL == args[0])
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &coin_pub,
+ sizeof (coin_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
+ args[0]);
+ }
+ if (NULL != args[1])
+ {
+ if (0 == strcmp (args[1],
+ "history"))
+ return TEH_handler_coins_get (rc,
+ &coin_pub);
+ if (0 == strcmp (args[1],
+ "link"))
+ return TEH_handler_link (rc,
+ &coin_pub);
+ }
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+}
+
+
+/**
+ * Signature of functions that handle operations
+ * authorized by AML officers.
+ *
+ * @param rc request context
+ * @param officer_pub the public key of the AML officer
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*AmlOpPostHandler)(struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a "/aml/$OFFICER_PUB/$OP" POST request. Parses the "officer_pub"
+ * EdDSA key of the officer and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_aml (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1])
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ AmlOpPostHandler handler;
+
+ } h[] = {
+ {
+ .op = "decision",
+ .handler = &TEH_handler_post_aml_decision
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &officer_pub,
+ sizeof (officer_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+ args[0]);
+ }
+ for (unsigned int i = 0; NULL != h[i].op; i++)
+ if (0 == strcmp (h[i].op,
+ args[1]))
+ return h[i].handler (rc,
+ &officer_pub,
+ root);
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Signature of functions that handle operations
+ * authorized by AML officers.
+ *
+ * @param rc request context
+ * @param officer_pub the public key of the AML officer
+ * @param args remaining arguments
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*AmlOpGetHandler)(struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+/**
+ * Handle a "/aml/$OFFICER_PUB/$OP" GET request. Parses the "officer_pub"
+ * EdDSA key of the officer, checks the authentication signature, and
+ * demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_aml (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1])
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ AmlOpGetHandler handler;
+
+ } h[] = {
+ {
+ .op = "decisions",
+ .handler = &TEH_handler_aml_decisions_get
+ },
+ {
+ .op = "decision",
+ .handler = &TEH_handler_aml_decision_get
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if (NULL == args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+ "argument missing");
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &officer_pub,
+ sizeof (officer_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+ args[0]);
+ }
+ if (NULL == args[1])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
+ "AML GET operations must specify an operation identifier");
+ }
+ {
+ const char *sig_hdr;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+
+ sig_hdr = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ TALER_AML_OFFICER_SIGNATURE_HEADER);
+ if ( (NULL == sig_hdr) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (sig_hdr,
+ strlen (sig_hdr),
+ &officer_sig,
+ sizeof (officer_sig))) ||
+ (GNUNET_OK !=
+ TALER_officer_aml_query_verify (&officer_pub,
+ &officer_sig)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
+ sig_hdr);
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->test_aml_officer (TEH_plugin->cls,
+ &officer_pub);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ for (unsigned int i = 0; NULL != h[i].op; i++)
+ if (0 == strcmp (h[i].op,
+ args[1]))
+ return h[i].handler (rc,
+ &officer_pub,
+ &args[2]);
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
+ * Handle a "/age-withdraw/$ACH/reveal" POST request. Parses the "ACH"
+ * hash of the commitment from a previous call to
+ * /reserves/$reserve_pub/age-withdraw
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_age_withdraw (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ struct TALER_AgeWithdrawCommitmentHashP ach;
+
+ if (0 != strcmp ("reveal", args[1]))
+ return r404 (rc->connection,
+ args[1]);
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ach,
+ sizeof (ach)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+
+ return TEH_handler_age_withdraw_reveal (rc,
+ &ach,
+ root);
+}
+
+
+/**
* Signature of functions that handle operations on reserves.
*
* @param rc request context
@@ -373,16 +742,8 @@ handle_post_reserves (struct TEH_RequestContext *rc,
.handler = &TEH_handler_batch_withdraw
},
{
- .op = "withdraw",
- .handler = &TEH_handler_withdraw
- },
- {
- .op = "status",
- .handler = &TEH_handler_reserves_status
- },
- {
- .op = "history",
- .handler = &TEH_handler_reserves_history
+ .op = "age-withdraw",
+ .handler = &TEH_handler_age_withdraw
},
{
.op = "purse",
@@ -426,6 +787,87 @@ handle_post_reserves (struct TEH_RequestContext *rc,
/**
+ * Signature of functions that handle GET operations on reserves.
+ *
+ * @param rc request context
+ * @param reserve_pub the public key of the reserve
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ReserveGetOpHandler)(struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+/**
+ * Handle a "GET /reserves/$RESERVE_PUB[/$OP]" request. Parses the "reserve_pub"
+ * EdDSA key of the reserve and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args NULL-terminated array of additional options, zero, one or two
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_reserves (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct TALER_ReservePublicKeyP reserve_pub;
+ static const struct
+ {
+ /**
+ * Name of the operation (args[1]), optional
+ */
+ const char *op;
+
+ /**
+ * Function to call to perform the operation.
+ */
+ ReserveGetOpHandler handler;
+
+ } h[] = {
+ {
+ .op = NULL,
+ .handler = &TEH_handler_reserves_get
+ },
+ {
+ .op = "history",
+ .handler = &TEH_handler_reserves_history
+ },
+ {
+ .op = NULL,
+ .handler = NULL
+ },
+ };
+
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &reserve_pub,
+ sizeof (reserve_pub))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ for (unsigned int i = 0; NULL != h[i].handler; i++)
+ {
+ if ( ( (NULL == args[1]) &&
+ (NULL == h[i].op) ) ||
+ ( (NULL != args[1]) &&
+ (NULL != h[i].op) &&
+ (0 == strcmp (h[i].op,
+ args[1])) ) )
+ return h[i].handler (rc,
+ &reserve_pub);
+ }
+ return r404 (rc->connection,
+ args[1]);
+}
+
+
+/**
* Signature of functions that handle operations on purses.
*
* @param connection HTTP request handle
@@ -579,6 +1021,11 @@ handle_mhd_completion_callback (void *cls,
TEH_check_invariants ();
if (NULL != rc->rh_cleaner)
rc->rh_cleaner (rc);
+ if (NULL != rc->root)
+ {
+ json_decref (rc->root);
+ rc->root = NULL;
+ }
TEH_check_invariants ();
{
#if MHD_VERSION >= 0x00097304
@@ -645,7 +1092,6 @@ proceed_with_handler (struct TEH_RequestContext *rc,
const struct TEH_RequestHandler *rh = rc->rh;
const char *args[rh->nargs + 2];
size_t ulen = strlen (url) + 1;
- json_t *root = NULL;
MHD_RESULT ret;
/* We do check for "ulen" here, because we'll later stack-allocate a buffer
@@ -666,8 +1112,9 @@ proceed_with_handler (struct TEH_RequestContext *rc,
/* All POST endpoints come with a body in JSON format. So we parse
the JSON here. */
- if (0 == strcasecmp (rh->method,
- MHD_HTTP_METHOD_POST))
+ if ( (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST)) &&
+ (NULL == rc->root) )
{
enum GNUNET_GenericReturnValue res;
@@ -675,16 +1122,16 @@ proceed_with_handler (struct TEH_RequestContext *rc,
&rc->opaque_post_parsing_context,
upload_data,
upload_data_size,
- &root);
+ &rc->root);
if (GNUNET_SYSERR == res)
{
- GNUNET_assert (NULL == root);
+ GNUNET_assert (NULL == rc->root);
return MHD_NO; /* bad upload, could not even generate error */
}
if ( (GNUNET_NO == res) ||
- (NULL == root) )
+ (NULL == rc->root) )
{
- GNUNET_assert (NULL == root);
+ GNUNET_assert (NULL == rc->root);
return MHD_YES; /* so far incomplete upload or parser error */
}
}
@@ -696,9 +1143,9 @@ proceed_with_handler (struct TEH_RequestContext *rc,
/* Parse command-line arguments */
/* make a copy of 'url' because 'strtok_r()' will modify */
- memcpy (d,
- url,
- ulen);
+ GNUNET_memcpy (d,
+ url,
+ ulen);
i = 0;
args[i++] = strtok_r (d, "/", &sp);
while ( (NULL != args[i - 1]) &&
@@ -721,7 +1168,6 @@ proceed_with_handler (struct TEH_RequestContext *rc,
rh->url,
url);
GNUNET_break_op (0);
- json_decref (root);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
@@ -731,16 +1177,19 @@ proceed_with_handler (struct TEH_RequestContext *rc,
/* Above logic ensures that 'root' is exactly non-NULL for POST operations,
so we test for 'root' to decide which handler to invoke. */
- if (NULL != root)
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST))
ret = rh->handler.post (rc,
- root,
+ rc->root,
args);
- else /* We also only have "POST" or "GET" in the API for at this point
- (OPTIONS/HEAD are taken care of earlier) */
+ else if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_DELETE))
+ ret = rh->handler.delete (rc,
+ args);
+ else /* Only GET left */
ret = rh->handler.get (rc,
args);
}
- json_decref (root);
return ret;
}
@@ -783,6 +1232,20 @@ handler_seed (struct TEH_RequestContext *rc,
/**
+ * Signature of functions that handle simple
+ * POST operations for the management API.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ManagementPostHandler)(
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
* Handle POST "/management/..." requests.
*
* @param rc request context
@@ -795,6 +1258,55 @@ handle_post_management (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[])
{
+ static const struct
+ {
+ const char *arg0;
+ const char *arg1;
+ ManagementPostHandler handler;
+ } plain_posts[] = {
+ {
+ .arg0 = "keys",
+ .handler = &TEH_handler_management_post_keys
+ },
+ {
+ .arg0 = "wire",
+ .handler = &TEH_handler_management_post_wire
+ },
+ {
+ .arg0 = "wire",
+ .arg1 = "disable",
+ .handler = &TEH_handler_management_post_wire_disable
+ },
+ {
+ .arg0 = "wire-fee",
+ .handler = &TEH_handler_management_post_wire_fees
+ },
+ {
+ .arg0 = "global-fee",
+ .handler = &TEH_handler_management_post_global_fees
+ },
+ {
+ .arg0 = "extensions",
+ .handler = &TEH_handler_management_post_extensions
+ },
+ {
+ .arg0 = "drain",
+ .handler = &TEH_handler_management_post_drain
+ },
+ {
+ .arg0 = "aml-officers",
+ .handler = &TEH_handler_management_aml_officers
+ },
+ {
+ .arg0 = "partners",
+ .handler = &TEH_handler_management_partners
+ },
+ {
+ NULL,
+ NULL,
+ NULL
+ }
+ };
if (NULL == args[0])
{
GNUNET_break_op (0);
@@ -890,82 +1402,22 @@ handle_post_management (struct TEH_RequestContext *rc,
&exchange_pub,
root);
}
- if (0 == strcmp (args[0],
- "keys"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/keys/*");
- }
- return TEH_handler_management_post_keys (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "wire"))
- {
- if (NULL == args[1])
- return TEH_handler_management_post_wire (rc->connection,
- root);
- if ( (0 != strcmp (args[1],
- "disable")) ||
- (NULL != args[2]) )
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/wire/disable");
- }
- return TEH_handler_management_post_wire_disable (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "wire-fee"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/wire-fee/*");
- }
- return TEH_handler_management_post_wire_fees (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "global-fee"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/global-fee/*");
- }
- return TEH_handler_management_post_global_fees (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "extensions"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/extensions/*");
+ for (unsigned int i = 0;
+ NULL != plain_posts[i].handler;
+ i++)
+ {
+ if (0 == strcmp (args[0],
+ plain_posts[i].arg0))
+ {
+ if ( ( (NULL == args[1]) &&
+ (NULL == plain_posts[i].arg1) ) ||
+ ( (NULL != args[1]) &&
+ (NULL != plain_posts[i].arg1) &&
+ (0 == strcmp (args[1],
+ plain_posts[i].arg1)) ) )
+ return plain_posts[i].handler (rc->connection,
+ root);
}
- return TEH_handler_management_post_extensions (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "drain"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/drain/*");
- }
- return TEH_handler_management_post_drain (rc->connection,
- root);
}
GNUNET_break_op (0);
return r404 (rc->connection,
@@ -974,7 +1426,7 @@ handle_post_management (struct TEH_RequestContext *rc,
/**
- * Handle a get "/management" request.
+ * Handle a GET "/management" request.
*
* @param rc request context
* @param args array of additional options (must be [0] == "keys")
@@ -1055,6 +1507,56 @@ handle_post_auditors (struct TEH_RequestContext *rc,
/**
+ * Generates the response for "/", redirecting the
+ * client to the ``toplevel_redirect_url``.
+ *
+ * @param rc request context
+ * @param args remaining arguments (should be empty)
+ * @return MHD result code
+ */
+static MHD_RESULT
+toplevel_redirect (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ const char *text = "Redirecting to /webui/";
+ struct MHD_Response *response;
+
+ response = MHD_create_response_from_buffer (strlen (text),
+ (void *) text,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ toplevel_redirect_url))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ return MHD_NO;
+ }
+
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ MHD_HTTP_FOUND,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
+}
+
+
+/**
* Handle incoming HTTP request.
*
* @param cls closure for MHD daemon (unused)
@@ -1087,15 +1589,11 @@ handle_mhd_request (void *cls,
.data = "User-agent: *\nDisallow: /\n",
.response_code = MHD_HTTP_OK
},
- /* Landing page, tell humans to go away. */
+ /* Landing page, redirect to toplevel_redirect_url */
{
.url = "",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = TEH_handler_static_response,
- .mime_type = "text/plain",
- .data =
- "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
- .response_code = MHD_HTTP_OK
+ .handler.get = &toplevel_redirect
},
/* AGPL licensing page, redirect to source. As per the AGPL-license, every
deployment is required to offer the user a download of the source of
@@ -1111,6 +1609,12 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handler_seed
},
+ /* Configuration */
+ {
+ .url = "config",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_config
+ },
/* Performance metrics */
{
.url = "metrics",
@@ -1135,12 +1639,6 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_keys_get_handler,
},
- /* Requests for wiring information */
- {
- .url = "wire",
- .method = MHD_HTTP_METHOD_GET,
- .handler.get = &TEH_handler_wire
- },
{
.url = "batch-deposit",
.method = MHD_HTTP_METHOD_POST,
@@ -1164,8 +1662,9 @@ handle_mhd_request (void *cls,
{
.url = "reserves",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = &TEH_handler_reserves_get,
- .nargs = 1
+ .handler.get = &handle_get_reserves,
+ .nargs = 2,
+ .nargs_is_upper_bound = true
},
{
.url = "reserves",
@@ -1174,6 +1673,12 @@ handle_mhd_request (void *cls,
.nargs = 2
},
{
+ .url = "age-withdraw",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_age_withdraw,
+ .nargs = 2
+ },
+ {
.url = "reserves-attest",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_reserves_get_attest,
@@ -1195,8 +1700,9 @@ handle_mhd_request (void *cls,
{
.url = "coins",
.method = MHD_HTTP_METHOD_GET,
- .handler.get = TEH_handler_link,
+ .handler.get = &handle_get_coins,
.nargs = 2,
+ .nargs_is_upper_bound = true
},
/* refreshes/$RCH/reveal */
{
@@ -1224,7 +1730,7 @@ handle_mhd_request (void *cls,
.url = "purses",
.method = MHD_HTTP_METHOD_POST,
.handler.post = &handle_post_purses,
- .nargs = 2 // ??
+ .nargs = 2
},
/* Getting purse status */
{
@@ -1233,6 +1739,13 @@ handle_mhd_request (void *cls,
.handler.get = &TEH_handler_purses_get,
.nargs = 2
},
+ /* Deleting purse */
+ {
+ .url = "purses",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler.delete = &TEH_handler_purses_delete,
+ .nargs = 1
+ },
/* Getting contracts */
{
.url = "contracts",
@@ -1251,8 +1764,7 @@ handle_mhd_request (void *cls,
.url = "kyc-proof",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_kyc_proof,
- .nargs = 128,
- .nargs_is_upper_bound = true
+ .nargs = 1
},
{
.url = "kyc-wallet",
@@ -1260,6 +1772,20 @@ handle_mhd_request (void *cls,
.handler.post = &TEH_handler_kyc_wallet,
.nargs = 0
},
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_kyc_webhook_get,
+ .nargs = 16, /* more is not plausible */
+ .nargs_is_upper_bound = true
+ },
+ {
+ .url = "kyc-webhook",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &TEH_handler_kyc_webhook_post,
+ .nargs = 16, /* more is not plausible */
+ .nargs_is_upper_bound = true
+ },
/* POST management endpoints */
{
.url = "management",
@@ -1283,6 +1809,28 @@ handle_mhd_request (void *cls,
.nargs = 4,
.nargs_is_upper_bound = true
},
+ /* AML endpoints */
+ {
+ .url = "aml",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &handle_get_aml,
+ .nargs = 4,
+ .nargs_is_upper_bound = true
+ },
+ {
+ .url = "aml",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_aml,
+ .nargs = 2
+ },
+ {
+ .url = "webui",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_spa,
+ .nargs = 1,
+ .nargs_is_upper_bound = true
+ },
+
/* mark end of list */
{
.url = NULL
@@ -1323,30 +1871,8 @@ handle_mhd_request (void *cls,
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST))
{
- const char *cl;
-
- /* Maybe check for maximum upload size
- and refuse requests if they are just too big. */
- cl = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONTENT_LENGTH);
- if (NULL != cl)
- {
- unsigned long long cv;
- char dummy;
-
- if (1 != sscanf (cl,
- "%llu%c",
- &cv,
- &dummy))
- {
- /* Not valid HTTP request, just close connection. */
- GNUNET_break_op (0);
- return MHD_NO;
- }
- if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
- return TALER_MHD_reply_request_too_large (connection);
- }
+ TALER_MHD_check_content_length (connection,
+ TALER_MHD_REQUEST_BUFFER_MAX);
}
}
@@ -1525,281 +2051,178 @@ handle_mhd_request (void *cls,
/**
- * Load general KYC configuration parameters for the exchange server into the
- * #TEH_kyc_config variable.
+ * Load configuration parameters for the exchange
+ * server into the corresponding global variables.
*
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
-parse_kyc_settings (void)
+exchange_serve_process_config (void)
{
+ static struct TALER_CurrencySpecification defspec = {
+ .num_fractional_input_digits = 2,
+ .num_fractional_normal_digits = 2,
+ .num_fractional_trailing_zero_digits = 2
+ };
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "KYC_WITHDRAW_PERIOD",
- &TEH_kyc_config.withdraw_period))
+ TALER_KYCLOGIC_kyc_init (TEH_cfg))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WITHDRAW_PERIOD",
- "valid relative time expected");
return GNUNET_SYSERR;
}
- if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period))
- return GNUNET_OK;
if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- "exchange",
- "KYC_WITHDRAW_LIMIT",
- &TEH_kyc_config.withdraw_limit))
- return GNUNET_SYSERR;
- if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency,
- TEH_currency))
+ GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
+ "exchange",
+ "MAX_REQUESTS",
+ &req_max))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WITHDRAW_LIMIT",
- "currency mismatch");
- return GNUNET_SYSERR;
+ req_max = ULLONG_MAX;
}
- return GNUNET_OK;
-}
-
-
-/**
- * Load OAuth2.0 configuration parameters for the exchange server into the
- * #TEH_kyc_config variable.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_kyc_oauth_cfg (void)
-{
- char *s;
-
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_AUTH_URL",
- &s))
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &TEH_reserve_closing_delay))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_AUTH_URL");
- return GNUNET_SYSERR;
- }
- if ( (! TALER_url_valid_charset (s)) ||
- ( (0 != strncasecmp (s,
- "http://",
- strlen ("http://"))) &&
- (0 != strncasecmp (s,
- "https://",
- strlen ("https://"))) ) )
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_AUTH_URL",
- "not a valid URL");
- GNUNET_free (s);
- return GNUNET_SYSERR;
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ /* use default */
+ TEH_reserve_closing_delay
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
+ 4);
}
- TEH_kyc_config.details.oauth2.auth_url = s;
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_LOGIN_URL",
- &s))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_LOGIN_URL");
- return GNUNET_SYSERR;
- }
- if ( (! TALER_url_valid_charset (s)) ||
- ( (0 != strncasecmp (s,
- "http://",
- strlen ("http://"))) &&
- (0 != strncasecmp (s,
- "https://",
- strlen ("https://"))) ) )
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchange",
+ "MAX_KEYS_CACHING",
+ &TEH_max_keys_caching))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_LOGIN_URL",
- "not a valid URL");
- GNUNET_free (s);
+ "exchange",
+ "MAX_KEYS_CACHING",
+ "valid relative time expected");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.login_url = s;
-
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL",
- &s))
+ "exchange",
+ "KYC_AML_TRIGGER",
+ &TEH_kyc_aml_trigger))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL");
- return GNUNET_SYSERR;
- }
- if ( (! TALER_url_valid_charset (s)) ||
- ( (0 != strncasecmp (s,
- "http://",
- strlen ("http://"))) &&
- (0 != strncasecmp (s,
- "https://",
- strlen ("https://"))) ) )
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL",
- "not a valid URL");
- GNUNET_free (s);
+ "exchange",
+ "KYC_AML_TRIGGER");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.info_url = s;
-
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_ID",
- &s))
+ "exchange",
+ "TOPLEVEL_REDIRECT_URL",
+ &toplevel_redirect_url))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_ID");
- return GNUNET_SYSERR;
+ toplevel_redirect_url = GNUNET_strdup ("/terms");
}
- TEH_kyc_config.details.oauth2.client_id = s;
-
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_SECRET",
- &s))
+ TALER_config_get_currency (TEH_cfg,
+ &TEH_currency))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_SECRET");
+ "taler",
+ "CURRENCY");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.client_secret = s;
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_POST_URL",
- &s))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_POST_URL");
+ TALER_CONFIG_parse_currencies (TEH_cfg,
+ &num_cspecs,
+ &cspecs))
return GNUNET_SYSERR;
- }
- TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s;
- return GNUNET_OK;
-}
-
+ for (unsigned int i = 0; i<num_cspecs; i++)
+ {
+ struct TALER_CurrencySpecification *cspec;
-/**
- * Load configuration parameters for the exchange
- * server into the corresponding global variables.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-exchange_serve_process_config (void)
-{
+ cspec = &cspecs[i];
+ if (0 == strcmp (TEH_currency,
+ cspec->currency))
+ {
+ TEH_cspec = cspec;
+ break;
+ }
+ }
+ if (NULL == TEH_cspec)
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
+ "taler",
+ "CURRENCY",
+ "Lacking enabled currency specification for the given currency, using default");
+ defspec.map_alt_unit_names
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("0",
+ TEH_currency)
+ );
+ defspec.name = TEH_currency;
+ GNUNET_assert (strlen (TEH_currency) <
+ sizeof (defspec.currency));
+ strcpy (defspec.currency,
+ TEH_currency);
+ TEH_cspec = &defspec;
+ }
if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_init (TEH_cfg))
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "AML_THRESHOLD",
+ &TEH_aml_threshold))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR;
}
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "STEFAN_ABS",
+ &TEH_stefan_abs))
{
- char *kyc_mode;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange",
- "KYC_MODE",
- &kyc_mode))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_MODE");
- return GNUNET_SYSERR;
- }
- if (0 == strcasecmp (kyc_mode,
- "NONE"))
- {
- TEH_kyc_config.mode = TEH_KYC_NONE;
- }
- else if (0 == strcasecmp (kyc_mode,
- "OAUTH2"))
- {
- TEH_kyc_config.mode = TEH_KYC_OAUTH2;
- if (GNUNET_OK !=
- parse_kyc_oauth_cfg ())
- {
- GNUNET_free (kyc_mode);
- return GNUNET_SYSERR;
- }
- }
- else
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_MODE",
- "Must be 'NONE' or 'OAUTH2'");
- GNUNET_free (kyc_mode);
- return GNUNET_SYSERR;
- }
- GNUNET_free (kyc_mode);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_abs));
}
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
- "exchange",
- "MAX_REQUESTS",
- &req_max))
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "STEFAN_LOG",
+ &TEH_stefan_log))
{
- req_max = ULLONG_MAX;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_log));
}
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME",
- &TEH_reserve_closing_delay))
+ GNUNET_CONFIGURATION_get_value_float (TEH_cfg,
+ "exchange",
+ "STEFAN_LIN",
+ &TEH_stefan_lin))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME");
- /* use default */
- TEH_reserve_closing_delay
- = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
- 4);
+ TEH_stefan_lin = 0.0f;
}
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "MAX_KEYS_CACHING",
- &TEH_max_keys_caching))
+ if (0 != strcmp (TEH_currency,
+ TEH_aml_threshold.currency))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "MAX_KEYS_CACHING",
- "valid relative time expected");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Amount in section `exchange' under `AML_THRESHOLD' uses the wrong currency!\n");
return GNUNET_SYSERR;
}
- if (GNUNET_OK !=
- TALER_config_get_currency (TEH_cfg,
- &TEH_currency))
+ TEH_enable_rewards
+ = GNUNET_CONFIGURATION_get_value_yesno (
+ TEH_cfg,
+ "exchange",
+ "ENABLE_REWARDS");
+ if (GNUNET_SYSERR == TEH_enable_rewards)
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler",
- "CURRENCY");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need YES or NO in section `exchange' under `ENABLE_REWARDS'\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
@@ -1822,35 +2245,6 @@ exchange_serve_process_config (void)
return GNUNET_SYSERR;
}
- if (TEH_KYC_NONE != TEH_kyc_config.mode)
- {
- if (GNUNET_YES ==
- GNUNET_CONFIGURATION_have_value (TEH_cfg,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT"))
- {
- if ( (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT",
- &TEH_kyc_config.wallet_balance_limit)) ||
- (0 != strcasecmp (TEH_currency,
- TEH_kyc_config.wallet_balance_limit.currency)) )
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT",
- "valid amount expected");
- return GNUNET_SYSERR;
- }
- }
- else
- {
- memset (&TEH_kyc_config.wallet_balance_limit,
- 0,
- sizeof (TEH_kyc_config.wallet_balance_limit));
- }
- }
{
char *master_public_key_str;
@@ -1866,11 +2260,10 @@ exchange_serve_process_config (void)
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
- strlen (
- master_public_key_str),
- &TEH_master_public_key.
- eddsa_pub))
+ GNUNET_CRYPTO_eddsa_public_key_from_string (
+ master_public_key_str,
+ strlen (master_public_key_str),
+ &TEH_master_public_key.eddsa_pub))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
"exchange",
@@ -1879,23 +2272,46 @@ exchange_serve_process_config (void)
GNUNET_free (master_public_key_str);
return GNUNET_SYSERR;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Launching exchange with public key `%s'...\n",
+ master_public_key_str);
GNUNET_free (master_public_key_str);
}
- if (TEH_KYC_NONE != TEH_kyc_config.mode)
+
{
+ char *attr_enc_key_str;
+
if (GNUNET_OK !=
- parse_kyc_settings ())
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "ATTRIBUTE_ENCRYPTION_KEY",
+ &attr_enc_key_str))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "ATTRIBUTE_ENCRYPTION_KEY");
return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_hash (attr_enc_key_str,
+ strlen (attr_enc_key_str),
+ &TEH_attribute_key.hash);
+ GNUNET_free (attr_enc_key_str);
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Launching exchange with public key `%s'...\n",
- GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
- if (NULL ==
- (TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
+ for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
+ {
+ TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg);
+ if (NULL != TEH_plugin)
+ break;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to DB, will try again %u times\n",
+ MAX_DB_RETRIES - i);
+ sleep (1);
+ }
+ if (NULL == TEH_plugin)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize DB subsystem\n");
+ "Failed to initialize DB subsystem. Giving up.\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;
@@ -2040,7 +2456,9 @@ run_single_request (void)
xfork = fork ();
if (-1 == xfork)
{
- global_ret = EXIT_FAILURE;
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -2127,6 +2545,7 @@ do_shutdown (void *cls)
mhd = TALER_MHD_daemon_stop ();
TEH_resume_keys_requests (true);
+ TEH_deposits_get_cleanup ();
TEH_reserves_get_cleanup ();
TEH_purses_get_cleanup ();
TEH_kyc_check_cleanup ();
@@ -2156,6 +2575,11 @@ do_shutdown (void *cls)
exchange_curl_rc = NULL;
}
TALER_TEMPLATING_done ();
+ TEH_cspec = NULL;
+ TALER_CONFIG_free_currencies (num_cspecs,
+ cspecs);
+ num_cspecs = 0;
+ cspecs = NULL;
}
@@ -2194,37 +2618,47 @@ run (void *cls,
return;
}
if (GNUNET_OK !=
+ TEH_spa_init ())
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
TALER_TEMPLATING_init ("exchange"))
{
- global_ret = EXIT_FAILURE;
+ global_ret = EXIT_NOTINSTALLED;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_SYSERR ==
TEH_plugin->preflight (TEH_plugin->cls))
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_extensions_init ())
{
- global_ret = EXIT_FAILURE;
+ global_ret = EXIT_NOTINSTALLED;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_keys_init ())
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_wire_init ())
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -2236,7 +2670,7 @@ run (void *cls,
if (NULL == TEH_curl_ctx)
{
GNUNET_break (0);
- global_ret = EXIT_FAILURE;
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -2289,7 +2723,6 @@ run (void *cls,
global_ret = EXIT_SUCCESS;
TALER_MHD_daemon_start (mhd);
atexit (&write_stats);
-
#if HAVE_DEVELOPER
if (NULL != input_filename)
run_single_request ();
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index 4d3fb4901..25e9e1105 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -25,117 +25,12 @@
#include <microhttpd.h>
#include "taler_json_lib.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_kyclogic_plugin.h"
#include "taler_extensions.h"
#include <gnunet/gnunet_mhd_compat.h>
-/* ************* NOTE: OLD KYC logic,***********
- new logic is in taler-exchange-httpd_kyc.h!
- ********************************************* */
-
-/**
- * Enumeration for our KYC modes.
- */
-enum TEH_KycMode
-{
- /**
- * KYC is disabled.
- */
- TEH_KYC_NONE = 0,
-
- /**
- * We use Oauth2.0.
- */
- TEH_KYC_OAUTH2 = 1
-};
-
-
-/**
- * Structure describing our KYC configuration.
- */
-struct TEH_KycOptions
-{
- /**
- * What KYC mode are we in?
- */
- enum TEH_KycMode mode;
-
- /**
- * Maximum amount that can be withdrawn in @e withdraw_period without
- * needing KYC.
- * Only valid if @e mode is not #TEH_KYC_NONE and
- * if @e withdraw_period is non-zero.
- */
- struct TALER_Amount withdraw_limit;
-
- /**
- * Maximum balance a wallet can hold without
- * needing KYC.
- * Only valid if @e mode is not #TEH_KYC_NONE and
- * if the amount specified is valid.
- */
- struct TALER_Amount wallet_balance_limit;
-
- /**
- * Time period over which @e withdraw_limit applies.
- * Only valid if @e mode is not #TEH_KYC_NONE.
- */
- struct GNUNET_TIME_Relative withdraw_period;
-
- /**
- * Details depending on @e mode.
- */
- union
- {
-
- /**
- * Configuration details if @e mode is #TEH_KYC_OAUTH2.
- */
- struct
- {
-
- /**
- * URL of the OAuth2.0 endpoint for KYC checks.
- * (token/auth)
- */
- char *auth_url;
-
- /**
- * URL of the OAuth2.0 endpoint for KYC checks.
- */
- char *login_url;
-
- /**
- * URL of the user info access endpoint.
- */
- char *info_url;
-
- /**
- * Our client ID for OAuth2.0.
- */
- char *client_id;
-
- /**
- * Our client secret for OAuth2.0.
- */
- char *client_secret;
-
- /**
- * Where to redirect clients after the
- * Web-based KYC process is done?
- */
- char *post_kyc_redirect_url;
-
- } oauth2;
-
- } details;
-};
-
-
-extern struct TEH_KycOptions TEH_kyc_config;
-
/**
* How long is caching /keys allowed at most?
*/
@@ -170,6 +65,11 @@ extern int TEH_check_invariants_flag;
extern int TEH_allow_keys_timetravel;
/**
+ * Option set to #GNUNET_YES if rewards are allowed.
+ */
+extern int TEH_enable_rewards;
+
+/**
* Main directory with revocation data.
*/
extern char *TEH_revocation_directory;
@@ -188,16 +88,53 @@ extern bool TEH_suicide;
extern struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+extern struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
* Our DB plugin.
*/
extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
/**
+ * Absolute STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+extern float TEH_stefan_lin;
+
+/**
+ * Default ways how to render #TEH_currency amounts.
+ */
+extern const struct TALER_CurrencySpecification *TEH_cspec;
+
+/**
* Our currency.
*/
extern char *TEH_currency;
/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+extern char *TEH_kyc_aml_trigger;
+
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+extern struct TALER_Amount TEH_aml_threshold;
+
+/**
* Our (externally visible) base URL.
*/
extern char *TEH_base_url;
@@ -262,6 +199,11 @@ struct TEH_RequestContext
struct MHD_Connection *connection;
/**
+ * JSON root of uploaded data (or NULL, if none).
+ */
+ json_t *root;
+
+ /**
* @e rh-specific cleanup routine. Function called
* upon completion of the request that should
* clean up @a rh_ctx. Can be NULL.
@@ -301,11 +243,10 @@ struct TEH_RequestHandler
union
{
/**
- * Function to call to handle a GET requests (and those
+ * Function to call to handle GET requests (and those
* with @e method NULL).
*
* @param rc context for the request
- * @param mime_type the @e mime_type for the reply (hint, can be NULL)
* @param args array of arguments, needs to be of length @e args_expected
* @return MHD result code
*/
@@ -315,11 +256,11 @@ struct TEH_RequestHandler
/**
- * Function to call to handle a POST request.
+ * Function to call to handle POST requests.
*
* @param rc context for the request
* @param json uploaded JSON data
- * @param args array of arguments, needs to be of length @e args_expected
+ * @param args array of arguments, needs to be of length @e nargs
* @return MHD result code
*/
MHD_RESULT
@@ -327,18 +268,18 @@ struct TEH_RequestHandler
const json_t *root,
const char *const args[]);
- } handler;
-
- /**
- * Number of arguments this handler expects in the @a args array.
- */
- unsigned int nargs;
+ /**
+ * Function to call to handle DELETE requests.
+ *
+ * @param rc context for the request
+ * @param args array of arguments, needs to be of length @e nargs
+ * @return MHD result code
+ */
+ MHD_RESULT
+ (*delete)(struct TEH_RequestContext *rc,
+ const char *const args[]);
- /**
- * Is the number of arguments given in @e nargs only an upper bound,
- * and calling with fewer arguments could be OK?
- */
- bool nargs_is_upper_bound;
+ } handler;
/**
* Mime type to use in reply (hint, can be NULL).
@@ -359,6 +300,17 @@ struct TEH_RequestHandler
* Default response code. 0 for none provided.
*/
unsigned int response_code;
+
+ /**
+ * Number of arguments this handler expects in the @a args array.
+ */
+ unsigned int nargs;
+
+ /**
+ * Is the number of arguments given in @e nargs only an upper bound,
+ * and calling with fewer arguments could be OK?
+ */
+ bool nargs_is_upper_bound;
};
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c
new file mode 100644
index 000000000..9276fb191
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.c
@@ -0,0 +1,1019 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_age-withdraw.c
+ * @brief Handle /reserves/$RESERVE_PUB/age-withdraw requests
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+#include "taler_error_codes.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_age-withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler_util.h"
+
+
+/**
+ * Context for #age_withdraw_transaction.
+ */
+struct AgeWithdrawContext
+{
+ /**
+ * KYC status for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Timestamp
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Hash of the wire source URL, needed when kyc is needed.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * The data from the age-withdraw request, as we persist it
+ */
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+
+ /**
+ * Number of coins/denonations in the reveal
+ */
+ uint32_t num_coins;
+
+ /**
+ * #num_coins * #kappa hashes of blinded coin planchets.
+ */
+ struct TALER_BlindedPlanchet (*coin_evs) [ TALER_CNC_KAPPA];
+
+ /**
+ * #num_coins hashes of the denominations from which the coins are withdrawn.
+ * Those must support age restriction.
+ */
+ struct TALER_DenominationHashP *denom_hs;
+
+};
+
+/*
+ * @brief Free the resources within a AgeWithdrawContext
+ *
+ * @param awc the context to free
+ */
+static void
+free_age_withdraw_context_resources (struct AgeWithdrawContext *awc)
+{
+ GNUNET_free (awc->denom_hs);
+ GNUNET_free (awc->coin_evs);
+ GNUNET_free (awc->commitment.denom_serials);
+ /*
+ * Note:
+ * awc->commitment.denom_sigs and .h_coin_evs were stack allocated and
+ * .denom_pub_hashes is NULL for this context.
+ */
+}
+
+
+/**
+ * Parse the denominations and blinded coin data of an '/age-withdraw' request.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_denom_hs Array of n hashes of the denominations for the withdrawal, in JSON format
+ * @param j_blinded_coin_evs Array of n arrays of kappa blinded envelopes of in JSON format for the coins.
+ * @param[out] awc The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_withdraw_json (
+ struct MHD_Connection *connection,
+ const json_t *j_denom_hs,
+ const json_t *j_blinded_coin_evs,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mhd_ret)
+{
+ char buf[256] = {0};
+ const char *error = NULL;
+ unsigned int idx = 0;
+ json_t *value = NULL;
+ struct GNUNET_HashContext *hash_context;
+
+
+ /* The age value MUST be on the beginning of an age group */
+ if (awc->commitment.max_age !=
+ TALER_get_lowest_age (&TEH_age_restriction_config.mask,
+ awc->commitment.max_age))
+ {
+ error = "max_age must be the lower edge of an age group";
+ goto EXIT;
+ }
+
+ /* Verify JSON-structure consistency */
+ {
+ uint32_t num_coins = json_array_size (j_denom_hs);
+
+ if (! json_is_array (j_denom_hs))
+ error = "denoms_h must be an array";
+ else if (! json_is_array (j_blinded_coin_evs))
+ error = "coin_evs must be an array";
+ else if (num_coins == 0)
+ error = "denoms_h must not be empty";
+ else if (num_coins != json_array_size (j_blinded_coin_evs))
+ error = "denoms_h and coins_evs must be arrays of the same size";
+ else if (num_coins > TALER_MAX_FRESH_COINS)
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not withdraw any money
+ * from it. Note that the user can't get their money back in this case!
+ **/
+ error = "maximum number of coins that can be withdrawn has been exceeded";
+
+ _Static_assert ((TALER_MAX_FRESH_COINS < INT_MAX / TALER_CNC_KAPPA),
+ "TALER_MAX_FRESH_COINS too large");
+
+ if (NULL != error)
+ goto EXIT;
+
+ awc->num_coins = num_coins;
+ awc->commitment.num_coins = num_coins;
+ }
+
+ /* Continue parsing the parts */
+
+ /* Parse denomination keys */
+ awc->denom_hs = GNUNET_new_array (awc->num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denom_hs, idx, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &awc->denom_hs[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse entry no. %d in array denoms_h",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+ };
+
+ {
+ typedef struct TALER_BlindedPlanchet
+ _array_of_kappa_planchets[TALER_CNC_KAPPA];
+
+ awc->coin_evs = GNUNET_new_array (awc->num_coins,
+ _array_of_kappa_planchets);
+ }
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != hash_context);
+
+ /* Parse blinded envelopes. */
+ json_array_foreach (j_blinded_coin_evs, idx, value) {
+ const json_t *j_kappa_coin_evs = value;
+ if (! json_is_array (j_kappa_coin_evs))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "enxtry %d in array blinded_coin_evs is not an array",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+ else if (TALER_CNC_KAPPA != json_array_size (j_kappa_coin_evs))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "array no. %d in coin_evs not of correct size",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Now parse the individual kappa envelopes and calculate the hash of
+ * the commitment along the way. */
+ {
+ unsigned int kappa = 0;
+
+ json_array_foreach (j_kappa_coin_evs, kappa, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &awc->coin_evs[idx][kappa]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse array no. %d in blinded_coin_evs[%d]",
+ kappa + 1,
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Continue to hash of the coin candidates */
+ {
+ struct TALER_BlindedCoinHashP bch;
+
+ TALER_coin_ev_hash (&awc->coin_evs[idx][kappa],
+ &awc->denom_hs[idx],
+ &bch);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &bch,
+ sizeof(bch));
+ }
+
+ /* Check for duplicate planchets. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol */
+ for (unsigned int i = 0; i < idx; i++)
+ {
+ if (0 == TALER_blinded_planchet_cmp (&awc->coin_evs[idx][kappa],
+ &awc->coin_evs[i][kappa]))
+ {
+ GNUNET_JSON_parse_free (spec);
+ error = "duplicate planchet";
+ goto EXIT;
+ }
+ }
+ }
+ }
+ }; /* json_array_foreach over j_blinded_coin_evs */
+
+ /* Finally, calculate the h_commitment from all blinded envelopes */
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &awc->commitment.h_commitment.hash);
+
+ GNUNET_assert (NULL == error);
+
+
+EXIT:
+ if (NULL != error)
+ {
+ /* Note: resources are freed in caller */
+
+ *mhd_ret = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and supports age restriction.
+ *
+ * @param connection HTTP-connection to the client
+ * @param ksh The handle to the current state of (denomination) keys in the exchange
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] pdk On success, will contain the denomination key details
+ * @param[out] result On failure, an MHD-response will be queued and result will be set to accordingly
+ * @return true on success (denomination valid), false otherwise
+ */
+static bool
+denomination_is_valid (
+ struct MHD_Connection *connection,
+ struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk,
+ MHD_RESULT *result)
+{
+ struct TEH_DenominationKey *dk;
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_h,
+ connection,
+ result);
+ if (NULL == dk)
+ {
+ /* The denomination doesn't exist */
+ /* Note: a HTTP-response has been queued and result has been set by
+ * TEH_keys_denominations_by_hash_from_state */
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws */
+ /* FIXME[oec]: add idempotency check */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL);
+ return false;
+ }
+
+ if (0 == dk->denom_pub.age_mask.bits)
+ {
+ /* This denomation does not support age restriction */
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&denom_h->hash));
+
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg);
+ return false;
+ }
+
+ *pdk = dk;
+ return true;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys a) belong
+ * to valid denominations and b) those are marked as age restricted.
+ * Also, calculate the total amount of the denominations including fees
+ * for withdraw.
+ *
+ * @param connection The HTTP connection to the client
+ * @param len The lengths of the array @a denoms_h
+ * @param denom_hs array of hashes of denomination public keys
+ * @param coin_evs array of blinded coin planchet candidates
+ * @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
+ */
+static enum GNUNET_GenericReturnValue
+are_denominations_valid (
+ struct MHD_Connection *connection,
+ uint32_t len,
+ const struct TALER_DenominationHashP *denom_hs,
+ const struct TALER_BlindedPlanchet (*coin_evs) [ TALER_CNC_KAPPA],
+ uint64_t **denom_serials,
+ struct TALER_Amount *amount_with_fee,
+ MHD_RESULT *result)
+{
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_fee;
+ struct TEH_KeyStateHandle *ksh;
+ uint64_t *serials;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ *denom_serials =
+ serials = GNUNET_new_array (len, uint64_t);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_fee));
+
+ for (uint32_t i = 0; i < len; i++)
+ {
+ struct TEH_DenominationKey *dk;
+ if (! denomination_is_valid (connection,
+ ksh,
+ &denom_hs[i],
+ &dk,
+ result))
+ /* FIXME[oec]: add idempotency check */
+ return GNUNET_SYSERR;
+
+ /* Ensure the ciphers from the planchets match the denominations' */
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ coin_evs[i][k].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&total_amount,
+ &total_amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "amount");
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate the withdraw fees */
+ if (0 > TALER_amount_add (&total_fee,
+ &total_fee,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "fee");
+ return GNUNET_SYSERR;
+ }
+
+ serials[i] = dk->meta.serial;
+ }
+
+ /* Save the total amount including fees */
+ GNUNET_assert (0 < TALER_amount_add (amount_with_fee,
+ &total_amount,
+ &total_fee));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Verify the signature of the request body with the reserve key
+ *
+ * @param connection the connection to the client
+ * @param commitment the age withdraw commitment
+ * @param mhd_ret the response to fill in the error case
+ * @return GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_reserve_signature (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ enum MHD_Result *mhd_ret)
+{
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_age_withdraw_verify (&commitment->h_commitment,
+ &commitment->amount_with_fee,
+ &TEH_age_restriction_config.mask,
+ commitment->max_age,
+ &commitment->reserve_pub,
+ &commitment->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Send a response to a "age-withdraw" request.
+ *
+ * @param connection the connection to send the response to
+ * @param ach value the client committed to
+ * @param noreveal_index which index will the client not have to reveal
+ * @return a MHD status code
+ */
+static MHD_RESULT
+reply_age_withdraw_success (
+ struct MHD_Connection *connection,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ uint32_t noreveal_index)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec =
+ TALER_exchange_online_age_withdraw_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ ach,
+ noreveal_index,
+ &pub,
+ &sig);
+
+ if (TALER_EC_NONE != ec)
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("noreveal_index",
+ noreveal_index),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Check if the request is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param con connection to the client
+ * @param[in,out] awc 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
+request_is_idempotent (struct MHD_Connection *con,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mret)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+
+ qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls,
+ &awc->commitment.reserve_pub,
+ &awc->commitment.h_commitment,
+ &commitment);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mret = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw");
+ return true; /* Well, kind-of. At least we have set mret. */
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
+
+ /* Generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW]++;
+ *mret = reply_age_withdraw_success (con,
+ &commitment.h_commitment,
+ commitment.noreveal_index);
+ return true;
+}
+
+
+/**
+ * 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, of type struct AgeWithdrawContext
+ */
+static void
+age_withdraw_amount_cb (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct AgeWithdrawContext *awc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signaling amount %s for KYC check during age-withdrawal\n",
+ TALER_amount2s (&awc->commitment.amount_with_fee));
+ if (GNUNET_OK !=
+ cb (cb_cls,
+ &awc->commitment.amount_with_fee,
+ awc->now.abs_time))
+ return;
+ qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (TEH_plugin->cls,
+ &awc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this age-withdrawal and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * Function implementing age 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.
+ *
+ * @param cls a `struct AgeWithdrawContext *`
+ * @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
+age_withdraw_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AgeWithdrawContext *awc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
+ &awc->commitment.reserve_pub,
+ &awc->h_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_AGE_WITHDRAW,
+ &awc->h_payto,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &age_withdraw_amount_cb,
+ awc,
+ &kyc_required);
+
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "kyc_test_required");
+ }
+ return qs;
+ }
+
+ if (NULL != kyc_required)
+ {
+ /* Mark result and return by inserting KYC requirement into DB! */
+ awc->kyc.ok = false;
+ return TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ kyc_required,
+ &awc->h_payto,
+ &awc->commitment.reserve_pub,
+ &awc->kyc.requirement_row);
+ }
+ }
+
+ awc->kyc.ok = true;
+
+ /* KYC requirement fulfilled, do the age-withdraw transaction */
+ {
+ bool found = false;
+ bool balance_ok = false;
+ bool age_ok = false;
+ 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,
+ &conflict);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_age_withdraw");
+ return qs;
+ }
+ if (! found)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! age_ok)
+ {
+ enum TALER_ErrorCode ec =
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE;
+
+ *mhd_ret =
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_MHD_PACK_EC (ec),
+ GNUNET_JSON_pack_uint64 ("allowed_maximum_age",
+ allowed_maximum_age),
+ GNUNET_JSON_pack_uint64 ("reserve_birthday",
+ reserve_birthday));
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ 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;
+ }
+ if (conflict)
+ {
+ /* do_age_withdraw signaled a conflict, so there MUST be an entry
+ * in the DB. Put that into the response */
+ bool ok = request_is_idempotent (connection,
+ awc,
+ mhd_ret);
+ GNUNET_assert (ok);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ *mhd_ret = -1;
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW]++;
+ return qs;
+}
+
+
+/**
+ * @brief Sign the chosen blinded coins, debit the reserve and persist
+ * the commitment.
+ *
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ *
+ * On error (like, insufficient funds), the client is notified.
+ *
+ * Note that on success, there are two possible states:
+ * 1.) KYC is required (awc.kyc.ok == false) or
+ * 2.) age withdraw was successful.
+ *
+ * @param connection HTTP-connection to the client
+ * @param awc The context for the current age withdraw request
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+sign_and_do_age_withdraw (
+ struct MHD_Connection *connection,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct TALER_BlindedCoinHashP h_coin_evs[awc->num_coins];
+ struct TALER_BlindedDenominationSignature denom_sigs[awc->num_coins];
+ uint8_t noreveal_index;
+
+ awc->now = GNUNET_TIME_timestamp_get ();
+
+ /* Pick the challenge */
+ noreveal_index =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+
+ awc->commitment.noreveal_index = noreveal_index;
+
+ /* Choose and sign the coins */
+ {
+ struct TEH_CoinSignData csds[awc->num_coins];
+ enum TALER_ErrorCode ec;
+
+ /* Pick the chosen blinded coins */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ csds[i].bp = &awc->coin_evs[i][noreveal_index];
+ csds[i].h_denom_pub = &awc->denom_hs[i];
+ }
+
+ ec = TEH_keys_denomination_batch_sign (awc->num_coins,
+ csds,
+ false,
+ denom_sigs);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signatures ready, starting DB interaction\n");
+
+ /* Prepare the hashes of the coins for insertion */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ TALER_coin_ev_hash (&awc->coin_evs[i][noreveal_index],
+ &awc->denom_hs[i],
+ &h_coin_evs[i]);
+ }
+
+ /* Run the transaction */
+ awc->commitment.h_coin_evs = h_coin_evs;
+ awc->commitment.denom_sigs = denom_sigs;
+ ret = TEH_DB_run_transaction (connection,
+ "run age withdraw",
+ TEH_MT_REQUEST_AGE_WITHDRAW,
+ result,
+ &age_withdraw_transaction,
+ awc);
+ /* Free resources */
+ for (unsigned int i = 0; i<awc->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ awc->commitment.h_coin_evs = NULL;
+ awc->commitment.denom_sigs = NULL;
+ return ret;
+}
+
+
+MHD_RESULT
+TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ MHD_RESULT mhd_ret;
+ const json_t *j_denom_hs;
+ const json_t *j_blinded_coin_evs;
+ struct AgeWithdrawContext awc = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denom_hs",
+ &j_denom_hs),
+ GNUNET_JSON_spec_array_const ("blinded_coin_evs",
+ &j_blinded_coin_evs),
+ GNUNET_JSON_spec_uint16 ("max_age",
+ &awc.commitment.max_age),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &awc.commitment.reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ awc.commitment.reserve_pub = *reserve_pub;
+
+
+ /* Parse the JSON body */
+ {
+ 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;
+ }
+
+ do {
+ /* Note: If we break the statement here at any point,
+ * a response to the client MUST have been populated
+ * with an appropriate answer and mhd_ret MUST have
+ * been set accordingly.
+ */
+
+ /* Parse denoms_h and blinded_coins_evs, partially fill awc */
+ if (GNUNET_OK !=
+ parse_age_withdraw_json (rc->connection,
+ j_denom_hs,
+ j_blinded_coin_evs,
+ &awc,
+ &mhd_ret))
+ break;
+
+ /* Ensure validity of denoms and calculate amounts and fees */
+ if (GNUNET_OK !=
+ are_denominations_valid (rc->connection,
+ awc.num_coins,
+ awc.denom_hs,
+ awc.coin_evs,
+ &awc.commitment.denom_serials,
+ &awc.commitment.amount_with_fee,
+ &mhd_ret))
+ break;
+
+ /* Now that amount_with_fee is calculated, verify the signature of
+ * the request body with the reserve key.
+ */
+ if (GNUNET_OK !=
+ verify_reserve_signature (rc->connection,
+ &awc.commitment,
+ &mhd_ret))
+ break;
+
+ /* Sign the chosen blinded coins, persist the commitment and
+ * charge the reserve.
+ * On error (like, insufficient funds), the client is notified.
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ * Note that on success, there are two possible states:
+ * KYC is required (awc.kyc.ok == false) or
+ * age withdraw was successful.
+ */
+ if (GNUNET_OK !=
+ sign_and_do_age_withdraw (rc->connection,
+ &awc,
+ &mhd_ret))
+ break;
+
+ /* Send back final response, depending on the outcome of
+ * the DB-transaction */
+ if (! awc.kyc.ok)
+ mhd_ret = TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &awc.h_payto,
+ &awc.kyc);
+ else
+ mhd_ret = reply_age_withdraw_success (rc->connection,
+ &awc.commitment.h_commitment,
+ awc.commitment.noreveal_index);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ free_age_withdraw_context_resources (&awc);
+ return mhd_ret;
+
+}
+
+
+/* end of taler-exchange-httpd_age-withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.h b/src/exchange/taler-exchange-httpd_age-withdraw.h
index 2ec76bb92..a76779190 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.h
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 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
@@ -14,25 +14,25 @@
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
+ * @file taler-exchange-httpd_age-withdraw.h
+ * @brief Handle /reserve/$RESERVE_PUB/age-withdraw requests
+ * @author Özgür Kesim
*/
-#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
+#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_AGE_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.
+ * Handle a "/reserves/$RESERVE_PUB/age-withdraw" request.
+ *
+ * Parses the batch of commitments to withdraw age restricted coins, and checks
+ * that the signature "reserve_sig" makes this a valid withdrawal request from
+ * the specified reserve. If the request is valid, the response contains a
+ * noreveal_index which the client has to use for the subsequent call to
+ * /age-withdraw/$ACH/reveal.
*
* @param rc request context
* @param root uploaded JSON data
@@ -40,8 +40,8 @@
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_withdraw (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
+TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
#endif
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
new file mode 100644
index 000000000..c9aca8e99
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
@@ -0,0 +1,610 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_age-withdraw_reveal.c
+ * @brief Handle /age-withdraw/$ACH/reveal requests
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd_metrics.h"
+#include "taler_error_codes.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_age-withdraw_reveal.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+/**
+ * State for an /age-withdraw/$ACH/reveal operation.
+ */
+struct AgeRevealContext
+{
+
+ /**
+ * Commitment for the age-withdraw operation, previously called by the
+ * client.
+ */
+ struct TALER_AgeWithdrawCommitmentHashP ach;
+
+ /**
+ * Public key of the reserve for with the age-withdraw commitment was
+ * originally made. This parameter is provided by the client again
+ * during the call to reveal in order to save a database-lookup.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Number of coins to reveal. MUST be equal to
+ * @e num_secrets/(kappa -1).
+ */
+ uint32_t num_coins;
+
+ /**
+ * Number of secrets in the reveal. MUST be a multiple of (kappa-1).
+ */
+ uint32_t num_secrets;
+
+ /**
+ * @e num_secrets secrets for disclosed coins.
+ */
+ struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets;
+
+ /**
+ * The data from the original age-withdraw. Will be retrieved from
+ * the DB via @a ach and @a reserve_pub.
+ */
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+};
+
+
+/**
+ * Parse the json body of an '/age-withdraw/$ACH/reveal' request. It extracts
+ * the denomination hashes, blinded coins and disclosed coins and allocates
+ * memory for those.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_disclosed_coin_secrets The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from
+ * @param[out] actx The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD.
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_withdraw_reveal_json (
+ struct MHD_Connection *connection,
+ const json_t *j_disclosed_coin_secrets,
+ struct AgeRevealContext *actx,
+ MHD_RESULT *mhd_ret)
+{
+ enum GNUNET_GenericReturnValue result = GNUNET_SYSERR;
+ size_t num_entries;
+
+ /* Verify JSON-structure consistency */
+ {
+ const char *error = NULL;
+
+ num_entries = json_array_size (j_disclosed_coin_secrets); /* 0, if not an array */
+
+ if (! json_is_array (j_disclosed_coin_secrets))
+ error = "disclosed_coin_secrets must be an array";
+ else if (num_entries == 0)
+ error = "disclosed_coin_secrets must not be empty";
+ else if (num_entries > TALER_MAX_FRESH_COINS)
+ error = "maximum number of coins that can be withdrawn has been exceeded";
+
+ if (NULL != error)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ actx->num_secrets = num_entries * (TALER_CNC_KAPPA - 1);
+ actx->num_coins = num_entries;
+
+ }
+
+ /* Continue parsing the parts */
+ {
+ unsigned int idx = 0;
+ unsigned int k = 0;
+ json_t *array = NULL;
+ json_t *value = NULL;
+
+ /* Parse diclosed keys */
+ actx->disclosed_coin_secrets =
+ GNUNET_new_array (actx->num_secrets,
+ struct TALER_PlanchetMasterSecretP);
+
+ json_array_foreach (j_disclosed_coin_secrets, idx, array) {
+ if (! json_is_array (array) ||
+ (TALER_CNC_KAPPA - 1 != json_array_size (array)))
+ {
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "couldn't parse entry no. %d in array disclosed_coin_secrets",
+ idx + 1);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ goto EXIT;
+
+ }
+
+ json_array_foreach (array, k, value)
+ {
+ struct TALER_PlanchetMasterSecretP *secret =
+ &actx->disclosed_coin_secrets[2 * idx + k];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, secret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "couldn't parse entry no. %d in array disclosed_coin_secrets[%d]",
+ k + 1,
+ idx + 1);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
+ goto EXIT;
+ }
+ }
+ };
+ }
+
+ result = GNUNET_OK;
+
+EXIT:
+ return result;
+}
+
+
+/**
+ * Check if the request belongs to an existing age-withdraw request.
+ * If so, sets the commitment object with the request data.
+ * Otherwise, it queues an appropriate MHD response.
+ *
+ * @param connection The HTTP connection to the client
+ * @param h_commitment Original commitment value sent with the age-withdraw request
+ * @param reserve_pub Reserve public key used in the original age-withdraw request
+ * @param[out] commitment Data from the original age-withdraw request
+ * @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 withdraw request has been found,
+ * #GNUNET_SYSERR if we did not find the request in the DB
+ */
+static enum GNUNET_GenericReturnValue
+find_original_commitment (
+ struct MHD_Connection *connection,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int try = 0; try < 3; try++)
+ {
+ qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls,
+ reserve_pub,
+ h_commitment,
+ commitment);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * @brief Derives a age-restricted planchet from a given secret and calculates the hash
+ *
+ * @param connection Connection to the client
+ * @param keys The denomination keys in memory
+ * @param secret The secret to a planchet
+ * @param denom_pub_h The hash of the denomination for the planchet
+ * @param max_age The maximum age allowed
+ * @param[out] bch Hashcode to write
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise, with an error message
+ * written to the client and @e result set.
+ */
+static enum GNUNET_GenericReturnValue
+calculate_blinded_hash (
+ struct MHD_Connection *connection,
+ const struct TEH_KeyStateHandle *keys,
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct TALER_DenominationHashP *denom_pub_h,
+ uint8_t max_age,
+ struct TALER_BlindedCoinHashP *bch,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TEH_DenominationKey *denom_key;
+ struct TALER_AgeCommitmentHash ach;
+
+ /* First, retrieve denomination details */
+ denom_key = TEH_keys_denomination_by_hash_from_state (keys,
+ denom_pub_h,
+ connection,
+ result);
+ if (NULL == denom_key)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* calculate age commitment hash */
+ {
+ struct TALER_AgeCommitmentProof acp;
+
+ TALER_age_restriction_from_secret (secret,
+ &denom_key->denom_pub.age_mask,
+ max_age,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &ach);
+ TALER_age_commitment_proof_free (&acp);
+ }
+
+ /* Next: calculate planchet */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail detail = {0};
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = denom_key->denom_pub.bsign_pub_key->cipher
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ union GNUNET_CRYPTO_BlindSessionNonce *noncep = NULL;
+
+ // FIXME: add logic to denom.c to do this!
+ if (GNUNET_CRYPTO_BSA_CS == bi.cipher)
+ {
+ struct TEH_CsDeriveData cdd = {
+ .h_denom_pub = &denom_key->h_denom_pub,
+ .nonce = &nonce.cs_nonce,
+ };
+
+ TALER_cs_withdraw_nonce_derive (secret,
+ &nonce.cs_nonce);
+ noncep = &nonce;
+ GNUNET_assert (TALER_EC_NONE ==
+ TEH_keys_denomination_cs_r_pub (
+ &cdd,
+ false,
+ &bi.details.cs_values));
+ }
+ TALER_planchet_blinding_secret_create (secret,
+ &alg_values,
+ &bks);
+ TALER_planchet_setup_coin_priv (secret,
+ &alg_values,
+ &coin_priv);
+ ret = TALER_planchet_prepare (&denom_key->denom_pub,
+ &alg_values,
+ &bks,
+ noncep,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &detail);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_json_pack (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "{ss}",
+ "details",
+ "failed to prepare planchet from base key");
+ return ret;
+ }
+
+ TALER_coin_ev_hash (&detail.blinded_planchet,
+ &denom_key->h_denom_pub,
+ bch);
+ TALER_blinded_planchet_free (&detail.blinded_planchet);
+ }
+
+ return ret;
+}
+
+
+/**
+ * @brief Checks the validity of the disclosed coins as follows:
+ * - Derives and calculates the disclosed coins'
+ * - public keys,
+ * - nonces (if applicable),
+ * - age commitments,
+ * - blindings
+ * - blinded hashes
+ * - Computes h_commitment with those calculated and the undisclosed hashes
+ * - Compares h_commitment with the value from the original commitment
+ * - Verifies that all public keys in indices larger than the age group
+ * corresponding to max_age are derived from the constant public key.
+ *
+ * The derivation of the blindings, (potential) nonces and age-commitment from
+ * a coin's private keys is defined in
+ * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw
+ *
+ * @param connection HTTP-connection to the client
+ * @param commitment Original commitment
+ * @param disclosed_coin_secrets The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many
+ * @param num_coins number of coins to reveal via @a disclosed_coin_secrets
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+verify_commitment_and_max_age (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ const struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets,
+ uint32_t num_coins,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct GNUNET_HashContext *hash_context;
+ struct TEH_KeyStateHandle *keys;
+
+ if (num_coins != commitment->num_coins)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "#coins");
+ return GNUNET_SYSERR;
+ }
+
+ /* We need the current keys in memory for the meta-data of the denominations */
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ for (size_t coin_idx = 0; coin_idx < num_coins; coin_idx++)
+ {
+ size_t i = 0; /* either 0 or 1, to index into coin_evs */
+
+ for (size_t k = 0; k<TALER_CNC_KAPPA; k++)
+ {
+ if (k == (size_t) commitment->noreveal_index)
+ {
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &commitment->h_coin_evs[coin_idx],
+ sizeof(commitment->h_coin_evs[coin_idx]));
+ }
+ else
+ {
+ /* j is the index into disclosed_coin_secrets[] */
+ size_t j = (TALER_CNC_KAPPA - 1) * coin_idx + i;
+ const struct TALER_PlanchetMasterSecretP *secret;
+ struct TALER_BlindedCoinHashP bch;
+
+ GNUNET_assert (2>i);
+ GNUNET_assert ((TALER_CNC_KAPPA - 1) * num_coins > j);
+
+ secret = &disclosed_coin_secrets[j];
+ i++;
+
+ ret = calculate_blinded_hash (connection,
+ keys,
+ secret,
+ &commitment->denom_pub_hashes[coin_idx],
+ commitment->max_age,
+ &bch,
+ result);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return GNUNET_SYSERR;
+ }
+
+ /* Continue the running hash of all coin hashes with the calculated
+ * hash-value of the current, disclosed coin */
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &bch,
+ sizeof(bch));
+ }
+ }
+ }
+
+ /* Finally, compare the calculated hash with the original commitment */
+ {
+ struct GNUNET_HashCode calc_hash;
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &calc_hash);
+
+ if (0 != GNUNET_CRYPTO_hash_cmp (&commitment->h_commitment.hash,
+ &calc_hash))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Send a response for "/age-withdraw/$RCH/reveal"
+ *
+ * @param connection The http connection to the client to send the response to
+ * @param commitment The data from the commitment with signatures
+ * @return a MHD result code
+ */
+static MHD_RESULT
+reply_age_withdraw_reveal_success (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment)
+{
+ json_t *list = json_array ();
+ GNUNET_assert (NULL != list);
+
+ for (unsigned int i = 0; i < commitment->num_coins; i++)
+ {
+ json_t *obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (NULL,
+ &commitment->denom_sigs[i]));
+ GNUNET_assert (0 ==
+ json_array_append_new (list,
+ obj));
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ list));
+}
+
+
+MHD_RESULT
+TEH_handler_age_withdraw_reveal (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ const json_t *root)
+{
+ MHD_RESULT result = MHD_NO;
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct AgeRevealContext actx = {0};
+ const json_t *j_disclosed_coin_secrets;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &actx.reserve_pub),
+ GNUNET_JSON_spec_array_const ("disclosed_coin_secrets",
+ &j_disclosed_coin_secrets),
+ GNUNET_JSON_spec_end ()
+ };
+
+ actx.ach = *ach;
+
+ /* Parse JSON body*/
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+
+
+ do {
+ /* Extract denominations, blinded and disclosed coins */
+ if (GNUNET_OK !=
+ parse_age_withdraw_reveal_json (
+ rc->connection,
+ j_disclosed_coin_secrets,
+ &actx,
+ &result))
+ break;
+
+ /* Find original commitment */
+ if (GNUNET_OK !=
+ find_original_commitment (
+ rc->connection,
+ &actx.ach,
+ &actx.reserve_pub,
+ &actx.commitment,
+ &result))
+ break;
+
+ /* Verify the computed h_commitment equals the committed one and that coins
+ * have a maximum age group corresponding max_age (age-mask dependent) */
+ if (GNUNET_OK !=
+ verify_commitment_and_max_age (
+ rc->connection,
+ &actx.commitment,
+ actx.disclosed_coin_secrets,
+ actx.num_coins,
+ &result))
+ break;
+
+ /* Finally, return the signatures */
+ result = reply_age_withdraw_reveal_success (rc->connection,
+ &actx.commitment);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ if (NULL != actx.commitment.denom_sigs)
+ for (unsigned int i = 0; i<actx.num_coins; i++)
+ TALER_blinded_denom_sig_free (&actx.commitment.denom_sigs[i]);
+ GNUNET_free (actx.commitment.denom_sigs);
+ GNUNET_free (actx.commitment.denom_pub_hashes);
+ GNUNET_free (actx.commitment.denom_serials);
+ GNUNET_free (actx.disclosed_coin_secrets);
+ return result;
+}
+
+
+/* end of taler-exchange-httpd_age-withdraw_reveal.c */
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
new file mode 100644
index 000000000..f7b813fe7
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_age-withdraw_reveal.h
+ * @brief Handle /age-withdraw/$ACH/reveal requests
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_REVEAL_H
+#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_REVEAL_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/age-withdraw/$ACH/reveal" request.
+ *
+ * The client got a noreveal_index in response to a previous request
+ * /reserve/$RESERVE_PUB/age-withdraw. It now has to reveal all n*(kappa-1)
+ * coin's private keys (except for the noreveal_index), from which all other
+ * coin-relevant data (blinding, age restriction, nonce) is derived from.
+ *
+ * The exchange computes those values, ensures that the maximum age is
+ * correctly applied, calculates the hash of the blinded envelopes, and -
+ * together with the non-disclosed blinded envelopes - compares the hash of
+ * those against the original commitment $ACH.
+ *
+ * If all those checks and the used denominations turn out to be correct, the
+ * exchange signs all blinded envelopes with their appropriate denomination
+ * keys.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param ach commitment to the age restricted coints from the age-withdraw request
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_age_withdraw_reveal (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-decision-get.c b/src/exchange/taler-exchange-httpd_aml-decision-get.c
new file mode 100644
index 000000000..b4f337db1
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision-get.c
@@ -0,0 +1,233 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_aml-decision-get.c
+ * @brief Return summary information about AML decision
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_aml-decision.h"
+#include "taler-exchange-httpd_metrics.h"
+
+
+/**
+ * Maximum number of records we return per request.
+ */
+#define MAX_RECORDS 1024
+
+/**
+ * Callback with KYC attributes about a particular user.
+ *
+ * @param[in,out] cls closure with a `json_t *` array to update
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ */
+static void
+kyc_attribute_cb (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
+{
+ json_t *kyc_attributes = cls;
+ json_t *attributes;
+
+ attributes = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ GNUNET_break (NULL != attributes);
+ GNUNET_assert (
+ 0 ==
+ json_array_append (
+ kyc_attributes,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("provider_section",
+ provider_section),
+ GNUNET_JSON_pack_timestamp ("collection_time",
+ collection_time),
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ expiration_time),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("attributes",
+ attributes))
+ )));
+}
+
+
+/**
+ * Return historic AML decision(s).
+ *
+ * @param[in,out] cls closure with a `json_t *` array to update
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_state AML decision status
+ * @param decision_time when was the decision made
+ * @param justification human-readable text justifying the decision
+ * @param decider_pub public key of the staff member
+ * @param decider_sig signature of the staff member
+ */
+static void
+aml_history_cb (
+ void *cls,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_state,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig)
+{
+ json_t *aml_history = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append (
+ aml_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("decider_pub",
+ decider_pub),
+ GNUNET_JSON_pack_string ("justification",
+ justification),
+ TALER_JSON_pack_amount ("new_threshold",
+ new_threshold),
+ GNUNET_JSON_pack_int64 ("new_state",
+ new_state),
+ GNUNET_JSON_pack_timestamp ("decision_time",
+ decision_time)
+ )));
+}
+
+
+MHD_RESULT
+TEH_handler_aml_decision_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ struct TALER_PaytoHashP h_payto;
+
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &h_payto,
+ sizeof (h_payto))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "h_payto");
+ }
+
+ if (NULL != args[1])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[1]);
+ }
+
+ {
+ json_t *aml_history;
+ json_t *kyc_attributes;
+ enum GNUNET_DB_QueryStatus qs;
+ bool none = false;
+
+ aml_history = json_array ();
+ GNUNET_assert (NULL != aml_history);
+ qs = TEH_plugin->select_aml_history (TEH_plugin->cls,
+ &h_payto,
+ &aml_history_cb,
+ aml_history);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (aml_history);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ none = true;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ none = false;
+ break;
+ }
+
+ kyc_attributes = json_array ();
+ GNUNET_assert (NULL != kyc_attributes);
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
+ &h_payto,
+ &kyc_attribute_cb,
+ kyc_attributes);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (aml_history);
+ json_decref (kyc_attributes);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (none)
+ {
+ json_decref (aml_history);
+ json_decref (kyc_attributes);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("aml_history",
+ aml_history),
+ GNUNET_JSON_pack_array_steal ("kyc_attributes",
+ kyc_attributes));
+ }
+}
+
+
+/* end of taler-exchange-httpd_aml-decision_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c
new file mode 100644
index 000000000..bf43fdbf2
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -0,0 +1,358 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_aml-decision.c
+ * @brief Handle request about an AML decision.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for #make_aml_decision()
+ */
+struct DecisionContext
+{
+ /**
+ * Justification given for the decision.
+ */
+ const char *justification;
+
+ /**
+ * When was the decision taken.
+ */
+ struct GNUNET_TIME_Timestamp decision_time;
+
+ /**
+ * New threshold for revising the decision.
+ */
+ struct TALER_Amount new_threshold;
+
+ /**
+ * Hash of payto://-URI of affected account.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * New AML state.
+ */
+ enum TALER_AmlDecisionState new_state;
+
+ /**
+ * Signature affirming the decision.
+ */
+ struct TALER_AmlOfficerSignatureP officer_sig;
+
+ /**
+ * Public key of the AML officer.
+ */
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub;
+
+ /**
+ * KYC requirements imposed, NULL for none.
+ */
+ const json_t *kyc_requirements;
+
+};
+
+
+/**
+ * Function implementing AML decision database 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.
+ *
+ * @param cls closure with a `struct DecisionContext`
+ * @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
+make_aml_decision (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DecisionContext *dc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp last_date;
+ bool invalid_officer;
+ uint64_t requirement_row = 0;
+
+ if ( (NULL != dc->kyc_requirements) &&
+ (0 != json_array_size (dc->kyc_requirements)) )
+ {
+ char *res = NULL;
+ size_t idx;
+ json_t *req;
+ bool satisfied;
+
+ json_array_foreach (dc->kyc_requirements, idx, req)
+ {
+ const char *r = json_string_value (req);
+
+ if (NULL == res)
+ {
+ res = GNUNET_strdup (r);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s %s",
+ res,
+ r);
+ GNUNET_free (res);
+ res = tmp;
+ }
+ }
+
+ {
+ json_t *kyc_details = NULL;
+
+ qs = TALER_KYCLOGIC_check_satisfied (
+ &res,
+ &dc->h_payto,
+ &kyc_details,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &satisfied);
+ json_decref (kyc_details);
+ }
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_satisfied_kyc_processes");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+ }
+ if (! satisfied)
+ {
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ res,
+ &dc->h_payto,
+ NULL, /* not a reserve */
+ &requirement_row);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_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 GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+ }
+ }
+ GNUNET_free (res);
+ }
+
+ qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
+ &dc->h_payto,
+ &dc->new_threshold,
+ dc->new_state,
+ dc->decision_time,
+ dc->justification,
+ dc->kyc_requirements,
+ requirement_row,
+ dc->officer_pub,
+ &dc->officer_sig,
+ &invalid_officer,
+ &last_date);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_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_aml_decision");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+ }
+ if (invalid_officer)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >=,
+ dc->decision_time))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+MHD_RESULT
+TEH_handler_post_aml_decision (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root)
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct DecisionContext dc = {
+ .officer_pub = officer_pub
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("officer_sig",
+ &dc.officer_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &dc.h_payto),
+ TALER_JSON_spec_amount ("new_threshold",
+ TEH_currency,
+ &dc.new_threshold),
+ GNUNET_JSON_spec_string ("justification",
+ &dc.justification),
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &dc.decision_time),
+ TALER_JSON_spec_aml_decision ("new_state",
+ &dc.new_state),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("kyc_requirements",
+ &dc.kyc_requirements),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_officer_aml_decision_verify (dc.justification,
+ dc.decision_time,
+ &dc.new_threshold,
+ &dc.h_payto,
+ dc.new_state,
+ dc.kyc_requirements,
+ dc.officer_pub,
+ &dc.officer_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ if (NULL != dc.kyc_requirements)
+ {
+ size_t index;
+ json_t *elem;
+
+ json_array_foreach (dc.kyc_requirements, index, elem)
+ {
+ const char *val;
+
+ if (! json_is_string (elem))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kyc_requirements array members must be strings");
+ }
+ val = json_string_value (elem);
+ if (GNUNET_SYSERR ==
+ TALER_KYCLOGIC_check_satisfiable (val))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AML_DECISION_UNKNOWN_CHECK,
+ val);
+ }
+ }
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "make-aml-decision",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &make_aml_decision,
+ &dc))
+ {
+ return mhd_ret;
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_aml-decision.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.h b/src/exchange/taler-exchange-httpd_aml-decision.h
new file mode 100644
index 000000000..8af742c0a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decision.h
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_aml-decision.h
+ * @brief Handle /aml/$OFFICER_PUB/decision requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_AML_DECISION_H
+#define TALER_EXCHANGE_HTTPD_AML_DECISION_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/aml/$OFFICER_PUB/decision" request. Parses the decision
+ * details, checks the signatures and if appropriately authorized executes
+ * the decision.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_post_aml_decision (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/decisions/$STATE" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be the state)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_decisions_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/decision/$H_PAYTO" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the AML history and KYC attributes for the account.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be one)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_decision_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-decisions-get.c b/src/exchange/taler-exchange-httpd_aml-decisions-get.c
new file mode 100644
index 000000000..763817cf6
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_aml-decisions-get.c
@@ -0,0 +1,215 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_aml-decisions-get.c
+ * @brief Return summary information about AML decisions
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_aml-decision.h"
+#include "taler-exchange-httpd_metrics.h"
+
+
+/**
+ * Maximum number of records we return per request.
+ */
+#define MAX_RECORDS 1024
+
+/**
+ * Return AML status.
+ *
+ * @param cls closure
+ * @param row_id current row in AML status table
+ * @param h_payto account for which the attribute data is stored
+ * @param threshold currently monthly threshold that would trigger an AML check
+ * @param status what is the current AML decision
+ */
+static void
+record_cb (
+ void *cls,
+ uint64_t row_id,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold,
+ enum TALER_AmlDecisionState status)
+{
+ json_t *records = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append (
+ records,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_int64 ("current_state",
+ status),
+ TALER_JSON_pack_amount ("threshold",
+ threshold),
+ GNUNET_JSON_pack_int64 ("rowid",
+ row_id)
+ )));
+}
+
+
+MHD_RESULT
+TEH_handler_aml_decisions_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ enum TALER_AmlDecisionState decision;
+ int delta = -20;
+ unsigned long long start;
+ const char *state_str = args[0];
+
+ if (NULL == state_str)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[0]);
+ }
+ if (0 == strcmp (state_str,
+ "pending"))
+ decision = TALER_AML_PENDING;
+ else if (0 == strcmp (state_str,
+ "frozen"))
+ decision = TALER_AML_FROZEN;
+ else if (0 == strcmp (state_str,
+ "normal"))
+ decision = TALER_AML_NORMAL;
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ state_str);
+ }
+ if (NULL != args[1])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[1]);
+ }
+
+ {
+ const char *p;
+
+ p = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "delta");
+ if (NULL != p)
+ {
+ char dummy;
+
+ if (1 != sscanf (p,
+ "%d%c",
+ &delta,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delta");
+ }
+ }
+ if (delta > 0)
+ start = 0;
+ else
+ start = INT64_MAX;
+ p = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "start");
+ if (NULL != p)
+ {
+ char dummy;
+
+ if (1 != sscanf (p,
+ "%llu%c",
+ &start,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "start");
+ }
+ }
+ }
+
+ {
+ json_t *records;
+ enum GNUNET_DB_QueryStatus qs;
+
+ records = json_array ();
+ GNUNET_assert (NULL != records);
+ if (INT_MIN == delta)
+ delta = INT_MIN + 1;
+ qs = TEH_plugin->select_aml_process (TEH_plugin->cls,
+ decision,
+ start,
+ GNUNET_MIN (MAX_RECORDS,
+ delta > 0
+ ? delta
+ : -delta),
+ delta > 0,
+ &record_cb,
+ records);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (records);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("records",
+ records));
+ }
+}
+
+
+/* end of taler-exchange-httpd_aml-decisions_get.c */
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c
index 0545c393b..84f27dd94 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.c
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -28,9 +28,10 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
+#include "taler_extensions_policy.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_deposit.h"
+#include "taler-exchange-httpd_batch-deposit.h"
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_lib.h"
#include "taler-exchange-httpd_keys.h"
@@ -41,10 +42,11 @@
*/
struct BatchDepositContext
{
+
/**
- * Information about the individual coin deposits.
+ * Array with the individual coin deposit fees.
*/
- struct TALER_EXCHANGEDB_Deposit *deposits;
+ struct TALER_Amount *deposit_fees;
/**
* Our timestamp (when we received the request).
@@ -54,37 +56,22 @@ struct BatchDepositContext
struct GNUNET_TIME_Timestamp exchange_timestamp;
/**
- * Hash over the proposal data between merchant and customer
- * (remains unknown to the Exchange).
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Public key of the merchant. Enables later identification
- * of the merchant in case of a need to rollback transactions.
- */
- struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
- * Salt used by the merchant to compute @e h_wire.
+ * Details about the batch deposit operation.
*/
- struct TALER_WireSaltP wire_salt;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
- /**
- * Hash over the wire details (with @e wire_salt).
- */
- struct TALER_MerchantWireHashP h_wire;
/**
- * Hash of the payto URI.
+ * Total amount that is accumulated with this deposit,
+ * without fee.
*/
- struct TALER_PaytoHashP h_payto;
+ struct TALER_Amount accumulated_total_without_fee;
/**
- * Information about the receiver for executing the transaction. URI in
- * payto://-format.
+ * True, if no policy was present in the request. Then
+ * @e policy_json is NULL and @e h_policy will be all zero.
*/
- const char *payto_uri;
+ bool has_no_policy;
/**
* Additional details for policy extension relevant for this
@@ -93,9 +80,11 @@ struct BatchDepositContext
json_t *policy_json;
/**
- * Will be true if policy_json were provided
+ * If @e policy_json was present, the corresponding policy extension
+ * calculates these details. These will be persisted in the policy_details
+ * table.
*/
- bool has_policy;
+ struct TALER_PolicyDetails policy_details;
/**
* Hash over @e policy_details, might be all zero
@@ -103,11 +92,9 @@ struct BatchDepositContext
struct TALER_ExtensionPolicyHashP h_policy;
/**
- * If @e policy_json was present, the corresponding policy extension
- * calculates these details. These will be persisted in the policy_details
- * table.
+ * Hash over the merchant's payto://-URI with the wire salt.
*/
- struct TALER_PolicyDetails policy_details;
+ struct TALER_MerchantWireHashP h_wire;
/**
* When @e policy_details are persisted, this contains the id of the record
@@ -115,40 +102,6 @@ struct BatchDepositContext
*/
uint64_t policy_details_serial_id;
- /**
- * Time when this request was generated. Used, for example, to
- * assess when (roughly) the income was achieved for tax purposes.
- * Note that the Exchange will only check that the timestamp is not "too
- * far" into the future (i.e. several days). The fact that the
- * timestamp falls within the validity period of the coin's
- * denomination key is irrelevant for the validity of the deposit
- * request, as obviously the customer and merchant could conspire to
- * set any timestamp. Also, the Exchange must accept very old deposit
- * requests, as the merchant might have been unable to transmit the
- * deposit request in a timely fashion (so back-dating is not
- * prevented).
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * How much time does the merchant have to issue a refund request?
- * Zero if refunds are not allowed. After this time, the coin
- * cannot be refunded.
- */
- struct GNUNET_TIME_Timestamp refund_deadline;
-
- /**
- * How much time does the merchant have to execute the wire transfer?
- * This time is advisory for aggregating transactions, not a hard
- * constraint (as the merchant can theoretically pick any time,
- * including one in the past).
- */
- struct GNUNET_TIME_Timestamp wire_deadline;
-
- /**
- * Number of coins in the batch.
- */
- unsigned int num_coins;
};
@@ -160,84 +113,52 @@ struct BatchDepositContext
* requested batch deposit operation with the given wiring details.
*
* @param connection connection to the client
- * @param bdc information about the batch deposit
+ * @param dc information about the batch deposit
* @return MHD result code
*/
static MHD_RESULT
reply_batch_deposit_success (
struct MHD_Connection *connection,
- const struct BatchDepositContext *bdc)
+ const struct BatchDepositContext *dc)
{
- json_t *arr;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
+ enum TALER_ErrorCode ec;
struct TALER_ExchangePublicKeyP pub;
-
-again:
- arr = json_array ();
- GNUNET_assert (NULL != arr);
- for (unsigned int i = 0; i<bdc->num_coins; i++)
+ struct TALER_ExchangeSignatureP sig;
+
+ for (unsigned int i = 0; i<bd->num_cdis; i++)
+ csigs[i] = &bd->cdis[i].csig;
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_deposit_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &bd->h_contract_terms,
+ &dc->h_wire,
+ dc->has_no_policy ? NULL : &dc->h_policy,
+ dc->exchange_timestamp,
+ bd->wire_deadline,
+ bd->refund_deadline,
+ &dc->accumulated_total_without_fee,
+ bd->num_cdis,
+ csigs,
+ &dc->bd.merchant_pub,
+ &pub,
+ &sig)))
{
- const struct TALER_EXCHANGEDB_Deposit *deposit = &bdc->deposits[i];
- struct TALER_ExchangePublicKeyP pubi;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
- struct TALER_Amount amount_without_fee;
-
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &deposit->amount_with_fee,
- &deposit->deposit_fee));
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_deposit_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &bdc->h_contract_terms,
- &bdc->h_wire,
- bdc->has_policy ? &bdc->h_policy: NULL,
- bdc->exchange_timestamp,
- bdc->wire_deadline,
- bdc->refund_deadline,
- &amount_without_fee,
- &deposit->coin.coin_pub,
- &bdc->merchant_pub,
- &pubi,
- &sig)))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- if (0 == i)
- pub = pubi;
- if (0 !=
- GNUNET_memcmp (&pub,
- &pubi))
- {
- /* note: in the future, maybe have batch
- sign API to avoid having to handle
- key rollover... */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange public key changed during batch deposit, trying again\n");
- json_decref (arr);
- goto again;
- }
- GNUNET_assert (
- 0 ==
- json_array_append_new (arr,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto (
- "exchange_sig",
- &sig))));
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- bdc->exchange_timestamp),
- GNUNET_JSON_pack_data_auto (
- "exchange_pub",
- &pub),
- GNUNET_JSON_pack_array_steal ("exchange_sigs",
- arr));
+ dc->exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig));
}
@@ -260,80 +181,113 @@ batch_deposit_transaction (void *cls,
MHD_RESULT *mhd_ret)
{
struct BatchDepositContext *dc = cls;
- enum GNUNET_DB_QueryStatus qs = GNUNET_SYSERR;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
+ uint32_t bad_balance_coin_index = UINT32_MAX;
bool balance_ok;
bool in_conflict;
/* If the deposit has a policy associated to it, persist it. This will
* insert or update the record. */
- if (dc->has_policy)
+ if (! dc->has_no_policy)
{
- qs = TEH_plugin->persist_policy_details (TEH_plugin->cls,
- &dc->policy_details,
- &dc->policy_details_serial_id,
- &dc->policy_details.
- accumulated_total,
- &dc->policy_details.
- fulfillment_state);
+ qs = TEH_plugin->persist_policy_details (
+ TEH_plugin->cls,
+ &dc->policy_details,
+ &dc->bd.policy_details_serial_id,
+ &dc->accumulated_total_without_fee,
+ &dc->policy_details.fulfillment_state);
if (qs < 0)
return qs;
+
+ dc->bd.policy_blocked =
+ dc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
}
- for (unsigned int i = 0; i<dc->num_coins; i++)
+ /* FIXME: replace by batch insert! */
+ for (unsigned int i = 0; i<bd->num_cdis; i++)
{
- const struct TALER_EXCHANGEDB_Deposit *deposit = &dc->deposits[i];
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bd->cdis[i];
uint64_t known_coin_id;
- qs = TEH_make_coin_known (&deposit->coin,
+ qs = TEH_make_coin_known (&cdi->coin,
connection,
&known_coin_id,
mhd_ret);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "make coin known (%s) returned %d\n",
+ TALER_B2S (&cdi->coin.coin_pub),
+ qs);
if (qs < 0)
return qs;
- qs = TEH_plugin->do_deposit (
- TEH_plugin->cls,
- deposit,
- known_coin_id,
- &dc->h_payto,
- (dc->has_policy)
- ? &dc->policy_details_serial_id
- : NULL,
- &dc->exchange_timestamp,
- &balance_ok,
- &in_conflict);
- if (qs < 0)
+ }
+
+ qs = TEH_plugin->do_deposit (
+ TEH_plugin->cls,
+ bd,
+ &dc->exchange_timestamp,
+ &balance_ok,
+ &bad_balance_coin_index,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store /batch-deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "do_deposit returned: %d / %s[%u] / %s\n",
+ qs,
+ balance_ok ? "balance ok" : "balance insufficient",
+ (unsigned int) bad_balance_coin_index,
+ in_conflict ? "in conflict" : "no conflict");
+ if (in_conflict)
+ {
+ struct TALER_MerchantWireHashP h_wire;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TEH_plugin->get_wire_hash_for_contract (
+ TEH_plugin->cls,
+ &bd->merchant_pub,
+ &bd->h_contract_terms,
+ &h_wire))
{
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
TALER_LOG_WARNING (
- "Failed to store /batch-deposit information in database\n");
+ "Failed to retrieve conflicting contract details from database\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"batch-deposit");
return qs;
}
- if (in_conflict)
- {
- /* FIXME: #7267 conficting contract != insufficient funds */
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
- &deposit->coin.denom_pub_hash,
- &deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &deposit->coin.denom_pub_hash,
- &deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_conflicting_contract (
+ connection,
+ TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
+ &h_wire);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_assert (bad_balance_coin_index < bd->num_cdis);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "returning history of conflicting coin (%s)\n",
+ TALER_B2S (&bd->cdis[bad_balance_coin_index].coin.coin_pub));
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &bd->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+ &bd->cdis[bad_balance_coin_index].coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
return qs;
@@ -346,34 +300,37 @@ batch_deposit_transaction (void *cls,
* @a ctx.
*
* @param connection connection we are handling
+ * @param dc information about the overall batch
* @param jcoin coin data to parse
- * @param dc overall batch deposit context information to use
- * @param[out] deposit where to store the result
+ * @param[out] cdi where to store the result
+ * @param[out] deposit_fee where to write the deposit fee
* @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
* #GNUNET_SYSERR on failure and no error could be returned
*/
static enum GNUNET_GenericReturnValue
parse_coin (struct MHD_Connection *connection,
- json_t *jcoin,
const struct BatchDepositContext *dc,
- struct TALER_EXCHANGEDB_Deposit *deposit)
+ json_t *jcoin,
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
+ struct TALER_Amount *deposit_fee)
{
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("contribution",
TEH_currency,
- &deposit->amount_with_fee),
+ &cdi->amount_with_fee),
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &deposit->coin.denom_pub_hash),
+ &cdi->coin.denom_pub_hash),
TALER_JSON_spec_denom_sig ("ub_sig",
- &deposit->coin.denom_sig),
+ &cdi->coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &deposit->coin.coin_pub),
+ &cdi->coin.coin_pub),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &deposit->coin.h_age_commitment),
- &deposit->coin.no_age_commitment),
+ &cdi->coin.h_age_commitment),
+ &cdi->coin.no_age_commitment),
GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &deposit->csig),
+ &cdi->csig),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
@@ -388,16 +345,18 @@ parse_coin (struct MHD_Connection *connection,
struct TEH_DenominationKey *dk;
MHD_RESULT mret;
- dk = TEH_keys_denomination_by_hash (&deposit->coin.denom_pub_hash,
+ dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
connection,
&mret);
if (NULL == dk)
{
GNUNET_JSON_parse_free (spec);
- return mret;
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
if (0 > TALER_amount_cmp (&dk->meta.value,
- &deposit->amount_with_fee))
+ &cdi->amount_with_fee))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -416,7 +375,7 @@ parse_coin (struct MHD_Connection *connection,
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
connection,
- &deposit->coin.denom_pub_hash,
+ &cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
"DEPOSIT"))
? GNUNET_NO
@@ -429,7 +388,7 @@ parse_coin (struct MHD_Connection *connection,
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
connection,
- &deposit->coin.denom_pub_hash,
+ &cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
"DEPOSIT"))
? GNUNET_NO
@@ -442,13 +401,14 @@ parse_coin (struct MHD_Connection *connection,
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
connection,
- &deposit->coin.denom_pub_hash,
+ &cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
"DEPOSIT"))
? GNUNET_NO
: GNUNET_SYSERR;
}
- if (dk->denom_pub.cipher != deposit->coin.denom_sig.cipher)
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ cdi->coin.denom_sig.unblinded_sig->cipher)
{
/* denomination cipher and denomination signature cipher not the same */
GNUNET_JSON_parse_free (spec);
@@ -461,21 +421,21 @@ parse_coin (struct MHD_Connection *connection,
: GNUNET_SYSERR;
}
- deposit->deposit_fee = dk->meta.fees.deposit;
+ *deposit_fee = dk->meta.fees.deposit;
/* check coin signature */
- switch (dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
break;
default:
break;
}
if (GNUNET_YES !=
- TALER_test_coin_valid (&deposit->coin,
+ TALER_test_coin_valid (&cdi->coin,
&dk->denom_pub))
{
TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
@@ -489,8 +449,8 @@ parse_coin (struct MHD_Connection *connection,
: GNUNET_SYSERR;
}
}
- if (0 < TALER_amount_cmp (&deposit->deposit_fee,
- &deposit->amount_with_fee))
+ if (0 < TALER_amount_cmp (deposit_fee,
+ &cdi->amount_with_fee))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -506,18 +466,21 @@ parse_coin (struct MHD_Connection *connection,
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_wallet_deposit_verify (
- &deposit->amount_with_fee,
- &deposit->deposit_fee,
+ &cdi->amount_with_fee,
+ deposit_fee,
&dc->h_wire,
- &dc->h_contract_terms,
- &deposit->coin.h_age_commitment,
- dc->has_policy ? &dc->h_policy : NULL,
- &deposit->coin.denom_pub_hash,
- dc->timestamp,
- &dc->merchant_pub,
- dc->refund_deadline,
- &deposit->coin.coin_pub,
- &deposit->csig))
+ &bd->h_contract_terms,
+ &bd->wallet_data_hash,
+ cdi->coin.no_age_commitment
+ ? NULL
+ : &cdi->coin.h_age_commitment,
+ NULL != dc->policy_json ? &dc->h_policy : NULL,
+ &cdi->coin.denom_pub_hash,
+ bd->wallet_timestamp,
+ &bd->merchant_pub,
+ bd->refund_deadline,
+ &cdi->coin.coin_pub,
+ &cdi->csig))
{
TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
GNUNET_JSON_parse_free (spec);
@@ -525,17 +488,10 @@ parse_coin (struct MHD_Connection *connection,
TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
- TALER_B2S (&deposit->coin.coin_pub)))
+ TALER_B2S (&cdi->coin.coin_pub)))
? GNUNET_NO
: GNUNET_SYSERR;
}
- deposit->merchant_pub = dc->merchant_pub;
- deposit->h_contract_terms = dc->h_contract_terms;
- deposit->wire_salt = dc->wire_salt;
- deposit->receiver_wire_account = (char *) dc->payto_uri;
- deposit->timestamp = dc->timestamp;
- deposit->refund_deadline = dc->refund_deadline;
- deposit->wire_deadline = dc->wire_deadline;
return GNUNET_OK;
}
@@ -546,62 +502,64 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
const char *const args[])
{
struct MHD_Connection *connection = rc->connection;
- struct BatchDepositContext dc;
- json_t *coins;
+ struct BatchDepositContext dc = { 0 };
+ struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
+ const json_t *coins;
bool no_refund_deadline = true;
- bool no_policy_json = true;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("merchant_payto_uri",
- &dc.payto_uri),
+ TALER_JSON_spec_payto_uri ("merchant_payto_uri",
+ &bd->receiver_wire_account),
GNUNET_JSON_spec_fixed_auto ("wire_salt",
- &dc.wire_salt),
+ &bd->wire_salt),
GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &dc.merchant_pub),
+ &bd->merchant_pub),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &dc.h_contract_terms),
- GNUNET_JSON_spec_json ("coins",
- &coins),
+ &bd->h_contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+ &bd->wallet_data_hash),
+ &bd->no_wallet_data_hash),
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("policy",
&dc.policy_json),
- &no_policy_json),
+ &dc.has_no_policy),
GNUNET_JSON_spec_timestamp ("timestamp",
- &dc.timestamp),
+ &bd->wallet_timestamp),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
- &dc.refund_deadline),
+ &bd->refund_deadline),
&no_refund_deadline),
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &dc.wire_deadline),
+ &bd->wire_deadline),
GNUNET_JSON_spec_end ()
};
- enum GNUNET_GenericReturnValue res;
(void) args;
- memset (&dc,
- 0,
- sizeof (dc));
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
{
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
+ enum GNUNET_GenericReturnValue res;
- dc.has_policy = ! no_policy_json;
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
/* validate merchant's wire details (as far as we can) */
{
char *emsg;
- emsg = TALER_payto_validate (dc.payto_uri);
+ emsg = TALER_payto_validate (bd->receiver_wire_account);
if (NULL != emsg)
{
MHD_RESULT ret;
@@ -616,9 +574,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
return ret;
}
}
- if (GNUNET_TIME_timestamp_cmp (dc.refund_deadline,
+ if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
>,
- dc.wire_deadline))
+ bd->wire_deadline))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -627,7 +585,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
NULL);
}
- if (GNUNET_TIME_absolute_is_never (dc.wire_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -636,33 +594,42 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
NULL);
}
- TALER_payto_hash (dc.payto_uri,
- &dc.h_payto);
- TALER_merchant_wire_signature_hash (dc.payto_uri,
- &dc.wire_salt,
+ TALER_payto_hash (bd->receiver_wire_account,
+ &bd->wire_target_h_payto);
+ TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
+ &bd->wire_salt,
&dc.h_wire);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &dc.accumulated_total_without_fee));
+
/* handle policy, if present */
- if (dc.has_policy)
+ if (! dc.has_no_policy)
{
const char *error_hint = NULL;
if (GNUNET_OK !=
TALER_extensions_create_policy_details (
+ TEH_currency,
dc.policy_json,
&dc.policy_details,
&error_hint))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
error_hint);
+ }
TALER_deposit_policy_hash (dc.policy_json,
&dc.h_policy);
}
- dc.num_coins = json_array_size (coins);
- if (0 == dc.num_coins)
+ bd->num_cdis = json_array_size (coins);
+ if (0 == bd->num_cdis)
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -671,7 +638,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"coins");
}
- if (TALER_MAX_FRESH_COINS < dc.num_coins)
+ if (TALER_MAX_FRESH_COINS < bd->num_cdis)
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -680,90 +647,91 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"coins");
}
- dc.deposits = GNUNET_new_array (dc.num_coins,
- struct TALER_EXCHANGEDB_Deposit);
- for (unsigned int i = 0; i<dc.num_coins; i++)
+
{
- do {
+ struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
+ GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
+ enum GNUNET_GenericReturnValue res;
+ unsigned int i;
+
+ bd->cdis = cdis;
+ dc.deposit_fees = deposit_fees;
+ for (i = 0; i<bd->num_cdis; i++)
+ {
+ struct TALER_Amount amount_without_fee;
+
res = parse_coin (connection,
- json_array_get (coins, i),
&dc,
- &dc.deposits[i]);
+ json_array_get (coins,
+ i),
+ &cdis[i],
+ &deposit_fees[i]);
if (GNUNET_OK != res)
break;
-
- /* If applicable, accumulate all contributions into the policy_details */
- if (dc.has_policy)
- {
- /* FIXME: how do deposit-fee and policy-fee interact? */
- struct TALER_Amount amount_without_fee;
-
- res = TALER_amount_subtract (&amount_without_fee,
- &dc.deposits[i].amount_with_fee,
- &dc.deposits[i].deposit_fee
- );
- res = TALER_amount_add (
- &dc.policy_details.accumulated_total,
- &dc.policy_details.accumulated_total,
- &amount_without_fee);
- }
- } while(0);
-
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (
+ &amount_without_fee,
+ &cdis[i].amount_with_fee,
+ &deposit_fees[i]));
+
+ GNUNET_assert (0 <=
+ TALER_amount_add (
+ &dc.accumulated_total_without_fee,
+ &dc.accumulated_total_without_fee,
+ &amount_without_fee));
+ }
if (GNUNET_OK != res)
{
for (unsigned int j = 0; j<i; j++)
- TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
- GNUNET_free (dc.deposits);
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
GNUNET_JSON_parse_free (spec);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- }
- dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure");
- }
+ dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
- /* execute transaction */
- {
- MHD_RESULT mhd_ret;
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute batch deposit",
+ TEH_MT_REQUEST_BATCH_DEPOSIT,
+ &mhd_ret,
+ &batch_deposit_transaction,
+ &dc))
+ {
+ for (unsigned int j = 0; j<bd->num_cdis; j++)
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
+ }
+ }
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute batch deposit",
- TEH_MT_REQUEST_BATCH_DEPOSIT,
- &mhd_ret,
- &batch_deposit_transaction,
- &dc))
+ /* generate regular response */
{
- GNUNET_JSON_parse_free (spec);
- for (unsigned int j = 0; j<dc.num_coins; j++)
- TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
- GNUNET_free (dc.deposits);
+ MHD_RESULT mhd_ret;
+
+ mhd_ret = reply_batch_deposit_success (connection,
+ &dc);
+ for (unsigned int j = 0; j<bd->num_cdis; j++)
+ TALER_denom_sig_free (&cdis[j].coin.denom_sig);
GNUNET_JSON_parse_free (spec);
return mhd_ret;
}
}
-
- /* generate regular response */
- {
- MHD_RESULT res;
-
- res = reply_batch_deposit_success (connection,
- &dc);
- for (unsigned int j = 0; j<dc.num_coins; j++)
- TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
- GNUNET_free (dc.deposits);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
}
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c
index a923784bf..2b80c2fc4 100644
--- a/src/exchange/taler-exchange-httpd_batch-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -26,12 +26,14 @@
#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_batch-withdraw.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keys.h"
+#include "taler_util.h"
/**
@@ -73,11 +75,16 @@ struct BatchWithdrawContext
{
/**
- * Public key of the reserv.
+ * Public key of the reserve.
*/
const struct TALER_ReservePublicKeyP *reserve_pub;
/**
+ * request context
+ */
+ const struct TEH_RequestContext *rc;
+
+ /**
* KYC status of the reserve used for the operation.
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
@@ -108,6 +115,11 @@ struct BatchWithdrawContext
*/
unsigned int planchets_length;
+ /**
+ * AML decision, #TALER_AML_NORMAL if we may proceed.
+ */
+ enum TALER_AmlDecisionState aml_decision;
+
};
@@ -151,6 +163,127 @@ batch_withdraw_amount_cb (void *cls,
/**
+ * 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;
+}
+
+
+/**
+ * Generates our final (successful) response.
+ *
+ * @param rc request context
+ * @param wc operation context
+ * @return MHD queue status
+ */
+static MHD_RESULT
+generate_reply_success (const struct TEH_RequestContext *rc,
+ const struct BatchWithdrawContext *wc)
+{
+ json_t *sigs;
+
+ if (! wc->kyc.ok)
+ {
+ /* KYC required */
+ return TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &wc->h_payto,
+ &wc->kyc);
+ }
+ if (TALER_AML_NORMAL != wc->aml_decision)
+ return TEH_RESPONSE_reply_aml_blocked (rc->connection,
+ wc->aml_decision);
+
+ sigs = json_array ();
+ GNUNET_assert (NULL != sigs);
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ sigs,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (
+ "ev_sig",
+ &pc->collectable.sig))));
+ }
+ TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ sigs));
+}
+
+
+/**
+ * Check if the @a wc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param 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 (const struct BatchWithdrawContext *wc,
+ MHD_RESULT *mret)
+{
+ const struct TEH_RequestContext *rc = wc->rc;
+
+ for (unsigned int i = 0; i<wc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &wc->planchets[i];
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
+ &pc->h_coin_envelope,
+ &pc->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_BATCH_WITHDRAW]++;
+ *mret = generate_reply_success (rc,
+ wc);
+ return true;
+}
+
+
+/**
* 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,
@@ -175,56 +308,189 @@ batch_withdraw_transaction (void *cls,
struct BatchWithdrawContext *wc = cls;
uint64_t ruuid;
enum GNUNET_DB_QueryStatus qs;
- bool balance_ok = false;
bool found = false;
- const char *kyc_required;
+ 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;
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->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->reserve_pub,
&wc->h_payto);
if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- *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 (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,
+ "reserves_get_origin");
+ return qs;
}
- kyc_required = TALER_KYCLOGIC_kyc_test_required (
- TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
- &wc->h_payto,
- TEH_plugin->select_satisfied_kyc_processes,
- TEH_plugin->cls,
- &batch_withdraw_amount_cb,
- wc);
- if (NULL != kyc_required)
+ /* 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)
{
- /* insert KYC requirement into DB! */
- wc->kyc.ok = false;
- return TEH_plugin->insert_kyc_requirement_for_account (
- TEH_plugin->cls,
- kyc_required,
+ qs = TALER_KYCLOGIC_kyc_test_required (
+ TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
&wc->h_payto,
- &wc->kyc.requirement_row);
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &batch_withdraw_amount_cb,
+ wc,
+ &kyc_required);
+ 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,
+ "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_payto,
+ wc->reserve_pub,
+ &wc->kyc.requirement_row);
+ GNUNET_free (kyc_required);
+ 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,
+ "insert_kyc_requirement_for_account");
+ }
+ return qs;
+ }
}
wc->kyc.ok = true;
+
qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls,
wc->now,
wc->reserve_pub,
&wc->batch_total,
+ TEH_age_restriction_enabled,
&found,
&balance_ok,
+ &reserve_balance,
+ &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,
"update_reserve_batch_withdraw");
+ }
return qs;
}
if (! found)
@@ -235,12 +501,29 @@ batch_withdraw_transaction (void *cls,
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);
*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;
@@ -251,14 +534,22 @@ batch_withdraw_transaction (void *cls,
{
struct PlanchetContext *pc = &wc->planchets[i];
const struct TALER_BlindedPlanchet *bp = &pc->blinded_planchet;
- const struct TALER_CsNonce *nonce;
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce = NULL;
bool denom_unknown = true;
bool conflict = true;
bool nonce_reuse = true;
- nonce = (TALER_DENOMINATION_CS == bp->cipher)
- ? &bp->details.cs_blinded_planchet.nonce
- : NULL;
+ switch (bp->blinded_message->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ nonce = (const union GNUNET_CRYPTO_BlindSessionNonce *)
+ &bp->blinded_message->details.cs_blinded_message.nonce;
+ break;
+ }
qs = TEH_plugin->do_batch_withdraw_insert (TEH_plugin->cls,
nonce,
&pc->collectable,
@@ -273,7 +564,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)
@@ -288,12 +579,18 @@ batch_withdraw_transaction (void *cls,
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
(conflict) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Idempotent coin in batch, not allowed. Aborting.\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
- NULL);
+ if (! check_request_idempotent (wc,
+ mhd_ret))
+ {
+ /* We do not support *some* of the coins of the request being
+ idempotent while others being fresh. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Idempotent coin in batch, not allowed. Aborting.\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
+ NULL);
+ }
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (nonce_reuse)
@@ -312,96 +609,6 @@ batch_withdraw_transaction (void *cls,
/**
- * Generates our final (successful) response.
- *
- * @param rc request context
- * @param wc operation context
- * @return MHD queue status
- */
-static MHD_RESULT
-generate_reply_success (const struct TEH_RequestContext *rc,
- const struct BatchWithdrawContext *wc)
-{
- json_t *sigs;
-
- if (! wc->kyc.ok)
- {
- /* KYC required */
- return TEH_RESPONSE_reply_kyc_required (rc->connection,
- &wc->h_payto,
- &wc->kyc);
- }
-
- sigs = json_array ();
- GNUNET_assert (NULL != sigs);
- for (unsigned int i = 0; i<wc->planchets_length; i++)
- {
- struct PlanchetContext *pc = &wc->planchets[i];
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- sigs,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_denom_sig (
- "ev_sig",
- &pc->collectable.sig))));
- }
- TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("ev_sigs",
- sigs));
-}
-
-
-/**
- * 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 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 (const struct TEH_RequestContext *rc,
- const struct BatchWithdrawContext *wc,
- MHD_RESULT *mret)
-{
- for (unsigned int i = 0; i<wc->planchets_length; i++)
- {
- struct PlanchetContext *pc = &wc->planchets[i];
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
- &pc->h_coin_envelope,
- &pc->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_BATCH_WITHDRAW]++;
- *mret = generate_reply_success (rc,
- wc);
- return true;
-}
-
-
-/**
* The request was parsed successfully. Prepare
* our side for the main DB transaction.
*
@@ -427,8 +634,8 @@ prepare_transaction (const struct TEH_RequestContext *rc,
enum TALER_ErrorCode ec;
ec = TEH_keys_denomination_batch_sign (
- csds,
wc->planchets_length,
+ csds,
false,
bss);
if (TALER_EC_NONE != ec)
@@ -528,8 +735,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
ksh = TEH_keys_get_state ();
if (NULL == ksh)
{
- if (! check_request_idempotent (rc,
- wc,
+ if (! check_request_idempotent (wc,
&mret))
{
return TALER_MHD_reply_with_error (rc->connection,
@@ -544,14 +750,15 @@ parse_planchets (const struct TEH_RequestContext *rc,
struct PlanchetContext *pc = &wc->planchets[i];
struct TEH_DenominationKey *dk;
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &pc->collectable.denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &pc->collectable.denom_pub_hash,
+ NULL,
+ NULL);
+
if (NULL == dk)
{
- if (! check_request_idempotent (rc,
- wc,
+ if (! check_request_idempotent (wc,
&mret))
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
@@ -563,8 +770,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
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,
+ if (! check_request_idempotent (wc,
&mret))
{
return TEH_RESPONSE_reply_expired_denom_pub_hash (
@@ -588,8 +794,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
if (dk->recoup_possible)
{
/* This denomination has been revoked */
- if (! check_request_idempotent (rc,
- wc,
+ if (! check_request_idempotent (wc,
&mret))
{
return TEH_RESPONSE_reply_expired_denom_pub_hash (
@@ -600,7 +805,8 @@ parse_planchets (const struct TEH_RequestContext *rc,
}
return mret;
}
- if (dk->denom_pub.cipher != pc->blinded_planchet.cipher)
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ pc->blinded_planchet.blinded_message->cipher)
{
/* denomination cipher and blinded planchet cipher not the same */
GNUNET_break_op (0);
@@ -632,17 +838,10 @@ parse_planchets (const struct TEH_RequestContext *rc,
NULL);
}
- if (GNUNET_OK !=
- TALER_coin_ev_hash (&pc->blinded_planchet,
- &pc->collectable.denom_pub_hash,
- &pc->collectable.h_coin_envelope))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- }
+ TALER_coin_ev_hash (&pc->blinded_planchet,
+ &pc->collectable.denom_pub_hash,
+ &pc->collectable.h_coin_envelope);
+
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_wallet_withdraw_verify (&pc->collectable.denom_pub_hash,
@@ -669,21 +868,20 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
const struct TALER_ReservePublicKeyP *reserve_pub,
const json_t *root)
{
- struct BatchWithdrawContext wc;
- json_t *planchets;
+ struct BatchWithdrawContext wc = {
+ .reserve_pub = reserve_pub,
+ .rc = rc
+ };
+ const json_t *planchets;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("planchets",
- &planchets),
+ GNUNET_JSON_spec_array_const ("planchets",
+ &planchets),
GNUNET_JSON_spec_end ()
};
- memset (&wc,
- 0,
- sizeof (wc));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TEH_currency,
&wc.batch_total));
- wc.reserve_pub = reserve_pub;
{
enum GNUNET_GenericReturnValue res;
@@ -693,20 +891,17 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
if (GNUNET_OK != res)
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
- if ( (! json_is_array (planchets)) ||
- (0 == json_array_size (planchets)) )
+ wc.planchets_length = json_array_size (planchets);
+ if (0 == wc.planchets_length)
{
- GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"planchets");
}
- wc.planchets_length = json_array_size (planchets);
if (wc.planchets_length > TALER_MAX_FRESH_COINS)
{
- GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
@@ -732,7 +927,6 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
TALER_blinded_planchet_free (&pc->blinded_planchet);
TALER_blinded_denom_sig_free (&pc->collectable.sig);
}
- GNUNET_JSON_parse_free (spec);
return ret;
}
}
diff --git a/src/exchange/taler-exchange-httpd_coins_get.c b/src/exchange/taler-exchange-httpd_coins_get.c
new file mode 100644
index 000000000..cd453275e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_coins_get.c
@@ -0,0 +1,709 @@
+/*
+ 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_coins_get.c
+ * @brief Handle GET /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_coins_get.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Add the headers we want to set for every response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param coin_pub public key of the coin
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+static json_t *
+compile_transaction_history (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+ json_t *history;
+
+ history = json_array ();
+ if (NULL == history)
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return NULL;
+ }
+ for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ {
+ const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
+ pos->details.deposit;
+ struct TALER_MerchantWireHashP h_wire;
+
+ TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
+ &deposit->wire_salt,
+ &h_wire);
+#if ENABLE_SANITY_CHECKS
+ /* internal sanity check before we hand out a bogus sig... */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ &deposit->amount_with_fee,
+ &deposit->deposit_fee,
+ &h_wire,
+ &deposit->h_contract_terms,
+ deposit->no_wallet_data_hash
+ ? NULL
+ : &deposit->wallet_data_hash,
+ deposit->no_age_commitment
+ ? NULL
+ : &deposit->h_age_commitment,
+ &deposit->h_policy,
+ &deposit->h_denom_pub,
+ deposit->timestamp,
+ &deposit->merchant_pub,
+ deposit->refund_deadline,
+ coin_pub,
+ &deposit->csig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "DEPOSIT"),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount_with_fee),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &deposit->deposit_fee),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ deposit->timestamp),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ deposit->refund_deadline)),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &deposit->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &h_wire),
+ GNUNET_JSON_pack_allow_null (
+ deposit->no_age_commitment ?
+ GNUNET_JSON_pack_string (
+ "h_age_commitment", NULL) :
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ &deposit->h_age_commitment)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &deposit->csig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_MELT:
+ {
+ const struct TALER_EXCHANGEDB_MeltListEntry *melt =
+ pos->details.melt;
+ const struct TALER_AgeCommitmentHash *phac = NULL;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ &melt->amount_with_fee,
+ &melt->melt_fee,
+ &melt->rc,
+ &melt->h_denom_pub,
+ &melt->h_age_commitment,
+ coin_pub,
+ &melt->coin_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+
+ /* Age restriction is optional. We communicate a NULL value to
+ * JSON_PACK below */
+ if (! melt->no_age_commitment)
+ phac = &melt->h_age_commitment;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MELT"),
+ TALER_JSON_pack_amount ("amount",
+ &melt->amount_with_fee),
+ TALER_JSON_pack_amount ("melt_fee",
+ &melt->melt_fee),
+ GNUNET_JSON_pack_data_auto ("rc",
+ &melt->rc),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &melt->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_RefundListEntry *refund =
+ pos->details.refund;
+ struct TALER_Amount value;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (
+ coin_pub,
+ &refund->h_contract_terms,
+ refund->rtransaction_id,
+ &refund->refund_amount,
+ &refund->merchant_pub,
+ &refund->merchant_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 >
+ TALER_amount_subtract (&value,
+ &refund->refund_amount,
+ &refund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "REFUND"),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &refund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &refund->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &refund->merchant_pub),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ refund->rtransaction_id),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &refund->merchant_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.old_coin_recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ &pr->coin.coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
+ the denomination key's RSA signature over coin_pub, but as the
+ wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "OLD-COIN-RECOUP"),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &pr->coin.coin_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_RECOUP:
+ {
+ const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
+ pos->details.recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ coin_pub,
+ &recoup->reserve_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP"),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &recoup->coin_blind),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.recoup_refresh;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key
+ hash and the denomination key's RSA signature over coin_pub, but as
+ the wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP-REFRESH"),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ &pr->old_coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pr->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &pr->coin_blind),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
+ = pos->details.purse_deposit;
+ const struct TALER_AgeCommitmentHash *phac = NULL;
+
+ if (! pd->no_age_commitment)
+ phac = &pd->h_age_commitment;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-DEPOSIT"),
+ TALER_JSON_pack_amount ("amount",
+ &pd->amount),
+ GNUNET_JSON_pack_string ("exchange_base_url",
+ NULL == pd->exchange_base_url
+ ? TEH_base_url
+ : pd->exchange_base_url),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &pd->purse_pub),
+ GNUNET_JSON_pack_bool ("refunded",
+ pd->refunded),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pd->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
+ pos->details.purse_refund;
+ struct TALER_Amount value;
+ enum TALER_ErrorCode ec;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (0 >
+ TALER_amount_subtract (&value,
+ &prefund->refund_amount,
+ &prefund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ ec = TALER_exchange_online_purse_refund_sign (
+ &TEH_keys_exchange_sign_,
+ &value,
+ &prefund->refund_fee,
+ coin_pub,
+ &prefund->purse_pub,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-REFUND"),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &prefund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &prefund->purse_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
+ = pos->details.reserve_open;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RESERVE-OPEN-DEPOSIT"),
+ TALER_JSON_pack_amount ("coin_contribution",
+ &role->coin_contribution),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &role->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &role->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ }
+ }
+ return history;
+}
+
+
+MHD_RESULT
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
+ uint64_t start_off = 0;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_Amount balance;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
+ /* Check signature */
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_COIN_HISTORY_SIGNATURE_HEADER,
+ &coin_sig,
+ required);
+ if (GNUNET_OK !=
+ TALER_wallet_coin_history_verify (start_off,
+ coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = start_off;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
+ }
+ else
+ {
+ etag_in = start_off;
+ }
+ }
+
+ /* Get history from DB between etag and now */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ coin_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_coin_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_coin_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ }
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
+ {
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
+ }
+ if (NULL == tl)
+ {
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ http_status = MHD_HTTP_NO_CONTENT;
+ }
+ else
+ {
+ /* 200: regular history */
+ json_t *history;
+
+ history = compile_transaction_history (coin_pub,
+ tl);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ tl = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ "Failed to compile coin history");
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+ http_status = MHD_HTTP_OK;
+ }
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_coins_get.c */
diff --git a/src/exchange/taler-exchange-httpd_deposit.h b/src/exchange/taler-exchange-httpd_coins_get.h
index a4d598a69..90405b55d 100644
--- a/src/exchange/taler-exchange-httpd_deposit.h
+++ b/src/exchange/taler-exchange-httpd_coins_get.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014 Taler Systems SA
+ Copyright (C) 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
@@ -14,36 +14,40 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-exchange-httpd_deposit.h
- * @brief Handle /deposit requests
+ * @file taler-exchange-httpd_coins_get.h
+ * @brief Handle GET /coins/$COIN_PUB requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
-#ifndef TALER_EXCHANGE_HTTPD_DEPOSIT_H
-#define TALER_EXCHANGE_HTTPD_DEPOSIT_H
+#ifndef TALER_EXCHANGE_HTTPD_COINS_GET_H
+#define TALER_EXCHANGE_HTTPD_COINS_GET_H
-#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
- * Handle a "/coins/$COIN_PUB/deposit" request. Parses the JSON, and, if
- * successful, passes the JSON data to #deposit_transaction() to
- * further check the details of the operation specified. If everything checks
- * out, this will ultimately lead to the "/deposit" being executed, or
- * rejected.
+ * Shutdown reserves-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/coins/$COIN_PUB/history" request. Parses the
+ * given "coins_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then respond with the
+ * transaction history of the coin.
*
- * @param connection the MHD connection to handle
+ * @param rc request context
* @param coin_pub public key of the coin
- * @param root uploaded JSON data
* @return MHD result code
- */
+ */
MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
-
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c
index 0c91f0bc9..898e23dd9 100644
--- a/src/exchange/taler-exchange-httpd_common_deposit.c
+++ b/src/exchange/taler-exchange-httpd_common_deposit.c
@@ -135,7 +135,8 @@ TEH_common_purse_deposit_parse_coin (
"PURSE CREATE"))
? GNUNET_NO : GNUNET_SYSERR;
}
- if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher)
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ coin->cpi.denom_sig.unblinded_sig->cipher)
{
/* denomination cipher and denomination signature cipher not the same */
GNUNET_JSON_parse_free (spec);
@@ -164,12 +165,12 @@ TEH_common_purse_deposit_parse_coin (
&coin->deposit_fee));
/* check coin signature */
- switch (dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
break;
default:
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c
new file mode 100644
index 000000000..bcee5a0d2
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_kyc.c
@@ -0,0 +1,302 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_common_kyc.c
+ * @brief shared logic for finishing a KYC process
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler_attributes.h"
+#include "taler_error_codes.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include <gnunet/gnunet_common.h>
+
+struct TEH_KycAmlTrigger
+{
+
+ /**
+ * Our logging scope.
+ */
+ struct GNUNET_AsyncScopeId scope;
+
+ /**
+ * account the operation is about
+ */
+ struct TALER_PaytoHashP account_id;
+
+ /**
+ * until when is the KYC data valid
+ */
+ struct GNUNET_TIME_Absolute expiration;
+
+ /**
+ * legitimization process the KYC data is about
+ */
+ uint64_t process_row;
+
+ /**
+ * name of the configuration section of the logic that was run
+ */
+ char *provider_section;
+
+ /**
+ * set to user ID at the provider, or NULL if not supported or unknown
+ */
+ char *provider_user_id;
+
+ /**
+ * provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ */
+ char *provider_legitimization_id;
+
+ /**
+ * function to call with the result
+ */
+ TEH_KycAmlTriggerCallback cb;
+
+ /**
+ * closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * user attributes returned by the provider
+ */
+ json_t *attributes;
+
+ /**
+ * response to return to the HTTP client
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Handle to an external process that evaluates the
+ * need to run AML on the account.
+ */
+ struct TALER_JSON_ExternalConversion *kyc_aml;
+
+ /**
+ * HTTP status code of @e response
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure of type `struct TEH_KycAmlTrigger *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process,
+ * non-zero if AML checks are required next
+ * @param result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+kyc_aml_finished (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result)
+{
+ struct TEH_KycAmlTrigger *kat = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ size_t eas;
+ void *ea;
+ const char *birthdate;
+ unsigned int birthday = 0;
+ struct GNUNET_ShortHashCode kyc_prox;
+ struct GNUNET_AsyncScopeSave old_scope;
+ unsigned int num_checks;
+ char **provided_checks;
+
+ kat->kyc_aml = NULL;
+ GNUNET_async_scope_enter (&kat->scope,
+ &old_scope);
+ TALER_CRYPTO_attributes_to_kyc_prox (kat->attributes,
+ &kyc_prox);
+ birthdate = json_string_value (json_object_get (kat->attributes,
+ TALER_ATTRIBUTE_BIRTHDATE));
+ if ( (TEH_age_restriction_enabled) &&
+ (NULL != birthdate) )
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_parse_coarse_date (birthdate,
+ &TEH_age_restriction_config.mask,
+ &birthday);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse birthdate `%s' from KYC attributes\n",
+ birthdate);
+ if (NULL != kat->response)
+ MHD_destroy_response (kat->response);
+ kat->http_status = MHD_HTTP_BAD_REQUEST;
+ kat->response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ TALER_ATTRIBUTE_BIRTHDATE);
+ goto RETURN_RESULT;
+ }
+ }
+
+ TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
+ kat->attributes,
+ &ea,
+ &eas);
+ TALER_KYCLOGIC_lookup_checks (kat->provider_section,
+ &num_checks,
+ &provided_checks);
+ qs = TEH_plugin->insert_kyc_attributes (
+ TEH_plugin->cls,
+ kat->process_row,
+ &kat->account_id,
+ &kyc_prox,
+ kat->provider_section,
+ num_checks,
+ (const char **) provided_checks,
+ birthday,
+ GNUNET_TIME_timestamp_get (),
+ kat->provider_user_id,
+ kat->provider_legitimization_id,
+ kat->expiration,
+ eas,
+ ea,
+ 0 != code);
+ for (unsigned int i = 0; i<num_checks; i++)
+ GNUNET_free (provided_checks[i]);
+ GNUNET_free (provided_checks);
+ GNUNET_free (ea);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Stored encrypted KYC process #%llu attributes: %d\n",
+ (unsigned long long) kat->process_row,
+ qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ if (NULL != kat->response)
+ MHD_destroy_response (kat->response);
+ kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do_insert_kyc_attributes");
+ /* Continued below to return the response */
+ }
+RETURN_RESULT:
+ /* Finally, return result to main handler */
+ kat->cb (kat->cb_cls,
+ kat->http_status,
+ kat->response);
+ kat->response = NULL;
+ TEH_kyc_finished_cancel (kat);
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+struct TEH_KycAmlTrigger *
+TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
+ unsigned int http_status,
+ struct MHD_Response *response,
+ TEH_KycAmlTriggerCallback cb,
+ void *cb_cls)
+{
+ struct TEH_KycAmlTrigger *kat;
+
+ kat = GNUNET_new (struct TEH_KycAmlTrigger);
+ kat->scope = *scope;
+ kat->process_row = process_row;
+ kat->account_id = *account_id;
+ kat->provider_section
+ = GNUNET_strdup (provider_section);
+ if (NULL != provider_user_id)
+ kat->provider_user_id
+ = GNUNET_strdup (provider_user_id);
+ if (NULL != provider_legitimization_id)
+ kat->provider_legitimization_id
+ = GNUNET_strdup (provider_legitimization_id);
+ kat->expiration = expiration;
+ kat->attributes = json_incref ((json_t*) attributes);
+ kat->http_status = http_status;
+ kat->response = response;
+ kat->cb = cb;
+ kat->cb_cls = cb_cls;
+ kat->kyc_aml
+ = TALER_JSON_external_conversion_start (
+ attributes,
+ &kyc_aml_finished,
+ kat,
+ TEH_kyc_aml_trigger,
+ TEH_kyc_aml_trigger,
+ NULL);
+ if (NULL == kat->kyc_aml)
+ {
+ GNUNET_break (0);
+ TEH_kyc_finished_cancel (kat);
+ return NULL;
+ }
+ return kat;
+}
+
+
+void
+TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat)
+{
+ if (NULL != kat->kyc_aml)
+ {
+ TALER_JSON_external_conversion_stop (kat->kyc_aml);
+ kat->kyc_aml = NULL;
+ }
+ GNUNET_free (kat->provider_section);
+ GNUNET_free (kat->provider_user_id);
+ GNUNET_free (kat->provider_legitimization_id);
+ json_decref (kat->attributes);
+ if (NULL != kat->response)
+ {
+ MHD_destroy_response (kat->response);
+ kat->response = NULL;
+ }
+ GNUNET_free (kat);
+}
+
+
+bool
+TEH_kyc_failed (uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_kyc_failure (
+ TEH_plugin->cls,
+ process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id);
+ GNUNET_break (qs >= 0);
+ return qs >= 0;
+}
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.h b/src/exchange/taler-exchange-httpd_common_kyc.h
new file mode 100644
index 000000000..8198679c9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_kyc.h
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_common_kyc.h
+ * @brief shared logic for finishing a KYC process
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_COMMON_KYC_H
+#define TALER_EXCHANGE_HTTPD_COMMON_KYC_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+typedef void
+(*TEH_KycAmlTriggerCallback) (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response);
+
+
+/**
+ * Handle for an asynchronous operation to finish
+ * a KYC process after running the AML trigger.
+ */
+struct TEH_KycAmlTrigger;
+
+// FIXME: also pass async log context and set it!
+/**
+ * We have finished a KYC process and obtained new
+ * @a attributes for a given @a account_id.
+ * Check with the KYC-AML trigger to see if we need
+ * to initiate an AML process, and store the attributes
+ * in the database. Then call @a cb.
+ *
+ * @param scope the HTTP request logging scope
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param expiration until when is the KYC check valid
+ * @param attributes user attributes returned by the provider
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel the operation
+ */
+struct TEH_KycAmlTrigger *
+TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
+ unsigned int http_status,
+ struct MHD_Response *response,
+ TEH_KycAmlTriggerCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel KYC finish operation.
+ *
+ * @param[in] kat operation to abort
+ */
+void
+TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat);
+
+
+/**
+ * Update state of a legitmization process to 'finished'
+ * (and failed, no attributes were obtained).
+ *
+ * @param process_row legitimization process the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_section name of the configuration section of the logic that was run
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @return true on success, false if updating the database failed
+ */
+bool
+TEH_kyc_failed (uint64_t process_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_section,
+ const char *provider_user_id,
+ const char *provider_legitimization_id);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_config.c b/src/exchange/taler-exchange-httpd_config.c
new file mode 100644
index 000000000..257dfa6ba
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.c
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2024 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_config.c
+ * @brief Handle /config requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_config.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include <jansson.h>
+
+
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ static struct MHD_Response *resp;
+ static struct GNUNET_TIME_Absolute a;
+
+ (void) args;
+ if ( (GNUNET_TIME_absolute_is_past (a)) &&
+ (NULL != resp) )
+ {
+ MHD_destroy_response (resp);
+ resp = NULL;
+ }
+ if (NULL == resp)
+ {
+ struct GNUNET_TIME_Timestamp km;
+ char dat[128];
+
+ a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ /* => /config response stays at most 48h in caches! */
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ TALER_MHD_get_date_string (km.abs_time,
+ dat);
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("supported_kyc_requirements",
+ TALER_KYCLOGIC_get_satisfiable ()),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_CONFIG_currency_specs_to_json (TEH_cspec)),
+ GNUNET_JSON_pack_string ("currency",
+ TEH_currency),
+ GNUNET_JSON_pack_string ("name",
+ "taler-exchange"),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:taler-exchange:c-reference")
+ ,
+ GNUNET_JSON_pack_string ("version",
+ EXCHANGE_PROTOCOL_VERSION));
+
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,max-age=21600")); /* 6h */
+ }
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+}
+
+
+/* end of taler-exchange-httpd_config.c */
diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h
new file mode 100644
index 000000000..068f51d41
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 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 EXCHANGEABILITY 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 taler-exchange-httpd_config.h
+ * @brief headers for /config handler
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_CONFIG_H
+#define TALER_EXCHANGE_HTTPD_CONFIG_H
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Taler protocol version in the format CURRENT:REVISION:AGE
+ * as used by GNU libtool. See
+ * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ *
+ * Please be very careful when updating and follow
+ * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
+ * precisely. Note that this version has NOTHING to do with the
+ * release version, and the format is NOT the same that semantic
+ * versioning uses either.
+ *
+ * When changing this version, you likely want to also update
+ * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
+ * exchange_api_handle.c!
+ *
+ * Returned via both /config and /keys endpoints.
+ */
+#define EXCHANGE_PROTOCOL_VERSION "19:2:2"
+
+
+/**
+ * Manages a /config call.
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_csr.c b/src/exchange/taler-exchange-httpd_csr.c
index a22fdfff5..e4fa4f5e4 100644
--- a/src/exchange/taler-exchange-httpd_csr.c
+++ b/src/exchange/taler-exchange-httpd_csr.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -21,6 +21,7 @@
* @brief Handle /csr requests
* @author Lucien Heuzeveldt
* @author Gian Demarmles
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -39,12 +40,12 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
{
struct TALER_RefreshMasterSecretP rms;
unsigned int csr_requests_num;
- json_t *csr_requests;
+ const json_t *csr_requests;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("rms",
&rms),
- GNUNET_JSON_spec_json ("nks",
- &csr_requests),
+ GNUNET_JSON_spec_array_const ("nks",
+ &csr_requests),
GNUNET_JSON_spec_end ()
};
enum TALER_ErrorCode ec;
@@ -65,7 +66,7 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
if ( (TALER_MAX_FRESH_COINS <= csr_requests_num) ||
(0 == csr_requests_num) )
{
- GNUNET_JSON_parse_free (spec);
+ GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
rc->connection,
MHD_HTTP_BAD_REQUEST,
@@ -74,12 +75,12 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
}
{
- struct TALER_ExchangeWithdrawValues ewvs[csr_requests_num];
+ struct GNUNET_CRYPTO_BlindingInputValues ewvs[csr_requests_num];
{
- struct TALER_CsNonce nonces[csr_requests_num];
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[csr_requests_num];
struct TALER_DenominationHashP denom_pub_hashes[csr_requests_num];
struct TEH_CsDeriveData cdds[csr_requests_num];
- struct TALER_DenominationCSPublicRPairP r_pubs[csr_requests_num];
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[csr_requests_num];
for (unsigned int i = 0; i < csr_requests_num; i++)
{
@@ -101,22 +102,20 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
-1);
if (GNUNET_OK != res)
{
- GNUNET_JSON_parse_free (spec);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
TALER_cs_refresh_nonce_derive (&rms,
coin_off,
&nonces[i]);
}
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i < csr_requests_num; i++)
{
- const struct TALER_CsNonce *nonce = &nonces[i];
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = &nonces[i];
const struct TALER_DenominationHashP *denom_pub_hash =
&denom_pub_hashes[i];
- ewvs[i].cipher = TALER_DENOMINATION_CS;
+ ewvs[i].cipher = GNUNET_CRYPTO_BSA_CS;
/* check denomination referenced by denom_pub_hash */
{
struct TEH_KeyStateHandle *ksh;
@@ -129,10 +128,10 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
NULL);
}
- dk = TEH_keys_denomination_by_hash2 (ksh,
- denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_pub_hash,
+ NULL,
+ NULL);
if (NULL == dk)
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
@@ -167,7 +166,8 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
"csr-melt");
}
- if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ dk->denom_pub.bsign_pub_key->cipher)
{
/* denomination is valid but not for CS */
return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
@@ -178,8 +178,8 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
cdds[i].h_denom_pub = denom_pub_hash;
cdds[i].nonce = nonce;
} /* for (i) */
- ec = TEH_keys_denomination_cs_batch_r_pub (cdds,
- csr_requests_num,
+ ec = TEH_keys_denomination_cs_batch_r_pub (csr_requests_num,
+ cdds,
true,
r_pubs);
if (TALER_EC_NONE != ec)
@@ -202,10 +202,13 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
for (unsigned int i = 0; i < csr_requests_num; i++)
{
json_t *csr_obj;
+ struct TALER_ExchangeWithdrawValues exw = {
+ .blinding_inputs = &ewvs[i]
+ };
csr_obj = GNUNET_JSON_PACK (
TALER_JSON_pack_exchange_withdraw_values ("ewv",
- &ewvs[i]));
+ &exw));
GNUNET_assert (NULL != csr_obj);
GNUNET_assert (0 ==
json_array_append_new (csr_response_ewvs,
@@ -228,18 +231,16 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[])
{
- struct TALER_CsNonce nonce;
+ struct GNUNET_CRYPTO_CsSessionNonce nonce;
struct TALER_DenominationHashP denom_pub_hash;
- struct TALER_ExchangeWithdrawValues ewv = {
- .cipher = TALER_DENOMINATION_CS
+ struct GNUNET_CRYPTO_BlindingInputValues ewv = {
+ .cipher = GNUNET_CRYPTO_BSA_CS
};
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed ("nonce",
- &nonce,
- sizeof (struct TALER_CsNonce)),
- GNUNET_JSON_spec_fixed ("denom_pub_hash",
- &denom_pub_hash,
- sizeof (struct TALER_DenominationHashP)),
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &denom_pub_hash),
GNUNET_JSON_spec_end ()
};
struct TEH_DenominationKey *dk;
@@ -266,10 +267,10 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
NULL);
}
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ &denom_pub_hash,
+ NULL,
+ NULL);
if (NULL == dk)
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
@@ -304,7 +305,8 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
"csr-withdraw");
}
- if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ dk->denom_pub.bsign_pub_key->cipher)
{
/* denomination is valid but not for CS */
return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
@@ -332,17 +334,16 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
NULL);
}
}
-
{
- json_t *csr_obj;
+ struct TALER_ExchangeWithdrawValues exw = {
+ .blinding_inputs = &ewv
+ };
- csr_obj = GNUNET_JSON_PACK (
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
TALER_JSON_pack_exchange_withdraw_values ("ewv",
- &ewv));
- GNUNET_assert (NULL != csr_obj);
- return TALER_MHD_reply_json_steal (rc->connection,
- csr_obj,
- MHD_HTTP_OK);
+ &exw));
}
}
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 5660074ee..6fec3fee4 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -19,9 +19,12 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <gnunet/gnunet_db_lib.h>
#include <pthread.h>
#include <jansson.h>
#include <gnunet/gnunet_json_lib.h>
+#include "taler_error_codes.h"
+#include "taler_exchangedb_plugin.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_exchangedb_lib.h"
@@ -37,14 +40,14 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
{
enum TALER_EXCHANGEDB_CoinKnownStatus cks;
struct TALER_DenominationHashP h_denom_pub;
- struct TALER_AgeCommitmentHash age_hash;
+ struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};
/* make sure coin is 'known' in database */
cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
coin,
known_coin_id,
&h_denom_pub,
- &age_hash);
+ &h_age_commitment);
switch (cks)
{
case TALER_EXCHANGEDB_CKS_ADDED:
@@ -61,22 +64,50 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
- /* FIXME: insufficient_funds != denom conflict! See issue #7267, need new
- * strategy for evidence gathering */
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
- &h_denom_pub,
- &coin->coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case TALER_EXCHANGEDB_CKS_AGE_CONFLICT:
- /* FIXME: insufficient_funds != Age conflict! See issue #7267, need new
- * strategy for evidence gathering */
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ /* The exchange has a seen this coin before, but with a different denomination.
+ * Get the corresponding signature and sent it to the client as proof */
+ {
+ struct
+ {
+ struct TALER_DenominationPublicKey pub;
+ struct TALER_DenominationSignature sig;
+ } prev_denom = {0};
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TEH_plugin->get_signature_for_known_coin (TEH_plugin->cls,
+ &coin->coin_pub,
+ &prev_denom.pub,
+ &prev_denom.sig))
+ {
+ /* There _should_ have been a result, because
+ * we ended here due to a conflict! */
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret = TEH_RESPONSE_reply_coin_denomination_conflict (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ &coin->coin_pub,
+ &prev_denom.pub,
+ &prev_denom.sig);
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL:
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL:
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS:
+ *mhd_ret = TEH_RESPONSE_reply_coin_age_commitment_conflict (
connection,
TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH,
+ cks,
&h_denom_pub,
- &coin->coin_pub);
+ &coin->coin_pub,
+ &h_age_commitment);
return GNUNET_DB_STATUS_HARD_ERROR;
}
GNUNET_assert (0);
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
deleted file mode 100644
index 740db7c1f..000000000
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ /dev/null
@@ -1,569 +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 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_deposit.c
- * @brief Handle /deposit requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- * @author Özgür Kesim
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_deposit.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Send confirmation of deposit success to client. This function
- * will create a signed message affirming the given information
- * and return it to the client. By this, the exchange affirms that
- * the coin had sufficient (residual) value for the specified
- * transaction and that it will execute the requested deposit
- * operation with the given wiring details.
- *
- * @param connection connection to the client
- * @param coin_pub public key of the coin
- * @param h_wire hash of wire details
- * @param h_policy hash of applicable policy extension
- * @param h_contract_terms hash of contract details
- * @param exchange_timestamp exchange's timestamp
- * @param refund_deadline until when this deposit be refunded
- * @param wire_deadline until when will the exchange wire the funds
- * @param merchant merchant public key
- * @param amount_without_fee fraction of coin value to deposit, without the fee
- * @return MHD result code
- */
-static MHD_RESULT
-reply_deposit_success (
- struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExtensionPolicyHashP *h_policy,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- struct GNUNET_TIME_Timestamp exchange_timestamp,
- struct GNUNET_TIME_Timestamp refund_deadline,
- struct GNUNET_TIME_Timestamp wire_deadline,
- const struct TALER_MerchantPublicKeyP *merchant,
- const struct TALER_Amount *amount_without_fee)
-{
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
-
- ec = TALER_exchange_online_deposit_confirmation_sign (
- &TEH_keys_exchange_sign_,
- h_contract_terms,
- h_wire,
- h_policy,
- exchange_timestamp,
- wire_deadline,
- refund_deadline,
- amount_without_fee,
- coin_pub,
- merchant,
- &pub,
- &sig);
- if (TALER_EC_NONE != ec)
- {
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- exchange_timestamp),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Closure for #deposit_transaction.
- */
-struct DepositContext
-{
- /**
- * Information about the deposit request.
- */
- const struct TALER_EXCHANGEDB_Deposit *deposit;
-
- /**
- * Our timestamp (when we received the request).
- * Possibly updated by the transaction if the
- * request is idempotent (was repeated).
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Hash of the payto URI.
- */
- struct TALER_PaytoHashP h_payto;
-
- /**
- * Row of of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
- /*
- * True if @e policy_json was provided
- */
- bool has_policy;
-
- /**
- * If @e has_policy is true, the corresponding policy extension calculates
- * these details. These will be persisted in the policy_details table.
- */
- struct TALER_PolicyDetails policy_details;
-
- /**
- * Hash over the policy data for this deposit (remains unknown to the
- * Exchange). Needed for the verification of the deposit's signature
- */
- struct TALER_ExtensionPolicyHashP h_policy;
-
- /**
- * When has_policy is true, and deposit->policy_details are
- * persisted, this contains the id of the record in the policy_details table.
- */
- uint64_t policy_details_serial_id;
-
-};
-
-
-/**
- * Execute database transaction for /deposit. 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.
- *
- * @param cls a `struct DepositContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-deposit_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct DepositContext *dc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool balance_ok;
- bool in_conflict;
-
- qs = TEH_make_coin_known (&dc->deposit->coin,
- connection,
- &dc->known_coin_id,
- mhd_ret);
- if (qs < 0)
- return qs;
- /* If the deposit has a policy associated to it, persist it. This will
- * insert or update the record. */
- if (dc->has_policy)
- {
- qs = TEH_plugin->persist_policy_details (
- TEH_plugin->cls,
- &dc->policy_details,
- &dc->policy_details_serial_id,
- &dc->policy_details.accumulated_total,
- &dc->policy_details.fulfillment_state);
-
- if (qs < 0)
- return qs;
- }
- qs = TEH_plugin->do_deposit (
- TEH_plugin->cls,
- dc->deposit,
- dc->known_coin_id,
- &dc->h_payto,
- (dc->has_policy)
- ? &dc->policy_details_serial_id
- : NULL,
- &dc->exchange_timestamp,
- &balance_ok,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "deposit");
- return qs;
- }
- if (in_conflict)
- {
- /* FIXME #7267: conflicting contract != insufficient funds */
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
- &dc->deposit->coin.denom_pub_hash,
- &dc->deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &dc->deposit->coin.denom_pub_hash,
- &dc->deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- struct DepositContext dc;
- struct TALER_EXCHANGEDB_Deposit deposit;
- const char *payto_uri;
- struct TALER_ExtensionPolicyHashP *ph_policy = NULL;
- bool no_policy_json;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("merchant_payto_uri",
- &payto_uri),
- GNUNET_JSON_spec_fixed_auto ("wire_salt",
- &deposit.wire_salt),
- TALER_JSON_spec_amount ("contribution",
- TEH_currency,
- &deposit.amount_with_fee),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &deposit.coin.denom_pub_hash),
- TALER_JSON_spec_denom_sig ("ub_sig",
- &deposit.coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &deposit.merchant_pub),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &deposit.h_contract_terms),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &deposit.coin.h_age_commitment),
- &deposit.coin.no_age_commitment),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &deposit.csig),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &deposit.timestamp),
- /* TODO: refund_deadline and merchant_pub will move into the
- * extension policy_merchant_refunds */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &deposit.refund_deadline),
- NULL),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &deposit.wire_deadline),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("policy",
- &dc.policy_details.policy_json),
- &no_policy_json),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_MerchantWireHashP h_wire;
-
- memset (&deposit,
- 0,
- sizeof (deposit));
- deposit.coin.coin_pub = *coin_pub;
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
-
- dc.has_policy = ! no_policy_json;
-
- /* validate merchant's wire details (as far as we can) */
- {
- char *emsg;
-
- emsg = TALER_payto_validate (payto_uri);
- if (NULL != emsg)
- {
- MHD_RESULT ret;
-
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- emsg);
- GNUNET_free (emsg);
- return ret;
- }
- }
- if (GNUNET_TIME_timestamp_cmp (deposit.refund_deadline,
- >,
- deposit.wire_deadline))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
- NULL);
- }
- if (GNUNET_TIME_absolute_is_never (deposit.wire_deadline.abs_time))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
- NULL);
- }
- deposit.receiver_wire_account = (char *) payto_uri;
- TALER_payto_hash (payto_uri,
- &dc.h_payto);
- TALER_merchant_wire_signature_hash (payto_uri,
- &deposit.wire_salt,
- &h_wire);
- dc.deposit = &deposit;
-
- /* new deposit */
- dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
- /* check denomination exists and is valid */
- {
- struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
-
- dk = TEH_keys_denomination_by_hash (&deposit.coin.denom_pub_hash,
- connection,
- &mret);
- if (NULL == dk)
- {
- GNUNET_JSON_parse_free (spec);
- return mret;
- }
- if (0 > TALER_amount_cmp (&dk->meta.value,
- &deposit.amount_with_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
- NULL);
- }
- if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
- {
- /* This denomination is past the expiration time for deposits */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "DEPOSIT");
- }
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "DEPOSIT");
- }
- if (dk->recoup_possible)
- {
- /* This denomination has been revoked */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- "DEPOSIT");
- }
- if (dk->denom_pub.cipher != deposit.coin.denom_sig.cipher)
- {
- /* denomination cipher and denomination signature cipher not the same */
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL);
- }
-
- deposit.deposit_fee = dk->meta.fees.deposit;
- /* check coin signature */
- switch (dk->denom_pub.cipher)
- {
- case TALER_DENOMINATION_RSA:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
- break;
- case TALER_DENOMINATION_CS:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
- break;
- default:
- break;
- }
- if (GNUNET_YES !=
- TALER_test_coin_valid (&deposit.coin,
- &dk->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL);
- }
- }
- if (0 < TALER_amount_cmp (&deposit.deposit_fee,
- &deposit.amount_with_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
- NULL);
- }
-
- /* Check policy input and create policy details */
- if (dc.has_policy)
- {
- const char *error_hint = NULL;
-
- if (GNUNET_OK !=
- TALER_extensions_create_policy_details (
- dc.policy_details.policy_json,
- &dc.policy_details,
- &error_hint))
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
- error_hint);
-
- TALER_deposit_policy_hash (dc.policy_details.policy_json,
- &dc.h_policy);
- ph_policy = &dc.h_policy;
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (&deposit.amount_with_fee,
- &deposit.deposit_fee,
- &h_wire,
- &deposit.h_contract_terms,
- &deposit.coin.h_age_commitment,
- ph_policy,
- &deposit.coin.denom_pub_hash,
- deposit.timestamp,
- &deposit.merchant_pub,
- deposit.refund_deadline,
- &deposit.coin.coin_pub,
- &deposit.csig))
- {
- TALER_LOG_WARNING ("Invalid signature on /deposit request\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
- NULL);
- }
-
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure");
- }
-
- /* execute transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute deposit",
- TEH_MT_REQUEST_DEPOSIT,
- &mhd_ret,
- &deposit_transaction,
- &dc))
- {
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* generate regular response */
- {
- struct TALER_Amount amount_without_fee;
- MHD_RESULT res;
-
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &deposit.amount_with_fee,
- &deposit.deposit_fee));
- res = reply_deposit_success (connection,
- &deposit.coin.coin_pub,
- &h_wire,
- ph_policy,
- &deposit.h_contract_terms,
- dc.exchange_timestamp,
- deposit.refund_deadline,
- deposit.wire_deadline,
- &deposit.merchant_pub,
- &amount_without_fee);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c
index 86af9fff1..0850d19eb 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.c
+++ b/src/exchange/taler-exchange-httpd_deposits_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2017, 2021 Taler Systems SA
+ 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
@@ -23,6 +23,7 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
+#include "taler_dbevents.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
@@ -38,6 +39,26 @@ struct DepositWtidContext
{
/**
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *prev;
+
+ /**
+ * Context for the request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
* Hash over the proposal data of the contract for which this deposit is made.
*/
struct TALER_PrivateContractHashP h_contract_terms;
@@ -65,6 +86,12 @@ struct DepositWtidContext
struct TALER_WireTransferIdentifierRawP wtid;
/**
+ * Signature by the merchant.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+
+ /**
* Set by #handle_wtid data to the coin's contribution to the wire transfer.
*/
struct TALER_Amount coin_contribution;
@@ -80,6 +107,11 @@ struct DepositWtidContext
struct GNUNET_TIME_Timestamp execution_time;
/**
+ * Timeout of the request, for long-polling.
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
* Set by #handle_wtid to the coin contribution to the transaction
* (that is, @e coin_contribution minus @e coin_fee).
*/
@@ -91,15 +123,57 @@ struct DepositWtidContext
struct TALER_EXCHANGEDB_KycStatus kyc;
/**
+ * AML status information for the receiving account.
+ */
+ enum TALER_AmlDecisionState aml_decision;
+
+ /**
* Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending
* (and the above were not set).
* Set to #GNUNET_SYSERR if there was a serious error.
*/
enum GNUNET_GenericReturnValue pending;
+
+ /**
+ * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
+ * if we were woken up due to shutdown.
+ */
+ enum GNUNET_GenericReturnValue suspended;
};
/**
+ * Head of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_head;
+
+/**
+ * Tail of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_tail;
+
+
+void
+TEH_deposits_get_cleanup ()
+{
+ struct DepositWtidContext *n;
+
+ for (struct DepositWtidContext *ctx = dwc_head;
+ NULL != ctx;
+ ctx = n)
+ {
+ n = ctx->next;
+ GNUNET_assert (GNUNET_YES == ctx->suspended);
+ ctx->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (ctx->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
+ }
+}
+
+
+/**
* A merchant asked for details about a deposit. Provide
* them. Generates the 200 reply.
*
@@ -128,6 +202,7 @@ reply_deposit_details (
&pub,
&sig)))
{
+ GNUNET_break (0);
return TALER_MHD_reply_with_ec (connection,
ec,
NULL);
@@ -184,7 +259,8 @@ deposits_get_transaction (void *cls,
&ctx->execution_time,
&ctx->coin_contribution,
&fee,
- &ctx->kyc);
+ &ctx->kyc,
+ &ctx->aml_decision);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -221,33 +297,101 @@ deposits_get_transaction (void *cls,
/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct DepositWtidContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct DepositWtidContext *ctx = cls;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (GNUNET_YES != ctx->suspended)
+ return; /* might get multiple wake-up events */
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
+ GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming request handling\n");
+ TEH_check_invariants ();
+ ctx->suspended = GNUNET_NO;
+ MHD_resume_connection (ctx->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
* Lookup and return the wire transfer identifier.
*
- * @param connection the MHD connection to handle
* @param ctx context of the signed request to execute
* @return MHD result code
*/
static MHD_RESULT
handle_track_transaction_request (
- struct MHD_Connection *connection,
struct DepositWtidContext *ctx)
{
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "handle deposits GET",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &deposits_get_transaction,
- ctx))
- return mhd_ret;
+ struct MHD_Connection *connection = ctx->rc->connection;
+
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (NULL == ctx->eh) )
+ {
+ struct TALER_CoinDepositEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+ .merchant_pub = ctx->merchant
+ };
+
+ ctx->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (ctx->timeout),
+ &rep.header,
+ &db_event_cb,
+ ctx);
+ GNUNET_break (NULL != ctx->eh);
+ }
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "handle deposits GET",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &deposits_get_transaction,
+ ctx))
+ return mhd_ret;
+ }
if (GNUNET_SYSERR == ctx->pending)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
"wire fees exceed aggregate in database");
- if (ctx->pending)
+ if (GNUNET_YES == ctx->pending)
+ {
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (GNUNET_NO == ctx->suspended) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending request handling\n");
+ GNUNET_CONTAINER_DLL_insert (dwc_head,
+ dwc_tail,
+ ctx);
+ ctx->suspended = GNUNET_YES;
+ MHD_suspend_connection (connection);
+ return MHD_YES;
+ }
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_ACCEPTED,
@@ -257,98 +401,124 @@ handle_track_transaction_request (
NULL)
: GNUNET_JSON_pack_uint64 ("requirement_row",
ctx->kyc.requirement_row)),
+ GNUNET_JSON_pack_uint64 ("aml_decision",
+ (uint32_t) ctx->aml_decision),
GNUNET_JSON_pack_bool ("kyc_ok",
ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time",
ctx->execution_time));
+ }
return reply_deposit_details (connection,
ctx);
}
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+dwc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ GNUNET_assert (GNUNET_NO == ctx->suspended);
+ if (NULL != ctx->eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ ctx->eh);
+ ctx->eh = NULL;
+ }
+ GNUNET_free (ctx);
+}
+
+
MHD_RESULT
TEH_handler_deposits_get (struct TEH_RequestContext *rc,
const char *const args[4])
{
- enum GNUNET_GenericReturnValue res;
- struct TALER_MerchantSignatureP merchant_sig;
- struct DepositWtidContext ctx;
+ struct DepositWtidContext *ctx = rc->rh_ctx;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &ctx.h_wire,
- sizeof (ctx.h_wire)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
- args[0]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[1],
- strlen (args[1]),
- &ctx.merchant,
- sizeof (ctx.merchant)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
- args[1]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[2],
- strlen (args[2]),
- &ctx.h_contract_terms,
- sizeof (ctx.h_contract_terms)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
- args[2]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[3],
- strlen (args[3]),
- &ctx.coin_pub,
- sizeof (ctx.coin_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
- args[3]);
- }
- res = TALER_MHD_parse_request_arg_data (rc->connection,
- "merchant_sig",
- &merchant_sig,
- sizeof (merchant_sig));
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* internal error */
- if (GNUNET_NO == res)
- return MHD_YES; /* parse error */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (NULL == ctx)
{
+ ctx = GNUNET_new (struct DepositWtidContext);
+ ctx->rc = rc;
+ rc->rh_ctx = ctx;
+ rc->rh_cleaner = &dwc_cleaner;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ctx->h_wire,
+ sizeof (ctx->h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
+ args[0]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &ctx->merchant,
+ sizeof (ctx->merchant)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
+ args[1]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[2],
+ strlen (args[2]),
+ &ctx->h_contract_terms,
+ sizeof (ctx->h_contract_terms)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
+ args[2]);
+ }
if (GNUNET_OK !=
- TALER_merchant_deposit_verify (&ctx.merchant,
- &ctx.coin_pub,
- &ctx.h_contract_terms,
- &ctx.h_wire,
- &merchant_sig))
+ GNUNET_STRINGS_string_to_data (args[3],
+ strlen (args[3]),
+ &ctx->coin_pub,
+ sizeof (ctx->coin_pub)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
- NULL);
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
+ args[3]);
+ }
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "merchant_sig",
+ &ctx->merchant_sig);
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &ctx->timeout);
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ {
+ if (GNUNET_OK !=
+ TALER_merchant_deposit_verify (&ctx->merchant,
+ &ctx->coin_pub,
+ &ctx->h_contract_terms,
+ &ctx->h_wire,
+ &ctx->merchant_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
+ NULL);
+ }
}
}
- return handle_track_transaction_request (rc->connection,
- &ctx);
+ return handle_track_transaction_request (ctx);
}
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.h b/src/exchange/taler-exchange-httpd_deposits_get.h
index aee7521a5..c7b1698bb 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.h
+++ b/src/exchange/taler-exchange-httpd_deposits_get.h
@@ -27,6 +27,13 @@
/**
+ * Resume long pollers on GET /deposits.
+ */
+void
+TEH_deposits_get_cleanup (void);
+
+
+/**
* Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
* request.
*
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
index c89a00411..d62a618ae 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021, 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
@@ -23,6 +23,7 @@
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_extensions.h"
+#include "taler_extensions_policy.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_extensions.h"
@@ -118,14 +119,14 @@ extension_update_event_cb (void *cls,
err.text,
err.source);
GNUNET_break (0);
- free (manifest_js);
+ free (manifest_str);
return;
}
// Call the parser for the extension
ret = extension->load_config (
- (struct TALER_Extension *) extension,
- json_object_get (manifest_js, "config"));
+ json_object_get (manifest_js, "config"),
+ (struct TALER_Extension *) extension);
if (GNUNET_OK != ret)
{
@@ -148,14 +149,13 @@ extension_update_event_cb (void *cls,
TEH_age_restriction_enabled = false;
if (NULL != conf)
{
- TEH_age_restriction_enabled = true;
+ TEH_age_restriction_enabled = extension->enabled;
TEH_age_restriction_config = *conf;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "[age restriction] DB event has changed the config to %s with mask: %s\n",
+ TEH_age_restriction_enabled ? "enabled": "DISABLED",
+ TALER_age_mask_to_string (&conf->mask));
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "[age restriction] DB event has changed the config to %s with mask: %s\n",
- TEH_age_restriction_enabled ? "enabled": "DISABLED",
- TALER_age_mask_to_string (&conf->mask));
-
}
// Finally, call TEH_keys_update_states in order to refresh the cached
@@ -209,16 +209,20 @@ TEH_extensions_init ()
{
const struct TALER_Extension *ext = it->extension;
uint32_t typ = htonl (ext->type);
- char *manifest = json_dumps (ext->manifest (ext), JSON_COMPACT);
+ json_t *jmani;
+ char *manifest;
+ jmani = ext->manifest (ext);
+ manifest = json_dumps (jmani,
+ JSON_COMPACT);
+ json_decref (jmani);
TEH_plugin->set_extension_manifest (TEH_plugin->cls,
ext->name,
manifest);
-
+ free (manifest);
extension_update_event_cb (NULL,
&typ,
sizeof(typ));
- free (manifest);
}
return GNUNET_OK;
@@ -253,11 +257,16 @@ policy_fulfillment_transaction (
{
struct TALER_PolicyFulfillmentTransactionData *fulfillment = cls;
+ /* FIXME[oec]: use connection and mhd_ret? */
+ (void) connection;
+ (void) mhd_ret;
+
return TEH_plugin->add_policy_fulfillment_proof (TEH_plugin->cls,
fulfillment);
}
+/* FIXME[oec]-#7999: In this handler: do we transition correctly between states? */
MHD_RESULT
TEH_extensions_post_handler (
struct TEH_RequestContext *rc,
@@ -335,14 +344,48 @@ TEH_extensions_post_handler (
qs = TEH_plugin->get_policy_details (TEH_plugin->cls,
&hcs[idx],
&policy_details[idx]);
- if (qs < 0)
+ if (0 > qs)
+ {
+ GNUNET_free (hcs);
+ GNUNET_free (policy_details);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+ "a policy_hash_code couldn't be found");
+ }
+
+ /* We proceed according to the state of fulfillment */
+ switch (policy_details[idx].fulfillment_state)
{
- error_msg = "a policy_hash_code couldn't be found";
+ case TALER_PolicyFulfillmentReady:
break;
+ case TALER_PolicyFulfillmentInsufficient:
+ error_msg = "a policy is not yet fully funded";
+ ret = GNUNET_SYSERR;
+ break;
+ case TALER_PolicyFulfillmentTimeout:
+ error_msg = "a policy is has already timed out";
+ ret = GNUNET_SYSERR;
+ break;
+ case TALER_PolicyFulfillmentSuccess:
+ /* FIXME[oec]-#8001: Idempotency handling. */
+ GNUNET_break (0);
+ break;
+ case TALER_PolicyFulfillmentFailure:
+ /* FIXME[oec]-#7999: What to do in the failure case? */
+ GNUNET_break (0);
+ break;
+ default:
+ /* Unknown state */
+ GNUNET_assert (0);
}
+
+ if (GNUNET_OK != ret)
+ break;
}
GNUNET_free (hcs);
+
if (GNUNET_OK != ret)
{
GNUNET_free (policy_details);
@@ -354,44 +397,39 @@ TEH_extensions_post_handler (
}
+ if (GNUNET_OK !=
+ ext->policy_post_handler (root,
+ &args[1],
+ policy_details,
+ policy_details_count,
+ &output))
{
- enum GNUNET_GenericReturnValue ret;
-
- ret = ext->policy_post_handler (root,
- &args[1],
- policy_details,
- policy_details_count,
- &output);
-
- if (GNUNET_OK != ret)
- {
- TALER_MHD_reply_json_steal (
- rc->connection,
- output,
- MHD_HTTP_BAD_REQUEST);
- }
+ return TALER_MHD_reply_json_steal (
+ rc->connection,
+ output,
+ MHD_HTTP_BAD_REQUEST);
+ }
- /* execute fulfillment transaction */
+ /* execute fulfillment transaction */
+ {
+ MHD_RESULT mhd_ret;
+ struct TALER_PolicyFulfillmentTransactionData fulfillment = {
+ .proof = root,
+ .timestamp = GNUNET_TIME_timestamp_get (),
+ .details = policy_details,
+ .details_count = policy_details_count
+ };
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "execute policy fulfillment",
+ TEH_MT_REQUEST_POLICY_FULFILLMENT,
+ &mhd_ret,
+ &policy_fulfillment_transaction,
+ &fulfillment))
{
- MHD_RESULT mhd_ret;
- struct TALER_PolicyFulfillmentTransactionData fulfillment = {
- .proof = root,
- .timestamp = GNUNET_TIME_timestamp_get (),
- .details = policy_details,
- .details_count = policy_details_count
- };
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "execute policy fulfillment",
- TEH_MT_REQUEST_POLICY_FULFILLMENT,
- &mhd_ret,
- &policy_fulfillment_transaction,
- &fulfillment))
- {
- json_decref (output);
- return mhd_ret;
- }
+ json_decref (output);
+ return mhd_ret;
}
}
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
index b37c68d95..0ec28e950 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020-2022 Taler Systems SA
+ Copyright (C) 2020-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
@@ -25,6 +25,7 @@
#include "taler_kyclogic_lib.h"
#include "taler_dbevents.h"
#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_config.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_plugin.h"
@@ -45,24 +46,6 @@
/**
- * Taler protocol version in the format CURRENT:REVISION:AGE
- * as used by GNU libtool. See
- * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
- *
- * Please be very careful when updating and follow
- * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
- * precisely. Note that this version has NOTHING to do with the
- * release version, and the format is NOT the same that semantic
- * versioning uses either.
- *
- * When changing this version, you likely want to also update
- * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
- * exchange_api_handle.c!
- */
-#define EXCHANGE_PROTOCOL_VERSION "14:0:2"
-
-
-/**
* Information about a denomination on offer by the denomination helper.
*/
struct HelperDenomination
@@ -405,6 +388,113 @@ struct SuspendedKeysRequests
struct GNUNET_TIME_Absolute timeout;
};
+
+/**
+ * Information we track about wire fees.
+ */
+struct WireFeeSet
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeSet *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WireFeeSet *prev;
+
+ /**
+ * Actual fees.
+ */
+ struct TALER_WireFeeSet fees;
+
+ /**
+ * Start date of fee validity (inclusive).
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * End date of fee validity (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Wire method the fees apply to.
+ */
+ char *method;
+};
+
+
+/**
+ * State we keep per thread to cache the /wire response.
+ */
+struct WireStateHandle
+{
+
+ /**
+ * JSON reply for /wire response.
+ */
+ json_t *json_reply;
+
+ /**
+ * ETag for this response (if any).
+ */
+ char *etag;
+
+ /**
+ * head of DLL of wire fees.
+ */
+ struct WireFeeSet *wfs_head;
+
+ /**
+ * Tail of DLL of wire fees.
+ */
+ struct WireFeeSet *wfs_tail;
+
+ /**
+ * Earliest timestamp of all the wire methods when we have no more fees.
+ */
+ struct GNUNET_TIME_Absolute cache_expiration;
+
+ /**
+ * @e cache_expiration time, formatted.
+ */
+ char dat[128];
+
+ /**
+ * For which (global) wire_generation was this data structure created?
+ * Used to check when we are outdated and need to be re-generated.
+ */
+ uint64_t wire_generation;
+
+ /**
+ * Is the wire data ready?
+ */
+ bool ready;
+
+};
+
+
+/**
+ * Stores the latest generation of our wire response.
+ */
+static struct WireStateHandle *wire_state;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *wire_eh;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the #wire_state
+ * because something external changed.
+ */
+static uint64_t wire_generation;
+
+
/**
* Stores the latest generation of our key state.
*/
@@ -457,6 +547,11 @@ static struct GNUNET_SCHEDULER_Task *keys_tt;
static struct GNUNET_TIME_Relative signkey_legal_duration;
/**
+ * What type of asset are we dealing with here?
+ */
+static char *asset_type;
+
+/**
* RSA security module public key, all zero if not known.
*/
static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;
@@ -478,6 +573,449 @@ static bool terminating;
/**
+ * Free memory associated with @a wsh
+ *
+ * @param[in] wsh wire state to destroy
+ */
+static void
+destroy_wire_state (struct WireStateHandle *wsh)
+{
+ struct WireFeeSet *wfs;
+
+ while (NULL != (wfs = wsh->wfs_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
+ wsh->wfs_tail,
+ wfs);
+ GNUNET_free (wfs->method);
+ GNUNET_free (wfs);
+ }
+ json_decref (wsh->json_reply);
+ GNUNET_free (wsh->etag);
+ GNUNET_free (wsh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the wire data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+wire_update_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /wire update event\n");
+ TEH_check_invariants ();
+ wire_generation++;
+ key_generation++;
+ TEH_resume_keys_requests (false);
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_wire_init ()
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &es,
+ &wire_update_event_cb,
+ NULL);
+ if (NULL == wire_eh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TEH_wire_done ()
+{
+ if (NULL != wire_state)
+ {
+ destroy_wire_state (wire_state);
+ wire_state = NULL;
+ }
+ if (NULL != wire_eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ wire_eh);
+ wire_eh = NULL;
+ }
+}
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `json_t *` object to expand with wire account details
+ * @param payto_uri the exchange bank account URI to add
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param master_sig master key signature affirming that this is a bank
+ * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
+ * @param bank_label label the wallet should use to display the account, can be NULL
+ * @param priority priority for ordering bank account labels
+ */
+static void
+add_wire_account (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority)
+{
+ json_t *a = cls;
+
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire account with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ if (0 !=
+ json_array_append_new (
+ a,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 ("priority",
+ priority),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig))))
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return;
+ }
+}
+
+
+/**
+ * Closure for #add_wire_fee().
+ */
+struct AddContext
+{
+ /**
+ * Wire method the fees are for.
+ */
+ char *wire_method;
+
+ /**
+ * Wire state we are building.
+ */
+ struct WireStateHandle *wsh;
+
+ /**
+ * Array to append the fee to.
+ */
+ json_t *a;
+
+ /**
+ * Set to the maximum end-date seen.
+ */
+ struct GNUNET_TIME_Absolute max_seen;
+};
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `struct AddContext`
+ * @param fees the wire fees we charge
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
+ */
+static void
+add_wire_fee (void *cls,
+ const struct TALER_WireFeeSet *fees,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct AddContext *ac = cls;
+ struct WireFeeSet *wfs;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ ac->wire_method,
+ start_date,
+ end_date,
+ fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
+ end_date.abs_time);
+ wfs = GNUNET_new (struct WireFeeSet);
+ wfs->start_date = start_date;
+ wfs->end_date = end_date;
+ wfs->fees = *fees;
+ wfs->method = GNUNET_strdup (ac->wire_method);
+ GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
+ ac->wsh->wfs_tail,
+ wfs);
+ if (0 !=
+ json_array_append_new (
+ ac->a,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("wire_fee",
+ &fees->wire),
+ TALER_JSON_pack_amount ("closing_fee",
+ &fees->closing),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ GNUNET_JSON_pack_data_auto ("sig",
+ master_sig))))
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return;
+ }
+}
+
+
+/**
+ * Create the /wire response from our database state.
+ *
+ * @return NULL on error
+ */
+static struct WireStateHandle *
+build_wire_state (void)
+{
+ json_t *wire_accounts_array;
+ json_t *wire_fee_object;
+ uint64_t wg = wire_generation; /* must be obtained FIRST */
+ enum GNUNET_DB_QueryStatus qs;
+ struct WireStateHandle *wsh;
+ json_t *wads;
+
+ wsh = GNUNET_new (struct WireStateHandle);
+ wsh->wire_generation = wg;
+ wire_accounts_array = json_array ();
+ GNUNET_assert (NULL != wire_accounts_array);
+ qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
+ &add_wire_account,
+ wire_accounts_array);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (wire_accounts_array);
+ wsh->ready = false;
+ return wsh;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Build /wire data with %u accounts\n",
+ (unsigned int) json_array_size (wire_accounts_array));
+ wire_fee_object = json_object ();
+ GNUNET_assert (NULL != wire_fee_object);
+ wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
+ {
+ json_t *account;
+ size_t index;
+
+ json_array_foreach (wire_accounts_array,
+ index,
+ account)
+ {
+ char *wire_method;
+ const char *payto_uri = json_string_value (json_object_get (account,
+ "payto_uri"));
+
+ GNUNET_assert (NULL != payto_uri);
+ wire_method = TALER_payto_get_method (payto_uri);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire method in `%s'\n",
+ payto_uri);
+ wsh->ready = false;
+ json_decref (wire_accounts_array);
+ json_decref (wire_fee_object);
+ return wsh;
+ }
+ if (NULL == json_object_get (wire_fee_object,
+ wire_method))
+ {
+ struct AddContext ac = {
+ .wire_method = wire_method,
+ .wsh = wsh,
+ .a = json_array ()
+ };
+
+ GNUNET_assert (NULL != ac.a);
+ qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
+ wire_method,
+ &add_wire_fee,
+ &ac);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (ac.a);
+ json_decref (wire_fee_object);
+ json_decref (wire_accounts_array);
+ GNUNET_free (wire_method);
+ wsh->ready = false;
+ return wsh;
+ }
+ if (0 != json_array_size (ac.a))
+ {
+ wsh->cache_expiration
+ = GNUNET_TIME_absolute_min (ac.max_seen,
+ wsh->cache_expiration);
+ GNUNET_assert (0 ==
+ json_object_set_new (wire_fee_object,
+ wire_method,
+ ac.a));
+ }
+ else
+ {
+ json_decref (ac.a);
+ }
+ }
+ GNUNET_free (wire_method);
+ }
+ }
+
+ wads = json_array (); /* #7271 */
+ GNUNET_assert (NULL != wads);
+ wsh->json_reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("accounts",
+ wire_accounts_array),
+ GNUNET_JSON_pack_array_steal ("wads",
+ wads),
+ GNUNET_JSON_pack_object_steal ("fees",
+ wire_fee_object));
+ wsh->ready = true;
+ return wsh;
+}
+
+
+void
+TEH_wire_update_state (void)
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &es,
+ NULL,
+ 0);
+ wire_generation++;
+ key_generation++;
+}
+
+
+/**
+ * Return the current key state for this thread. Possibly
+ * re-builds the key state if we have reason to believe
+ * that something changed.
+ *
+ * @return NULL on error
+ */
+struct WireStateHandle *
+get_wire_state (void)
+{
+ struct WireStateHandle *old_wsh;
+
+ old_wsh = wire_state;
+ if ( (NULL == old_wsh) ||
+ (old_wsh->wire_generation < wire_generation) )
+ {
+ struct WireStateHandle *wsh;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rebuilding /wire, generation upgrade from %llu to %llu\n",
+ (unsigned long long) (NULL == old_wsh) ? 0LL :
+ old_wsh->wire_generation,
+ (unsigned long long) wire_generation);
+ TEH_check_invariants ();
+ wsh = build_wire_state ();
+ wire_state = wsh;
+ if (NULL != old_wsh)
+ destroy_wire_state (old_wsh);
+ TEH_check_invariants ();
+ return wsh;
+ }
+ return old_wsh;
+}
+
+
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+ struct GNUNET_TIME_Timestamp ts,
+ const char *method)
+{
+ struct WireStateHandle *wsh = get_wire_state ();
+
+ for (struct WireFeeSet *wfs = wsh->wfs_head;
+ NULL != wfs;
+ wfs = wfs->next)
+ {
+ if (0 != strcmp (method,
+ wfs->method))
+ continue;
+ if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
+ >,
+ ts)) ||
+ (GNUNET_TIME_timestamp_cmp (ts,
+ >=,
+ wfs->end_date)) )
+ continue;
+ return &wfs->fees;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire fees for method `%s' at %s configured\n",
+ method,
+ GNUNET_TIME_timestamp2s (ts));
+ return NULL;
+}
+
+
+/**
* Function called to forcefully resume suspended keys requests.
*
* @param cls unused, NULL
@@ -577,12 +1115,20 @@ check_dk (void *cls,
(void) cls;
(void) hc;
- GNUNET_assert (TALER_DENOMINATION_INVALID != dk->denom_pub.cipher);
- if (TALER_DENOMINATION_RSA == dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check (
- dk->denom_pub.details.rsa_public_key));
- // nothing to do for TALER_DENOMINATION_CS
- return GNUNET_OK;
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key));
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ /* nothing to do for GNUNET_CRYPTO_BSA_CS */
+ return GNUNET_OK;
+ }
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
}
@@ -750,7 +1296,7 @@ free_denom_cb (void *cls,
* @param value the `struct HelperSignkey` to release
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
free_esign_cb (void *cls,
const struct GNUNET_PeerIdentity *pid,
void *value)
@@ -813,7 +1359,7 @@ destroy_key_helpers (struct HelperState *hs)
* denomination.
*/
static struct TALER_AgeMask
-load_age_mask (const char*section_name)
+load_age_mask (const char *section_name)
{
static const struct TALER_AgeMask null_mask = {0};
enum GNUNET_GenericReturnValue ret;
@@ -863,7 +1409,7 @@ load_age_mask (const char*section_name)
* @param validity_duration how long does the key remain available for signing;
* zero if the key has been revoked or purged
* @param h_rsa hash of the @a denom_pub that is available (or was purged)
- * @param denom_pub the public key itself, NULL if the key was revoked or purged
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
* @param sm_pub public key of the security module, NULL if the key was revoked or purged
* @param sm_sig signature from the security module, NULL if the key was revoked or purged
* The signature was already verified against @a sm_pub.
@@ -875,7 +1421,7 @@ helper_rsa_cb (
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Relative validity_duration,
const struct TALER_RsaPubHashP *h_rsa,
- const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
const struct TALER_SecurityModulePublicKeyP *sm_pub,
const struct TALER_SecurityModuleSignatureP *sm_sig)
{
@@ -905,10 +1451,9 @@ helper_rsa_cb (
hd->validity_duration = validity_duration;
hd->h_details.h_rsa = *h_rsa;
hd->sm_sig = *sm_sig;
- GNUNET_assert (TALER_DENOMINATION_RSA == denom_pub->cipher);
- TALER_denom_pub_deep_copy (&hd->denom_pub,
- denom_pub);
- GNUNET_assert (TALER_DENOMINATION_RSA == hd->denom_pub.cipher);
+ GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key =
+ GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
/* load the age mask for the denomination, if applicable */
hd->denom_pub.age_mask = load_age_mask (section_name);
TALER_denom_pub_hash (&hd->denom_pub,
@@ -944,7 +1489,7 @@ helper_rsa_cb (
* @param validity_duration how long does the key remain available for signing;
* zero if the key has been revoked or purged
* @param h_cs hash of the @a denom_pub that is available (or was purged)
- * @param denom_pub the public key itself, NULL if the key was revoked or purged
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
* @param sm_pub public key of the security module, NULL if the key was revoked or purged
* @param sm_sig signature from the security module, NULL if the key was revoked or purged
* The signature was already verified against @a sm_pub.
@@ -956,7 +1501,7 @@ helper_cs_cb (
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Relative validity_duration,
const struct TALER_CsPubHashP *h_cs,
- const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
const struct TALER_SecurityModulePublicKeyP *sm_pub,
const struct TALER_SecurityModuleSignatureP *sm_sig)
{
@@ -986,9 +1531,9 @@ helper_cs_cb (
hd->validity_duration = validity_duration;
hd->h_details.h_cs = *h_cs;
hd->sm_sig = *sm_sig;
- GNUNET_assert (TALER_DENOMINATION_CS == denom_pub->cipher);
- TALER_denom_pub_deep_copy (&hd->denom_pub,
- denom_pub);
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
/* load the age mask for the denomination, if applicable */
hd->denom_pub.age_mask = load_age_mask (section_name);
TALER_denom_pub_hash (&hd->denom_pub,
@@ -1094,6 +1639,7 @@ setup_key_helpers (struct HelperState *hs)
= GNUNET_CONTAINER_multipeermap_create (32,
GNUNET_NO /* MUST BE NO! */);
hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
+ "taler-exchange",
&helper_rsa_cb,
hs);
if (NULL == hs->rsadh)
@@ -1102,6 +1648,7 @@ setup_key_helpers (struct HelperState *hs)
return GNUNET_SYSERR;
}
hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
+ "taler-exchange",
&helper_cs_cb,
hs);
if (NULL == hs->csdh)
@@ -1110,6 +1657,7 @@ setup_key_helpers (struct HelperState *hs)
return GNUNET_SYSERR;
}
hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
+ "taler-exchange",
&helper_esign_cb,
hs);
if (NULL == hs->esh)
@@ -1279,6 +1827,17 @@ TEH_keys_init ()
"SIGNKEY_LEGAL_DURATION");
return GNUNET_SYSERR;
}
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "ASSET_TYPE",
+ &asset_type))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchange",
+ "ASSET_TYPE");
+ asset_type = GNUNET_strdup ("fiat");
+ }
keys_eh = TEH_plugin->event_listen (TEH_plugin->cls,
GNUNET_TIME_UNIT_FOREVER_REL,
&es,
@@ -1339,7 +1898,25 @@ denomination_info_cb (
struct TEH_KeyStateHandle *ksh = cls;
struct TEH_DenominationKey *dk;
- GNUNET_assert (TALER_DENOMINATION_INVALID != denom_pub->cipher);
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ h_denom_pub,
+ meta->start,
+ meta->expire_withdraw,
+ meta->expire_deposit,
+ meta->expire_legal,
+ &meta->value,
+ &meta->fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has denomination with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ denom_pub->bsign_pub_key->cipher);
if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) ||
GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) ||
GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) ||
@@ -1351,8 +1928,8 @@ denomination_info_cb (
return;
}
dk = GNUNET_new (struct TEH_DenominationKey);
- TALER_denom_pub_deep_copy (&dk->denom_pub,
- denom_pub);
+ TALER_denom_pub_copy (&dk->denom_pub,
+ denom_pub);
dk->h_denom_pub = *h_denom_pub;
dk->meta = *meta;
dk->master_sig = *master_sig;
@@ -1365,7 +1942,6 @@ denomination_info_cb (
&dk->h_denom_pub.hash,
dk,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-
}
@@ -1388,6 +1964,19 @@ signkey_info_cb (
struct SigningKey *sk;
struct GNUNET_PeerIdentity pid;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ exchange_pub,
+ meta->start,
+ meta->expire_sign,
+ meta->expire_legal,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has signing key with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
sk = GNUNET_new (struct SigningKey);
sk->exchange_pub = *exchange_pub;
sk->meta = *meta;
@@ -1669,14 +2258,14 @@ add_denom_key_cb (void *cls,
/**
* Add the headers we want to set for every /keys response.
*
- * @param ksh the key state to use
+ * @param cls the key state to use
* @param[in,out] response the response to modify
- * @return #GNUNET_OK on success
*/
-static enum GNUNET_GenericReturnValue
-setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
+static void
+setup_general_response_headers (void *cls,
struct MHD_Response *response)
{
+ struct TEH_KeyStateHandle *ksh = cls;
char dat[128];
TALER_MHD_add_global_headers (response);
@@ -1684,27 +2273,38 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
"application/json"));
- TALER_MHD_get_date_string (ksh->reload_time.abs_time,
- dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
- MHD_HTTP_HEADER_LAST_MODIFIED,
- dat));
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,must-revalidate,max-age=86400"));
if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
{
struct GNUNET_TIME_Relative r;
struct GNUNET_TIME_Absolute a;
+ struct GNUNET_TIME_Timestamp km;
struct GNUNET_TIME_Timestamp m;
+ struct GNUNET_TIME_Timestamp we;
r = GNUNET_TIME_relative_min (TEH_max_keys_caching,
ksh->rekey_frequency);
a = GNUNET_TIME_relative_to_absolute (r);
- m = GNUNET_TIME_absolute_to_timestamp (a);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration);
+ m = GNUNET_TIME_timestamp_min (we,
+ km);
TALER_MHD_get_date_string (m.abs_time,
dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setting /keys 'Expires' header to '%s'\n",
- dat);
+ "Setting /keys 'Expires' header to '%s' (rekey frequency is %s)\n",
+ dat,
+ GNUNET_TIME_relative2s (ksh->rekey_frequency,
+ false));
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_EXPIRES,
@@ -1718,12 +2318,6 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_VARY,
MHD_HTTP_HEADER_ACCEPT_ENCODING));
- /* Information is always public, revalidate after 1 hour */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "public,max-age=3600"));
- return GNUNET_OK;
}
@@ -1754,44 +2348,45 @@ wallet_threshold_cb (void *cls,
*
* @param[in,out] ksh key state handle we build @a krd for
* @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
- * @param last_cpd timestamp to use
- * @param signkeys list of sign keys to return
- * @param recoup list of revoked keys to return
- * @param denoms list of denominations to return
- * @param grouped_denominations list of grouped denominations to return
- * @param[in] h_grouped XOR of all hashes in @a grouped_demoninations
+ * @param last_cherry_pick_date timestamp to use
+ * @param[in,out] signkeys list of sign keys to return
+ * @param[in,out] recoup list of revoked keys to return
+ * @param[in,out] grouped_denominations list of grouped denominations to return
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
create_krd (struct TEH_KeyStateHandle *ksh,
const struct GNUNET_HashCode *denom_keys_hash,
- struct GNUNET_TIME_Timestamp last_cpd,
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date,
json_t *signkeys,
json_t *recoup,
- json_t *denoms,
- json_t *grouped_denominations,
- const struct GNUNET_HashCode *h_grouped)
+ json_t *grouped_denominations)
{
struct KeysResponseData krd;
struct TALER_ExchangePublicKeyP exchange_pub;
struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP grouped_exchange_pub;
- struct TALER_ExchangeSignatureP grouped_exchange_sig;
+ struct WireStateHandle *wsh;
json_t *keys;
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time));
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ last_cherry_pick_date.abs_time));
GNUNET_assert (NULL != signkeys);
GNUNET_assert (NULL != recoup);
- GNUNET_assert (NULL != denoms);
GNUNET_assert (NULL != grouped_denominations);
- GNUNET_assert (NULL != h_grouped);
GNUNET_assert (NULL != ksh->auditors);
GNUNET_assert (NULL != TEH_currency);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Creating /keys at cherry pick date %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
- /* Sign hash over denomination keys */
+ /* Sign hash over master signatures of all denomination keys until this time
+ (in reverse order). */
{
enum TALER_ErrorCode ec;
@@ -1800,7 +2395,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
TALER_exchange_online_key_set_sign (
&TEH_keys_exchange_sign2_,
ksh,
- last_cpd,
+ last_cherry_pick_date,
denom_keys_hash,
&exchange_pub,
&exchange_sig)))
@@ -1812,33 +2407,6 @@ create_krd (struct TEH_KeyStateHandle *ksh,
}
}
- /* Sign grouped hash */
- {
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec =
- TALER_exchange_online_key_set_sign (
- &TEH_keys_exchange_sign2_,
- ksh,
- last_cpd,
- h_grouped,
- &grouped_exchange_pub,
- &grouped_exchange_sig)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Could not create key response data: cannot sign grouped hash (%s)\n",
- TALER_ErrorCode_get_hint (ec));
- return GNUNET_SYSERR;
- }
- }
-
- /* both public keys really must be the same */
- GNUNET_assert (0 ==
- memcmp (&grouped_exchange_pub,
- &exchange_pub,
- sizeof(exchange_pub)));
-
{
const struct SigningKey *sk;
@@ -1849,6 +2417,12 @@ create_krd (struct TEH_KeyStateHandle *ksh,
ksh->signature_expires);
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Build /keys data with %u wire accounts\n",
+ (unsigned int) json_array_size (
+ json_object_get (wsh->json_reply,
+ "accounts")));
+
keys = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("version",
EXCHANGE_PROTOCOL_VERSION),
@@ -1856,6 +2430,20 @@ create_krd (struct TEH_KeyStateHandle *ksh,
TEH_base_url),
GNUNET_JSON_pack_string ("currency",
TEH_currency),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_CONFIG_currency_specs_to_json (TEH_cspec)),
+ TALER_JSON_pack_amount ("stefan_abs",
+ &TEH_stefan_abs),
+ TALER_JSON_pack_amount ("stefan_log",
+ &TEH_stefan_log),
+ GNUNET_JSON_pack_double ("stefan_lin",
+ (double) TEH_stefan_lin),
+ GNUNET_JSON_pack_string ("asset_type",
+ asset_type),
+ GNUNET_JSON_pack_bool ("rewards_allowed",
+ GNUNET_YES ==
+ TEH_enable_rewards),
GNUNET_JSON_pack_data_auto ("master_public_key",
&TEH_master_public_key),
GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
@@ -1864,8 +2452,15 @@ create_krd (struct TEH_KeyStateHandle *ksh,
signkeys),
GNUNET_JSON_pack_array_incref ("recoup",
recoup),
- GNUNET_JSON_pack_array_incref ("denoms",
- denoms),
+ GNUNET_JSON_pack_array_incref ("wads",
+ json_object_get (wsh->json_reply,
+ "wads")),
+ GNUNET_JSON_pack_array_incref ("accounts",
+ json_object_get (wsh->json_reply,
+ "accounts")),
+ GNUNET_JSON_pack_object_incref ("wire_fees",
+ json_object_get (wsh->json_reply,
+ "fees")),
GNUNET_JSON_pack_array_incref ("denominations",
grouped_denominations),
GNUNET_JSON_pack_array_incref ("auditors",
@@ -1873,13 +2468,11 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_JSON_pack_array_incref ("global_fees",
ksh->global_fees),
GNUNET_JSON_pack_timestamp ("list_issue_date",
- last_cpd),
- GNUNET_JSON_pack_data_auto ("eddsa_pub",
+ last_cherry_pick_date),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
- GNUNET_JSON_pack_data_auto ("eddsa_sig",
- &exchange_sig),
- GNUNET_JSON_pack_data_auto ("denominations_sig",
- &grouped_exchange_sig));
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig));
GNUNET_assert (NULL != keys);
/* Set wallet limit if KYC is configured */
@@ -1904,6 +2497,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *extensions = json_object ();
bool has_extensions = false;
+ GNUNET_assert (NULL != extensions);
/* Fill in the configurations of the enabled extensions */
for (const struct TALER_Extensions *iter = TALER_extensions_get_head ();
NULL != iter && NULL != iter->extension;
@@ -1937,7 +2531,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *sig;
int r;
- r = json_object_set (
+ r = json_object_set_new (
keys,
"extensions",
extensions);
@@ -1999,9 +2593,9 @@ create_krd (struct TEH_KeyStateHandle *ksh,
keys_json,
MHD_RESPMEM_MUST_FREE);
GNUNET_assert (NULL != krd.response_uncompressed);
- GNUNET_assert (GNUNET_OK ==
- setup_general_response_headers (ksh,
- krd.response_uncompressed));
+ setup_general_response_headers (ksh,
+ krd.response_uncompressed);
+ /* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_uncompressed,
MHD_HTTP_HEADER_ETAG,
@@ -2021,16 +2615,16 @@ create_krd (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_CONTENT_ENCODING,
"deflate")) );
- GNUNET_assert (GNUNET_OK ==
- setup_general_response_headers (ksh,
- krd.response_compressed));
+ setup_general_response_headers (ksh,
+ krd.response_compressed);
+ /* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_ETAG,
etag));
krd.etag = GNUNET_strdup (etag);
}
- krd.cherry_pick_date = last_cpd;
+ krd.cherry_pick_date = last_cherry_pick_date;
GNUNET_array_append (ksh->krd_array,
ksh->krd_array_length,
krd);
@@ -2039,6 +2633,194 @@ create_krd (struct TEH_KeyStateHandle *ksh,
/**
+ * Element in the `struct SignatureContext` array.
+ */
+struct SignatureElement
+{
+
+ /**
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int offset;
+
+ /**
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int group_offset;
+
+ /**
+ * Pointer to actual master signature to hash over.
+ */
+ struct TALER_MasterSignatureP master_sig;
+};
+
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
+ /**
+ * Array of signatures to hash over.
+ */
+ struct SignatureElement *elements;
+
+ /**
+ * Write offset in the @e elements array.
+ */
+ unsigned int elements_pos;
+
+ /**
+ * Allocated space for @e elements.
+ */
+ unsigned int elements_size;
+};
+
+
+/**
+ * Determine order to sort two elements by before
+ * we hash the master signatures. Used for
+ * sorting with qsort().
+ *
+ * @param a pointer to a `struct SignatureElement`
+ * @param b pointer to a `struct SignatureElement`
+ * @return 0 if equal, -1 if a < b, 1 if a > b.
+ */
+static int
+signature_context_sort_cb (const void *a,
+ const void *b)
+{
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
+
+ if (sa->group_offset < sb->group_offset)
+ return -1;
+ if (sa->group_offset > sb->group_offset)
+ return 1;
+ if (sa->offset < sb->offset)
+ return -1;
+ if (sa->offset > sb->offset)
+ return 1;
+ /* We should never have two disjoint elements
+ with same time and offset */
+ GNUNET_assert (sa == sb);
+ return 0;
+}
+
+
+/**
+ * Append a @a master_sig to the @a sig_ctx using the
+ * given attributes for (later) sorting.
+ *
+ * @param[in,out] sig_ctx signature context to update
+ * @param group_offset offset for the group
+ * @param offset offset for the entry
+ * @param master_sig master signature for the entry
+ */
+static void
+append_signature (struct SignatureContext *sig_ctx,
+ unsigned int group_offset,
+ unsigned int offset,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct SignatureElement *element;
+ unsigned int new_size;
+
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
+ {
+ if (0 == sig_ctx->elements_size)
+ new_size = 1024;
+ else
+ new_size = sig_ctx->elements_size * 2;
+ GNUNET_array_grow (sig_ctx->elements,
+ sig_ctx->elements_size,
+ new_size);
+ }
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
+}
+
+
+/**
+ *GroupData is the value we store for each group meta-data */
+struct GroupData
+{
+ /**
+ * The json blob with the group meta-data and list of denominations
+ */
+ json_t *json;
+
+ /**
+ * List of denominations for the group,
+ * included in @e json, do not free separately!
+ */
+ json_t *list;
+
+ /**
+ * Offset of the group in the final array.
+ */
+ unsigned int group_off;
+
+};
+
+
+/**
+ * Helper function called to clean up the group data
+ * in the denominations_by_group below.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value a `struct GroupData` to free
+ * @return #GNUNET_OK
+ */
+static int
+free_group (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct GroupData *gd = value;
+
+ (void) cls;
+ (void) key;
+ GNUNET_free (gd);
+ return GNUNET_OK;
+}
+
+
+static void
+compute_msig_hash (struct SignatureContext *sig_ctx,
+ struct GNUNET_HashCode *hc)
+{
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ qsort (sig_ctx->elements,
+ sig_ctx->elements_pos,
+ sizeof (struct SignatureElement),
+ &signature_context_sort_cb);
+ for (unsigned int i = 0; i<sig_ctx->elements_pos; i++)
+ {
+ struct SignatureElement *element = &sig_ctx->elements[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding %u,%u,%s\n",
+ element->group_offset,
+ element->offset,
+ TALER_B2S (&element->master_sig));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &element->master_sig,
+ sizeof (element->master_sig));
+ }
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ hc);
+}
+
+
+/**
* Update the "/keys" responses in @a ksh, computing the detailed replies.
*
* This function is to recompute all (including cherry-picked) responses we
@@ -2050,23 +2832,50 @@ create_krd (struct TEH_KeyStateHandle *ksh,
static enum GNUNET_GenericReturnValue
finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
json_t *recoup;
- struct SignKeyCtx sctx;
- json_t *denoms = NULL;
+ struct SignKeyCtx sctx = {
+ .min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL
+ };
json_t *grouped_denominations = NULL;
- struct GNUNET_TIME_Timestamp last_cpd;
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date;
struct GNUNET_CONTAINER_Heap *heap;
- struct GNUNET_HashContext *hash_context = NULL;
- struct GNUNET_HashCode grouped_hash_xor = {0};
+ struct SignatureContext sig_ctx = { 0 };
+ /* Remember if we have any denomination with age restriction */
+ bool has_age_restricted_denomination = false;
+ struct WireStateHandle *wsh;
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ json_array_size (json_object_get (wsh->json_reply,
+ "accounts")) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire accounts available. Refusing to generate /keys response.\n");
+ return GNUNET_NO;
+ }
sctx.signkeys = json_array ();
GNUNET_assert (NULL != sctx.signkeys);
- sctx.min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL;
+ recoup = json_array ();
+ GNUNET_assert (NULL != recoup);
+ grouped_denominations = json_array ();
+ GNUNET_assert (NULL != grouped_denominations);
+
GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
&add_sign_key_cb,
&sctx);
- recoup = json_array ();
- GNUNET_assert (NULL != recoup);
+ if (0 == json_array_size (sctx.signkeys))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No online signing keys available. Refusing to generate /keys response.\n");
+ ret = GNUNET_NO;
+ goto CLEANUP;
+ }
heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
{
struct DenomKeyCtx dkc = {
@@ -2083,121 +2892,52 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
sctx.min_sk_frequency);
}
- denoms = json_array ();
- GNUNET_assert (NULL != denoms);
- hash_context = GNUNET_CRYPTO_hash_context_start ();
-
- grouped_denominations = json_array ();
- GNUNET_assert (NULL != grouped_denominations);
+ last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
- last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
-
- // FIXME: This block contains the implementation of the DEPRECATED
- // "denom_pubs" array along with the new grouped "denominations".
- // "denom_pubs" Will be removed sooner or later.
{
struct TEH_DenominationKey *dk;
struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
- /* groupData is the value we store for each group meta-data */
- struct groupData
- {
- /**
- * The json blob with the group meta-data and list of denominations
- */
- json_t *json;
-
- /**
- * xor of all hashes of denominations in that group
- */
- struct GNUNET_HashCode hash_xor;
- };
denominations_by_group =
GNUNET_CONTAINER_multihashmap_create (1024,
GNUNET_NO /* NO, because keys are only on the stack */);
-
-
- /* heap = min heap, sorted by start time */
+ /* heap = max heap, sorted by start time */
while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
{
- if (GNUNET_TIME_timestamp_cmp (last_cpd,
+ if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
!=,
dk->meta.start) &&
- (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time)) )
+ (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
{
/*
- * This is not the first entry in the heap (because last_cpd !=
+ * This is not the first entry in the heap (because last_cherry_pick_date !=
* GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
* start time. Therefore, we create a new entry in ksh.
*/
struct GNUNET_HashCode hc;
- GNUNET_CRYPTO_hash_context_finish (
- GNUNET_CRYPTO_hash_context_copy (hash_context),
- &hc);
-
+ compute_msig_hash (&sig_ctx,
+ &hc);
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
- denoms,
- grouped_denominations,
- &grouped_hash_xor))
+ grouped_denominations))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
- GNUNET_CRYPTO_hash_context_abort (hash_context);
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
/* drain heap before destroying it */
while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
/* intentionally empty */;
GNUNET_CONTAINER_heap_destroy (heap);
- json_decref (denoms);
- json_decref (grouped_denominations);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ goto CLEANUP;
}
}
- last_cpd = dk->meta.start;
-
- {
- json_t *denom;
-
- denom =
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("master_sig",
- &dk->master_sig),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- dk->meta.start),
- GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
- dk->meta.expire_withdraw),
- GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
- dk->meta.expire_deposit),
- GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
- dk->meta.expire_legal),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &dk->denom_pub),
- TALER_JSON_pack_amount ("value",
- &dk->meta.value),
- TALER_JSON_PACK_DENOM_FEES ("fee",
- &dk->meta.fees));
-
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &dk->h_denom_pub,
- sizeof (struct GNUNET_HashCode));
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- denoms,
- denom));
-
- }
-
+ last_cherry_pick_date = dk->meta.start;
/*
* Group the denominations by {cipher, value, fees, age_mask}.
*
@@ -2206,69 +2946,70 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
* denominations_by_group.
*/
{
- static const char *denoms_key = "denoms";
- struct groupData *group;
- json_t *list;
+ struct GroupData *group;
json_t *entry;
struct GNUNET_HashCode key;
struct TALER_DenominationGroup meta = {
- .cipher = dk->denom_pub.cipher,
+ .cipher = dk->denom_pub.bsign_pub_key->cipher,
.value = dk->meta.value,
.fees = dk->meta.fees,
.age_mask = dk->meta.age_mask,
};
- memset (&meta.hash, 0, sizeof(meta.hash));
-
/* Search the group/JSON-blob for the key */
- GNUNET_CRYPTO_hash (&meta, sizeof(meta), &key);
-
- group =
- (struct groupData *) GNUNET_CONTAINER_multihashmap_get (
- denominations_by_group,
- &key);
-
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ group = GNUNET_CONTAINER_multihashmap_get (
+ denominations_by_group,
+ &key);
if (NULL == group)
{
/* There is no group for this meta-data yet, so we create a new group */
bool age_restricted = meta.age_mask.bits != 0;
- char *cipher;
-
- group = GNUNET_new (struct groupData);
- memset (group, 0, sizeof(*group));
+ const char *cipher;
+ group = GNUNET_new (struct GroupData);
switch (meta.cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
cipher = age_restricted ? "RSA+age_restricted" : "RSA";
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
cipher = age_restricted ? "CS+age_restricted" : "CS";
break;
default:
GNUNET_assert (false);
}
-
+ /* Create a new array for the denominations in this group */
+ group->list = json_array ();
+ GNUNET_assert (NULL != group->list);
group->json = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher", cipher),
- TALER_JSON_PACK_DENOM_FEES ("fee", &meta.fees),
- TALER_JSON_pack_amount ("value", &meta.value));
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ group->list),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ TALER_JSON_pack_amount ("value",
+ &meta.value));
GNUNET_assert (NULL != group->json);
-
if (age_restricted)
{
- int r = json_object_set (group->json,
- "age_mask",
- json_integer (meta.age_mask.bits));
- GNUNET_assert (0 == r);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (group->json,
+ "age_mask",
+ json_integer (
+ meta.age_mask.bits)));
+ /* Remember that we have found at least _one_ age restricted denomination */
+ has_age_restricted_denomination = true;
}
-
- /* Create a new array for the denominations in this group */
- list = json_array ();
- GNUNET_assert (NULL != list);
+ group->group_off
+ = json_array_size (grouped_denominations);
GNUNET_assert (0 ==
- json_object_set (group->json, denoms_key, list));
-
+ json_array_append_new (
+ grouped_denominations,
+ group->json));
GNUNET_assert (
GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
@@ -2280,23 +3021,32 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
/* Now that we have found/created the right group, add the
denomination to the list */
{
+ struct HelperDenomination *hd;
struct GNUNET_JSON_PackSpec key_spec;
-
+ bool private_key_lost;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+ &dk->h_denom_pub.hash);
+ private_key_lost
+ = (NULL == hd) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (
+ hd->start_time.abs_time,
+ hd->validity_duration));
switch (meta.cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
key_spec =
- GNUNET_JSON_pack_rsa_public_key ("rsa_pub",
- dk->denom_pub.details.
- rsa_public_key);
+ GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key);
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
key_spec =
- GNUNET_JSON_pack_data_varsize ("cs_pub",
- &dk->denom_pub.details.
- cs_public_key,
- sizeof (dk->denom_pub.details.
- cs_public_key));
+ GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->denom_pub.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
break;
default:
GNUNET_assert (false);
@@ -2305,6 +3055,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
entry = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("master_sig",
&dk->master_sig),
+ GNUNET_JSON_pack_allow_null (
+ private_key_lost
+ ? GNUNET_JSON_pack_bool ("lost",
+ true)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL)),
GNUNET_JSON_pack_timestamp ("stamp_start",
dk->meta.start),
GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
@@ -2318,104 +3074,80 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
GNUNET_assert (NULL != entry);
}
- /* Build up the running xor of all hashes of the denominations in this
- group */
- GNUNET_CRYPTO_hash_xor (&dk->h_denom_pub.hash,
- &group->hash_xor,
- &group->hash_xor);
-
+ /* Build up the running hash of all master signatures of the
+ denominations */
+ append_signature (&sig_ctx,
+ group->group_off,
+ (unsigned int) json_array_size (group->list),
+ &dk->master_sig);
/* Finally, add the denomination to the list of denominations in this
group */
- list = json_object_get (group->json, denoms_key);
- GNUNET_assert (NULL != list);
- GNUNET_assert (true == json_is_array (list));
+ GNUNET_assert (json_is_array (group->list));
GNUNET_assert (0 ==
- json_array_append_new (list, entry));
+ json_array_append_new (group->list,
+ entry));
}
} /* loop over heap ends */
- /* Create the JSON-array of grouped denominations */
- if (0 <
- GNUNET_CONTAINER_multihashmap_size (denominations_by_group))
- {
- struct GNUNET_CONTAINER_MultiHashMapIterator *iter;
- struct groupData *group = NULL;
-
- iter =
- GNUNET_CONTAINER_multihashmap_iterator_create (denominations_by_group);
-
- while (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_iterator_next (iter,
- NULL,
- (const
- void **) &group))
- {
- /* Add the XOR over all hashes of denominations in this group to the group */
- GNUNET_assert (0 ==
- json_object_set (
- group->json,
- "hash",
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto (NULL,
- &group->hash_xor))));
-
- /* Add this group to the array */
- GNUNET_assert (0 ==
- json_array_append_new (
- grouped_denominations,
- group->json));
-
- /* Build the running XOR over all hash(_xor) */
- GNUNET_CRYPTO_hash_xor (&group->hash_xor,
- &grouped_hash_xor,
- &grouped_hash_xor);
-
- GNUNET_free (group);
- }
-
- GNUNET_CONTAINER_multihashmap_iterator_destroy (iter);
- GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
-
- }
+ GNUNET_CONTAINER_multihashmap_iterate (denominations_by_group,
+ &free_group,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
}
-
GNUNET_CONTAINER_heap_destroy (heap);
- if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))
+
+ if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
{
struct GNUNET_HashCode hc;
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &hc);
+ compute_msig_hash (&sig_ctx,
+ &hc);
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
- denoms,
- grouped_denominations,
- &grouped_hash_xor))
+ grouped_denominations))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
- json_decref (denoms);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ goto CLEANUP;
}
ksh->management_only = false;
+
+ /* Sanity check: Make sure that age restriction is enabled IFF at least
+ * one age restricted denomination exist */
+ if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is enabled, but NO denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
+ else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is NOT enabled, but denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No denomination keys available. Refusing to generate /keys response.\n");
- GNUNET_CRYPTO_hash_context_abort (hash_context);
}
- json_decref (sctx.signkeys);
+ ret = GNUNET_OK;
+
+CLEANUP:
+ GNUNET_array_grow (sig_ctx.elements,
+ sig_ctx.elements_size,
+ 0);
+ json_decref (grouped_denominations);
+ if (NULL != sctx.signkeys)
+ json_decref (sctx.signkeys);
json_decref (recoup);
- json_decref (denoms);
- return GNUNET_OK;
+ return ret;
}
@@ -2446,6 +3178,21 @@ global_fee_info_cb (
struct TEH_KeyStateHandle *ksh = cls;
struct TEH_GlobalFee *gf;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_global_fee_verify (
+ start_date,
+ end_date,
+ fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has global fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found global fees with %u purses\n",
purse_account_limit);
@@ -2518,9 +3265,9 @@ build_key_state (struct HelperState *hs,
ksh->helpers = hs;
}
ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
+ true);
ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
- GNUNET_NO /* MUST be NO! */);
+ false /* MUST be false! */);
ksh->auditors = json_array ();
GNUNET_assert (NULL != ksh->auditors);
/* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
@@ -2596,7 +3343,7 @@ build_key_state (struct HelperState *hs,
finish_keys_response (ksh))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Could not finish /keys response (likely no signing keys available yet)\n");
+ "Could not finish /keys response (required data not configured yet)\n");
destroy_key_state (ksh,
true);
return NULL;
@@ -2642,7 +3389,7 @@ keys_get_state (bool management_only)
if ( (old_ksh->key_generation < key_generation) ||
(GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Rebuilding /keys, generation upgrade from %llu to %llu\n",
(unsigned long long) old_ksh->key_generation,
(unsigned long long) key_generation);
@@ -2725,16 +3472,16 @@ TEH_keys_denomination_by_hash (
return NULL;
}
- return TEH_keys_denomination_by_hash2 (ksh,
- h_denom_pub,
- conn,
- mret);
+ return TEH_keys_denomination_by_hash_from_state (ksh,
+ h_denom_pub,
+ conn,
+ mret);
}
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (
- struct TEH_KeyStateHandle *ksh,
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn,
MHD_RESULT *mret)
@@ -2756,66 +3503,11 @@ TEH_keys_denomination_by_hash2 (
enum TALER_ErrorCode
-TEH_keys_denomination_sign (
- const struct TEH_CoinSignData *csd,
- bool for_melt,
- struct TALER_BlindedDenominationSignature *bs)
-{
- struct TEH_KeyStateHandle *ksh;
- struct HelperDenomination *hd;
- const struct TALER_DenominationHashP *h_denom_pub = csd->h_denom_pub;
- const struct TALER_BlindedPlanchet *bp = csd->bp;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- if (bp->cipher != hd->denom_pub.cipher)
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- switch (hd->denom_pub.cipher)
- {
- case TALER_DENOMINATION_RSA:
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA]++;
- {
- struct TALER_CRYPTO_RsaSignRequest rsr = {
- .h_rsa = &hd->h_details.h_rsa,
- .msg = bp->details.rsa_blinded_planchet.blinded_msg,
- .msg_size = bp->details.rsa_blinded_planchet.blinded_msg_size
- };
-
- return TALER_CRYPTO_helper_rsa_sign (
- ksh->helpers->rsadh,
- &rsr,
- bs);
- }
- case TALER_DENOMINATION_CS:
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS]++;
- {
- struct TALER_CRYPTO_CsSignRequest csr;
-
- csr.h_cs = &hd->h_details.h_cs;
- csr.blinded_planchet = &bp->details.cs_blinded_planchet;
- return TALER_CRYPTO_helper_cs_sign (
- ksh->helpers->csdh,
- &csr,
- for_melt,
- bs);
- }
- default:
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
-}
-
-
-enum TALER_ErrorCode
TEH_keys_denomination_batch_sign (
- const struct TEH_CoinSignData *csds,
unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
bool for_melt,
- struct TALER_BlindedDenominationSignature *bss)
+ struct TALER_BlindedDenominationSignature bss[static csds_length])
{
struct TEH_KeyStateHandle *ksh;
struct HelperDenomination *hd;
@@ -2839,21 +3531,23 @@ TEH_keys_denomination_batch_sign (
&h_denom_pub->hash);
if (NULL == hd)
return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- if (bp->cipher != hd->denom_pub.cipher)
+ if (bp->blinded_message->cipher !=
+ hd->denom_pub.bsign_pub_key->cipher)
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- switch (hd->denom_pub.cipher)
+ switch (hd->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
rsrs[rsrs_pos].msg
- = bp->details.rsa_blinded_planchet.blinded_msg;
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg;
rsrs[rsrs_pos].msg_size
- = bp->details.rsa_blinded_planchet.blinded_msg_size;
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size;
rsrs_pos++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
- csrs[csrs_pos].blinded_planchet = &bp->details.cs_blinded_planchet;
+ csrs[csrs_pos].blinded_planchet
+ = &bp->blinded_message->details.cs_blinded_message;
csrs_pos++;
break;
default:
@@ -2876,8 +3570,8 @@ TEH_keys_denomination_batch_sign (
{
ec = TALER_CRYPTO_helper_cs_batch_sign (
ksh->helpers->csdh,
- csrs,
csrs_pos,
+ csrs,
for_melt,
(0 == rsrs_pos) ? bss : cs);
if (TALER_EC_NONE != ec)
@@ -2892,8 +3586,8 @@ TEH_keys_denomination_batch_sign (
{
ec = TALER_CRYPTO_helper_rsa_batch_sign (
ksh->helpers->rsadh,
- rsrs,
rsrs_pos,
+ rsrs,
(0 == csrs_pos) ? bss : rs);
if (TALER_EC_NONE != ec)
{
@@ -2915,12 +3609,12 @@ TEH_keys_denomination_batch_sign (
{
const struct TALER_BlindedPlanchet *bp = csds[i].bp;
- switch (bp->cipher)
+ switch (bp->blinded_message->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
bss[i] = rs[rsrs_pos++];
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
bss[i] = cs[csrs_pos++];
break;
default:
@@ -2936,10 +3630,10 @@ enum TALER_ErrorCode
TEH_keys_denomination_cs_r_pub (
const struct TEH_CsDeriveData *cdd,
bool for_melt,
- struct TALER_DenominationCSPublicRPairP *r_pub)
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
{
const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub;
- const struct TALER_CsNonce *nonce = cdd->nonce;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdd->nonce;
struct TEH_KeyStateHandle *ksh;
struct HelperDenomination *hd;
@@ -2954,7 +3648,8 @@ TEH_keys_denomination_cs_r_pub (
{
return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
}
- if (TALER_DENOMINATION_CS != hd->denom_pub.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
{
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
}
@@ -2974,10 +3669,10 @@ TEH_keys_denomination_cs_r_pub (
enum TALER_ErrorCode
TEH_keys_denomination_cs_batch_r_pub (
- const struct TEH_CsDeriveData *cdds,
unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
bool for_melt,
- struct TALER_DenominationCSPublicRPairP *r_pubs)
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length])
{
struct TEH_KeyStateHandle *ksh;
struct HelperDenomination *hd;
@@ -2991,7 +3686,7 @@ TEH_keys_denomination_cs_batch_r_pub (
for (unsigned int i = 0; i<cdds_length; i++)
{
const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
- const struct TALER_CsNonce *nonce = cdds[i].nonce;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce;
hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
&h_denom_pub->hash);
@@ -2999,7 +3694,8 @@ TEH_keys_denomination_cs_batch_r_pub (
{
return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
}
- if (TALER_DENOMINATION_CS != hd->denom_pub.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
{
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
}
@@ -3008,8 +3704,8 @@ TEH_keys_denomination_cs_batch_r_pub (
}
return TALER_CRYPTO_helper_cs_r_batch_derive (ksh->helpers->csdh,
- cdrs,
cdds_length,
+ cdrs,
for_melt,
r_pubs);
}
@@ -3034,22 +3730,23 @@ TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub)
GNUNET_break (0);
return;
}
- switch (hd->denom_pub.cipher)
+ switch (hd->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->rsadh,
&hd->h_details.h_rsa);
TEH_keys_update_states ();
return;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TALER_CRYPTO_helper_cs_revoke (ksh->helpers->csdh,
&hd->h_details.h_cs);
TEH_keys_update_states ();
return;
- default:
- GNUNET_break (0);
- return;
}
+ GNUNET_break (0);
+ return;
}
@@ -3214,7 +3911,8 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
const struct KeysResponseData *krd;
ksh = TEH_keys_get_state ();
- if (NULL == ksh)
+ if ( (NULL == ksh) ||
+ (0 == ksh->krd_array_length) )
{
if ( ( (SKR_LIMIT == skr_size) &&
(rc->connection == skr_connection) ) ||
@@ -3258,28 +3956,11 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
if ( (NULL != etag) &&
(0 == strcmp (etag,
krd->etag)) )
- {
- MHD_RESULT ret;
- struct MHD_Response *resp;
-
- resp = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- TALER_MHD_add_global_headers (resp);
- GNUNET_break (GNUNET_OK ==
- setup_general_response_headers (ksh,
- resp));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- krd->etag));
- ret = MHD_queue_response (rc->connection,
- MHD_HTTP_NOT_MODIFIED,
- resp);
- GNUNET_break (MHD_YES == ret);
- MHD_destroy_response (resp);
- return ret;
- }
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ krd->etag,
+ &setup_general_response_headers,
+ ksh);
+
return MHD_queue_response (rc->connection,
MHD_HTTP_OK,
(MHD_YES ==
@@ -3395,9 +4076,10 @@ TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
meta);
if (GNUNET_OK == ok)
{
- GNUNET_assert (TALER_DENOMINATION_INVALID != hd->denom_pub.cipher);
- TALER_denom_pub_deep_copy (denom_pub,
- &hd->denom_pub);
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ hd->denom_pub.bsign_pub_key->cipher);
+ TALER_denom_pub_copy (denom_pub,
+ &hd->denom_pub);
}
else
{
@@ -3430,6 +4112,11 @@ TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
pid.public_key = exchange_pub->eddsa_pub;
hsk = GNUNET_CONTAINER_multipeermap_get (ksh->helpers->esign_keys,
&pid);
+ if (NULL == hsk)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
meta->start = hsk->start_time;
meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
@@ -3610,6 +4297,7 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) &&
(GNUNET_is_zero (&denom_cs_sm_pub)) )
{
+ /* Either IPC failed, or neither helper had any denominations configured. */
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_GATEWAY,
TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
@@ -3622,7 +4310,6 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
NULL);
}
- // then a secmod helper is not yet running and we should return an MHD_HTTP_BAD_GATEWAY!
GNUNET_assert (NULL != fbc.denoms);
GNUNET_assert (NULL != fbc.signkeys);
GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys,
diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h
index 8758afb71..e526385ff 100644
--- a/src/exchange/taler-exchange-httpd_keys.h
+++ b/src/exchange/taler-exchange-httpd_keys.h
@@ -154,6 +154,48 @@ struct TEH_KeyStateHandle;
void
TEH_check_invariants (void);
+/**
+ * Clean up wire subsystem.
+ */
+void
+TEH_wire_done (void);
+
+
+/**
+ * Look up wire fee structure by @a ts.
+ *
+ * @param ts timestamp to lookup wire fees at
+ * @param method wire method to lookup fees for
+ * @return the wire fee details, or
+ * NULL if none are configured for @a ts and @a method
+ */
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+ struct GNUNET_TIME_Timestamp ts,
+ const char *method);
+
+
+/**
+ * Initialize wire subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_wire_init (void);
+
+
+/**
+ * Something changed in the database. Rebuild the wire replies. This function
+ * should be called if the exchange learns about a new signature from our
+ * master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their wire state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_wire_update_state (void);
+
/**
* Return the current key state for this thread. Possibly re-builds the key
@@ -234,8 +276,8 @@ TEH_keys_denomination_by_hash (
* or NULL if @a h_denom_pub could not be found
*/
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (
- struct TEH_KeyStateHandle *ksh,
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn,
MHD_RESULT *mret);
@@ -258,22 +300,7 @@ struct TEH_CoinSignData
/**
- * Request to sign @a csd for melting.
- *
- * @param csd identifies data to blindly sign and key to sign with
- * @param for_melt true if this is for a melt operation
- * @param[out] bs set to the blind signature on success
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_denomination_sign (
- const struct TEH_CoinSignData *csd,
- bool for_melt,
- struct TALER_BlindedDenominationSignature *bs);
-
-
-/**
- * Request to sign @a csds for melting.
+ * Request to sign @a csds.
*
* @param csds array with data to blindly sign (and keys to sign with)
* @param csds_length length of @a csds array
@@ -283,10 +310,10 @@ TEH_keys_denomination_sign (
*/
enum TALER_ErrorCode
TEH_keys_denomination_batch_sign (
- const struct TEH_CoinSignData *csds,
unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
bool for_melt,
- struct TALER_BlindedDenominationSignature *bss);
+ struct TALER_BlindedDenominationSignature bss[static csds_length]);
/**
@@ -302,7 +329,7 @@ struct TEH_CsDeriveData
/**
* Nonce to use.
*/
- const struct TALER_CsNonce *nonce;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
};
@@ -318,7 +345,7 @@ enum TALER_ErrorCode
TEH_keys_denomination_cs_r_pub (
const struct TEH_CsDeriveData *cdd,
bool for_melt,
- struct TALER_DenominationCSPublicRPairP *r_pub);
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub);
/**
@@ -333,10 +360,10 @@ TEH_keys_denomination_cs_r_pub (
*/
enum TALER_ErrorCode
TEH_keys_denomination_cs_batch_r_pub (
- const struct TEH_CsDeriveData *cdds,
unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
bool for_melt,
- struct TALER_DenominationCSPublicRPairP *r_pubs);
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]);
/**
@@ -363,7 +390,7 @@ TEH_keys_finished (void);
/**
- * Resumse all suspended /keys requests, we may now have key material
+ * Resumes all suspended /keys requests, we may now have key material
* (or are shutting down).
*
* @param do_shutdown are we shutting down?
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
index 47338ae96..362c20a2e 100644
--- a/src/exchange/taler-exchange-httpd_kyc-check.c
+++ b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021-2022 Taler Systems SA
+ Copyright (C) 2021-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
@@ -113,6 +113,11 @@ struct KycPoller
const char *section_name;
/**
+ * Set to AML status of the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ /**
* Set to error encountered with KYC logic, if any.
*/
enum TALER_ErrorCode ec;
@@ -233,7 +238,8 @@ initiate_cb (
kyp->ih = NULL;
kyp->ih_done = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC initiation completed with status %d (%s)\n",
+ "KYC initiation `%s' completed with ec=%d (%s)\n",
+ provider_legitimization_id,
ec,
(TALER_EC_NONE == ec)
? redirect_url
@@ -254,8 +260,9 @@ initiate_cb (
&kyp->h_payto,
provider_user_id,
provider_legitimization_id,
+ redirect_url,
GNUNET_TIME_UNIT_ZERO_ABS);
- if (qs < 0)
+ if (qs <= 0)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"KYC requirement update failed for %s with status %d at %s:%u\n",
TALER_B2S (&kyp->h_payto),
@@ -297,11 +304,14 @@ kyc_check (void *cls,
enum GNUNET_GenericReturnValue ret;
struct TALER_PaytoHashP h_payto;
char *requirements;
+ char *redirect_url;
+ bool satisfied;
qs = TEH_plugin->lookup_kyc_requirement_by_row (
TEH_plugin->cls,
kyp->requirement_row,
&requirements,
+ &kyp->aml_status,
&h_payto);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
@@ -330,12 +340,26 @@ kyc_check (void *cls,
GNUNET_free (requirements);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if (TALER_KYCLOGIC_check_satisfied (
- requirements,
- &h_payto,
- &kyp->kyc_details,
- TEH_plugin->select_satisfied_kyc_processes,
- TEH_plugin->cls))
+ qs = TALER_KYCLOGIC_check_satisfied (
+ &requirements,
+ &h_payto,
+ &kyp->kyc_details,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &satisfied);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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");
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (satisfied)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirements `%s' already satisfied\n",
@@ -366,14 +390,50 @@ kyc_check (void *cls,
if (kyp->ih_done)
return qs;
-
- qs = TEH_plugin->insert_kyc_requirement_process (
+ qs = TEH_plugin->get_pending_kyc_requirement_process (
TEH_plugin->cls,
&h_payto,
kyp->section_name,
- NULL,
- NULL,
- &kyp->process_row);
+ &redirect_url);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (qs > 0) &&
+ (NULL != redirect_url) )
+ {
+ kyp->kyc_url = redirect_url;
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* set up new requirement process */
+ qs = TEH_plugin->insert_kyc_requirement_process (
+ TEH_plugin->cls,
+ &h_payto,
+ kyp->section_name,
+ NULL,
+ NULL,
+ &kyp->process_row);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Initiating KYC check with logic %s\n",
kyp->ih_logic->name);
@@ -488,34 +548,19 @@ TEH_handler_kyc_check (
"usertype");
}
- {
- const char *ts;
-
- ts = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != ts)
- {
- char dummy;
- unsigned long long tms;
-
- if (1 !=
- sscanf (ts,
- "%llu%c",
- &tms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms");
- }
- kyp->timeout = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- tms));
- }
- }
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &kyp->timeout);
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_string ("kyc_url",
+ kyp->kyc_url));
}
if ( (NULL == kyp->eh) &&
@@ -550,10 +595,32 @@ TEH_handler_kyc_check (
"Transaction failed.\n");
return res;
}
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_string ("kyc_url",
+ kyp->kyc_url));
+ }
if ( (NULL == kyp->ih) &&
(! kyp->kyc_required) )
{
+ if (TALER_AML_NORMAL != kyp->aml_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC is OK, but AML active: %d\n",
+ (int) kyp->aml_status);
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status));
+ }
/* KYC not required */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC not required %llu\n",
@@ -584,11 +651,12 @@ TEH_handler_kyc_check (
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending HTTP request on timeout (%s) now...\n",
- GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_duration (
+ GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
kyp->timeout),
true));
GNUNET_assert (NULL != kyp->eh);
kyp->suspended = true;
+ kyp->section_name = NULL;
GNUNET_CONTAINER_DLL_insert (kyp_head,
kyp_tail,
kyp);
@@ -596,16 +664,6 @@ TEH_handler_kyc_check (
return MHD_YES;
}
- /* KYC plugin generated reply? */
- if (NULL != kyp->kyc_url)
- {
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_string ("kyc_url",
- kyp->kyc_url));
- }
-
if (TALER_EC_NONE != kyp->ec)
{
return TALER_MHD_reply_with_ec (rc->connection,
@@ -639,6 +697,8 @@ TEH_handler_kyc_check (
&sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub),
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
GNUNET_JSON_pack_object_incref ("kyc_details",
kyp->kyc_details),
GNUNET_JSON_pack_timestamp ("now",
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c
index 5d89c4203..bad377a2a 100644
--- a/src/exchange/taler-exchange-httpd_kyc-proof.c
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021-2022 Taler Systems SA
+ Copyright (C) 2021-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
@@ -23,10 +23,12 @@
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
-#include <pthread.h>
+#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_responses.h"
@@ -68,6 +70,11 @@ struct KycProofContext
struct TALER_KYCLOGIC_ProofHandle *ph;
/**
+ * KYC AML trigger operation.
+ */
+ struct TEH_KycAmlTrigger *kat;
+
+ /**
* Process information about the user for the plugin from the database, can
* be NULL.
*/
@@ -159,6 +166,101 @@ TEH_kyc_proof_cleanup (void)
/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+static void
+proof_finish (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycProofContext *kpc = cls;
+
+ kpc->kat = NULL;
+ kpc->response_code = http_status;
+ kpc->response = response;
+ kpc_resume (kpc);
+}
+
+
+/**
+ * Generate HTML error for @a connection using @a template.
+ *
+ * @param connection HTTP client connection
+ * @param template template to expand
+ * @param[in,out] http_status HTTP status of the response
+ * @param ec Taler error code to return
+ * @param message extended message to return
+ * @return MHD response object
+ */
+struct MHD_Response *
+make_html_error (struct MHD_Connection *connection,
+ const char *template,
+ unsigned int *http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response = NULL;
+ json_t *body;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("message",
+ message)),
+ TALER_JSON_pack_ec (
+ ec));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (connection,
+ http_status,
+ template,
+ NULL,
+ NULL,
+ body,
+ &response));
+ json_decref (body);
+ return response;
+}
+
+
+/**
+ * Respond with an HTML message on the given @a rc.
+ *
+ * @param[in,out] rc request to respond to
+ * @param http_status HTTP status code to use
+ * @param template template to fill in
+ * @param ec error code to use for the template
+ * @param message additional message to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+respond_html_ec (struct TEH_RequestContext *rc,
+ unsigned int http_status,
+ const char *template,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response;
+ MHD_RESULT res;
+
+ response = make_html_error (rc->connection,
+ template,
+ &http_status,
+ ec,
+ message);
+ res = MHD_queue_response (rc->connection,
+ http_status,
+ response);
+ MHD_destroy_response (response);
+ return res;
+}
+
+
+/**
* Function called with the result of a proof check operation.
*
* Note that the "decref" for the @a response
@@ -169,6 +271,7 @@ TEH_kyc_proof_cleanup (void)
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param expiration until when is the KYC check valid
+ * @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
*/
@@ -179,6 +282,7 @@ proof_cb (
const char *provider_user_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response)
{
@@ -189,40 +293,104 @@ proof_cb (
kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
-
- if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
+ switch (status)
{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
- kpc->process_row,
- kpc->provider_section,
- &kpc->h_payto,
- provider_user_id,
- provider_legitimization_id,
- expiration);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process #%llu succeeded with KYC provider\n",
+ (unsigned long long) kpc->process_row);
+ kpc->kat = TEH_kyc_finished (&rc->async_scope_id,
+ kpc->process_row,
+ &kpc->h_payto,
+ kpc->provider_section,
+ provider_user_id,
+ provider_legitimization_id,
+ expiration,
+ attributes,
+ http_status,
+ response,
+ &proof_finish,
+ kpc);
+ if (NULL == kpc->kat)
{
- GNUNET_break (0);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ if (NULL != response)
+ MHD_destroy_response (response);
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ "[exchange] AML_KYC_TRIGGER");
+ }
+ break;
+ case TALER_KYCLOGIC_STATUS_FAILED:
+ case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
+ case TALER_KYCLOGIC_STATUS_USER_ABORTED:
+ case TALER_KYCLOGIC_STATUS_ABORTED:
+ GNUNET_assert (NULL == kpc->kat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process %s/%s (Row #%llu) failed: %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ status);
+ if (5 == http_status / 100)
+ {
+ char *msg;
+
+ /* OAuth2 server had a problem, do NOT log this as a KYC failure */
if (NULL != response)
MHD_destroy_response (response);
- kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
- "set_kyc_ok");
- GNUNET_async_scope_restore (&old_scope);
- return;
+ GNUNET_asprintf (&msg,
+ "Failure by KYC provider (HTTP status %u)\n",
+ http_status);
+ http_status = MHD_HTTP_BAD_GATEWAY;
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ msg);
+ GNUNET_free (msg);
}
+ else
+ {
+ if (! TEH_kyc_failed (kpc->process_row,
+ &kpc->h_payto,
+ kpc->provider_section,
+ provider_user_id,
+ provider_legitimization_id))
+ {
+ GNUNET_break (0);
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = make_html_error (kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &http_status,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_failure");
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (Row #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ (int) status);
+ break;
}
- else
+ if (NULL == kpc->kat)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC process #%llu failed with status %d\n",
(unsigned long long) kpc->process_row,
status);
+ proof_finish (kpc,
+ http_status,
+ response);
}
- kpc->response_code = http_status;
- kpc->response = response;
- kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope);
}
@@ -242,6 +410,11 @@ clean_kpc (struct TEH_RequestContext *rc)
kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL;
}
+ if (NULL != kpc->kat)
+ {
+ TEH_kyc_finished_cancel (kpc->kat);
+ kpc->kat = NULL;
+ }
if (NULL != kpc->response)
{
MHD_destroy_response (kpc->response);
@@ -256,64 +429,57 @@ clean_kpc (struct TEH_RequestContext *rc)
MHD_RESULT
TEH_handler_kyc_proof (
struct TEH_RequestContext *rc,
- const char *const args[3])
+ const char *const args[1])
{
struct KycProofContext *kpc = rc->rh_ctx;
+ const char *provider_section_or_logic = args[0];
if (NULL == kpc)
{
/* first time */
- if ( (NULL == args[0]) ||
- (NULL == args[1]) )
+ if (NULL == provider_section_or_logic)
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- "'/kyc-proof/$H_PATYO/$LOGIC' required");
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-endpoint-unknown",
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required");
}
-
kpc = GNUNET_new (struct KycProofContext);
kpc->rc = rc;
rc->rh_ctx = kpc;
rc->rh_cleaner = &clean_kpc;
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "state",
+ &kpc->h_payto);
if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &kpc->h_payto,
- sizeof (kpc->h_payto)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "h_payto");
- }
- if (GNUNET_OK !=
- TALER_KYCLOGIC_lookup_logic (args[1],
+ TALER_KYCLOGIC_lookup_logic (provider_section_or_logic,
&kpc->logic,
&kpc->pd,
&kpc->provider_section))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
- args[1]);
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ provider_section_or_logic);
}
if (NULL != kpc->provider_section)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Absolute expiration;
- if (0 != strcmp (args[1],
+ if (0 != strcmp (provider_section_or_logic,
kpc->provider_section))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "PROVIDER_SECTION");
+ return respond_html_ec (rc,
+ MHD_HTTP_BAD_REQUEST,
+ "kyc-proof-bad-request",
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "PROVIDER_SECTION");
}
qs = TEH_plugin->lookup_kyc_process_by_account (
@@ -328,31 +494,32 @@ TEH_handler_kyc_proof (
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_MHD_reply_with_ec (rc->connection,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "lookup_kyc_requirement_by_account");
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_kyc_process_by_account");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
- kpc->provider_section);
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ kpc->provider_section);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
if (GNUNET_TIME_absolute_is_future (expiration))
{
/* KYC not required */
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ return respond_html_ec (rc,
+ MHD_HTTP_OK,
+ "kyc-proof-already-done",
+ TALER_EC_NONE,
+ NULL);
}
}
kpc->ph = kpc->logic->proof (kpc->logic->cls,
kpc->pd,
- &args[2],
rc->connection,
&kpc->h_payto,
kpc->process_row,
@@ -363,10 +530,11 @@ TEH_handler_kyc_proof (
if (NULL == kpc->ph)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "could not start proof with KYC logic");
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "could not start proof with KYC logic");
}
@@ -381,10 +549,11 @@ TEH_handler_kyc_proof (
if (NULL == kpc->response)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "handler resumed without response");
+ return respond_html_ec (rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "handler resumed without response");
}
/* return response from KYC logic */
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.h b/src/exchange/taler-exchange-httpd_kyc-proof.h
index 98551557b..d40ea90a9 100644
--- a/src/exchange/taler-exchange-httpd_kyc-proof.h
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.h
@@ -43,7 +43,7 @@ TEH_kyc_proof_cleanup (void);
MHD_RESULT
TEH_handler_kyc_proof (
struct TEH_RequestContext *rc,
- const char *const args[3]);
+ const char *const args[1]);
#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.c b/src/exchange/taler-exchange-httpd_kyc-wallet.c
index 81acde4c3..21d07422d 100644
--- a/src/exchange/taler-exchange-httpd_kyc-wallet.c
+++ b/src/exchange/taler-exchange-httpd_kyc-wallet.c
@@ -42,6 +42,11 @@ struct KycRequestContext
struct TALER_PaytoHashP h_payto;
/**
+ * The reserve's public key
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
* KYC status, with row with the legitimization requirement.
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
@@ -54,7 +59,7 @@ struct KycRequestContext
/**
* Name of the required check.
*/
- const char *required;
+ char *required;
};
@@ -109,26 +114,39 @@ wallet_kyc_check (void *cls,
struct KycRequestContext *krc = cls;
enum GNUNET_DB_QueryStatus qs;
- krc->required = TALER_KYCLOGIC_kyc_test_required (
+ qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
&krc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&balance_iterator,
- krc);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC check required at %s is `%s'\n",
- TALER_amount2s (&krc->balance),
- krc->required);
+ krc,
+ &krc->required);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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 == krc->required)
{
krc->kyc.ok = true;
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check required at %s is `%s'\n",
+ TALER_amount2s (&krc->balance),
+ krc->required);
krc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (TEH_plugin->cls,
krc->required,
&krc->h_payto,
+ &krc->reserve_pub,
&krc->kyc.requirement_row);
if (qs < 0)
{
@@ -158,14 +176,11 @@ TEH_handler_kyc_wallet (
{
struct TALER_ReserveSignatureP reserve_sig;
struct KycRequestContext krc;
- struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&reserve_sig),
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &reserve_pub),
- // FIXME: add balance threshold crossed to the request
- // to spec and client API!
+ &krc.reserve_pub),
TALER_JSON_spec_amount ("balance",
TEH_currency,
&krc.balance),
@@ -184,10 +199,9 @@ TEH_handler_kyc_wallet (
return MHD_YES; /* failure */
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- // FIXME: add balance threshold crossed to
- // what the wallet signs over!
if (GNUNET_OK !=
- TALER_wallet_account_setup_verify (&reserve_pub,
+ TALER_wallet_account_setup_verify (&krc.reserve_pub,
+ &krc.balance,
&reserve_sig))
{
GNUNET_break_op (0);
@@ -201,7 +215,7 @@ TEH_handler_kyc_wallet (
char *payto_uri;
payto_uri = TALER_reserve_make_payto (TEH_base_url,
- &reserve_pub);
+ &krc.reserve_pub);
TALER_payto_hash (payto_uri,
&krc.h_payto);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -228,6 +242,7 @@ TEH_handler_kyc_wallet (
NULL,
0);
}
+ GNUNET_free (krc.required);
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&krc.h_payto,
&krc.kyc);
diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c
index 73038c139..b92b43e69 100644
--- a/src/exchange/taler-exchange-httpd_kyc-webhook.c
+++ b/src/exchange/taler-exchange-httpd_kyc-webhook.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-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
@@ -24,9 +24,11 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
+#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_responses.h"
@@ -53,6 +55,11 @@ struct KycWebhookContext
struct TEH_RequestContext *rc;
/**
+ * Handle for the KYC-AML trigger interaction.
+ */
+ struct TEH_KycAmlTrigger *kat;
+
+ /**
* Plugin responsible for the webhook.
*/
struct TALER_KYCLOGIC_Plugin *plugin;
@@ -140,8 +147,29 @@ TEH_kyc_webhook_cleanup (void)
/**
- * Function called with the result of a webhook
- * operation.
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure with a `struct KycWebhookContext *`
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+static void
+kyc_aml_webhook_finished (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycWebhookContext *kwh = cls;
+
+ kwh->kat = NULL;
+ kwh->response = response;
+ kwh->response_code = http_status;
+ kwh_resume (kwh);
+}
+
+
+/**
+ * Function called with the result of a KYC webhook operation.
*
* Note that the "decref" for the @a response
* will be done by the plugin.
@@ -154,6 +182,7 @@ TEH_kyc_webhook_cleanup (void)
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param status KYC status
* @param expiration until when is the KYC check valid
+ * @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
*/
@@ -167,6 +196,7 @@ webhook_finished_cb (
const char *provider_legitimization_id,
enum TALER_KYCLOGIC_KycStatus status,
struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response)
{
@@ -176,26 +206,53 @@ webhook_finished_cb (
switch (status)
{
case TALER_KYCLOGIC_STATUS_SUCCESS:
- /* _successfully_ resumed case */
+ kwh->kat = TEH_kyc_finished (
+ &kwh->rc->async_scope_id,
+ process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id,
+ expiration,
+ attributes,
+ http_status,
+ response,
+ &kyc_aml_webhook_finished,
+ kwh);
+ if (NULL == kwh->kat)
+ {
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ "[exchange] AML_KYC_TRIGGER");
+ break;
+ }
+ return;
+ case TALER_KYCLOGIC_STATUS_FAILED:
+ case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
+ case TALER_KYCLOGIC_STATUS_USER_ABORTED:
+ case TALER_KYCLOGIC_STATUS_ABORTED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process %s/%s (Row #%llu) failed: %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) process_row,
+ status);
+ if (! TEH_kyc_failed (process_row,
+ account_id,
+ provider_section,
+ provider_user_id,
+ provider_legitimization_id))
{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
- process_row,
- provider_section,
- account_id,
- provider_user_id,
- provider_legitimization_id,
- expiration);
- if (qs < 0)
- {
- GNUNET_break (0);
- kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
- "set_kyc_ok");
- kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- kwh_resume (kwh);
- return;
- }
+ GNUNET_break (0);
+ if (NULL != response)
+ MHD_destroy_response (response);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ response = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_failure");
}
break;
default:
@@ -204,12 +261,13 @@ webhook_finished_cb (
provider_user_id,
provider_legitimization_id,
(unsigned long long) process_row,
- status);
+ (int) status);
break;
}
- kwh->response = response;
- kwh->response_code = http_status;
- kwh_resume (kwh);
+ GNUNET_break (NULL == kwh->kat);
+ kyc_aml_webhook_finished (kwh,
+ http_status,
+ response);
}
@@ -228,6 +286,11 @@ clean_kwh (struct TEH_RequestContext *rc)
kwh->plugin->webhook_cancel (kwh->wh);
kwh->wh = NULL;
}
+ if (NULL != kwh->kat)
+ {
+ TEH_kyc_finished_cancel (kwh->kat);
+ kwh->kat = NULL;
+ }
if (NULL != kwh->response)
{
MHD_destroy_response (kwh->response);
@@ -262,11 +325,12 @@ handler_kyc_webhook_generic (
rc->rh_ctx = kwh;
rc->rh_cleaner = &clean_kwh;
- if (GNUNET_OK !=
- TALER_KYCLOGIC_lookup_logic (args[0],
- &kwh->plugin,
- &kwh->pd,
- &kwh->provider_section))
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (args[0],
+ &kwh->plugin,
+ &kwh->pd,
+ &kwh->provider_section)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"KYC logic `%s' unknown (check KYC provider configuration)\n",
@@ -274,8 +338,12 @@ handler_kyc_webhook_generic (
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
- "$NAME");
+ args[0]);
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC logic `%s' mapped to section %s\n",
+ args[0],
+ kwh->provider_section);
kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
kwh->pd,
TEH_plugin->kyc_provider_account_lookup,
@@ -301,13 +369,17 @@ handler_kyc_webhook_generic (
MHD_suspend_connection (rc->connection);
return MHD_YES;
}
+ GNUNET_break (GNUNET_NO == kwh->suspended);
if (NULL != kwh->response)
{
- /* handle _failed_ resumed cases */
- return MHD_queue_response (rc->connection,
- kwh->response_code,
- kwh->response);
+ MHD_RESULT res;
+
+ res = MHD_queue_response (rc->connection,
+ kwh->response_code,
+ kwh->response);
+ GNUNET_break (MHD_YES == res);
+ return res;
}
/* We resumed, but got no response? This should
diff --git a/src/exchange/taler-exchange-httpd_link.c b/src/exchange/taler-exchange-httpd_link.c
index 9b7e297bc..3d92a11a3 100644
--- a/src/exchange/taler-exchange-httpd_link.c
+++ b/src/exchange/taler-exchange-httpd_link.c
@@ -39,7 +39,7 @@ struct HTD_Context
/**
* Public key of the coin for which we are running link.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
/**
* Json array with transfer data we collect.
@@ -153,7 +153,7 @@ link_transaction (void *cls,
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_link_data (TEH_plugin->cls,
- &ctx->coin_pub,
+ ctx->coin_pub,
&handle_link_data,
ctx);
if (NULL == ctx->mlist)
@@ -178,26 +178,13 @@ link_transaction (void *cls,
MHD_RESULT
TEH_handler_link (struct TEH_RequestContext *rc,
- const char *const args[2])
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
- struct HTD_Context ctx;
+ struct HTD_Context ctx = {
+ .coin_pub = coin_pub
+ };
MHD_RESULT mhd_ret;
- memset (&ctx,
- 0,
- sizeof (ctx));
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &ctx.coin_pub,
- sizeof (ctx.coin_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
- args[0]);
- }
ctx.mlist = json_array ();
GNUNET_assert (NULL != ctx.mlist);
if (GNUNET_OK !=
diff --git a/src/exchange/taler-exchange-httpd_link.h b/src/exchange/taler-exchange-httpd_link.h
index 01679e877..255c0ca57 100644
--- a/src/exchange/taler-exchange-httpd_link.h
+++ b/src/exchange/taler-exchange-httpd_link.h
@@ -32,12 +32,12 @@
* Handle a "/coins/$COIN_PUB/link" request.
*
* @param rc request context
- * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
+ * @param coin_pub the coin public key
* @return MHD result code
*/
MHD_RESULT
TEH_handler_link (struct TEH_RequestContext *rc,
- const char *const args[2]);
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_management.h b/src/exchange/taler-exchange-httpd_management.h
index a5a8b0e72..2fc1fe8db 100644
--- a/src/exchange/taler-exchange-httpd_management.h
+++ b/src/exchange/taler-exchange-httpd_management.h
@@ -175,6 +175,32 @@ TEH_handler_management_post_drain (
/**
+ * Handle a POST "/management/aml-officers" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_aml_officers (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
+ * Handle a POST "/management/partners" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_management_partners (
+ struct MHD_Connection *connection,
+ const json_t *root);
+
+
+/**
* Initialize extension configuration handling.
*
* @return #GNUNET_OK on success
diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c
new file mode 100644
index 000000000..abc7c3d84
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_aml-officers.c
@@ -0,0 +1,142 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_management_aml-officers.c
+ * @brief Handle request to update AML officer status
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How often do we try the DB operation at most?
+ */
+#define MAX_RETRIES 10
+
+
+MHD_RESULT
+TEH_handler_management_aml_officers (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ const char *officer_name;
+ struct GNUNET_TIME_Timestamp change_date;
+ bool is_active;
+ bool read_only;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("officer_pub",
+ &officer_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_bool ("is_active",
+ &is_active),
+ GNUNET_JSON_spec_bool ("read_only",
+ &read_only),
+ GNUNET_JSON_spec_string ("officer_name",
+ &officer_name),
+ GNUNET_JSON_spec_timestamp ("change_date",
+ &change_date),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_aml_officer_status_verify (
+ &officer_pub,
+ officer_name,
+ change_date,
+ is_active,
+ read_only,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_UPDATE_AML_OFFICER_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp last_date;
+ unsigned int retries_left = MAX_RETRIES;
+
+ do {
+ qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls,
+ &officer_pub,
+ &master_sig,
+ officer_name,
+ is_active,
+ read_only,
+ change_date,
+ &last_date);
+ if (0 == --retries_left)
+ break;
+ } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_aml_officer");
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ change_date))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AML_OFFICERS_MORE_RECENT_PRESENT,
+ NULL);
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_aml-officers.c */
diff --git a/src/exchange/taler-exchange-httpd_management_auditors.c b/src/exchange/taler-exchange-httpd_management_auditors.c
index 9c7a5c472..7e0593534 100644
--- a/src/exchange/taler-exchange-httpd_management_auditors.c
+++ b/src/exchange/taler-exchange-httpd_management_auditors.c
@@ -153,7 +153,7 @@ TEH_handler_management_auditors (
&aac.master_sig),
GNUNET_JSON_spec_fixed_auto ("auditor_pub",
&aac.auditor_pub),
- GNUNET_JSON_spec_string ("auditor_url",
+ TALER_JSON_spec_web_url ("auditor_url",
&aac.auditor_url),
GNUNET_JSON_spec_string ("auditor_name",
&aac.auditor_name),
diff --git a/src/exchange/taler-exchange-httpd_management_drain.c b/src/exchange/taler-exchange-httpd_management_drain.c
index 565c292f4..1e490d799 100644
--- a/src/exchange/taler-exchange-httpd_management_drain.c
+++ b/src/exchange/taler-exchange-httpd_management_drain.c
@@ -124,8 +124,8 @@ TEH_handler_management_post_drain (
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("debit_account_section",
&dc.account_section),
- GNUNET_JSON_spec_string ("credit_payto_uri",
- &dc.payto_uri),
+ TALER_JSON_spec_payto_uri ("credit_payto_uri",
+ &dc.payto_uri),
GNUNET_JSON_spec_fixed_auto ("wtid",
&dc.wtid),
GNUNET_JSON_spec_fixed_auto ("master_sig",
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c
index 989b88fb6..3b24bace7 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -145,7 +145,7 @@ set_extensions (void *cls,
static enum GNUNET_GenericReturnValue
verify_extensions_from_json (
- json_t *extensions,
+ const json_t *extensions,
struct SetExtensionsContext *sec)
{
const char*name;
@@ -160,7 +160,7 @@ verify_extensions_from_json (
sec->extensions = GNUNET_new_array (sec->num_extensions,
struct Extension);
- json_object_foreach (extensions, name, manifest)
+ json_object_foreach ((json_t *) extensions, name, manifest)
{
int critical = 0;
json_t *config;
@@ -183,7 +183,7 @@ verify_extensions_from_json (
if (critical != extension->critical
|| 0 != strcmp (version, extension->version) // FIXME-oec: libtool compare
|| NULL == config
- || GNUNET_OK != extension->load_config (NULL, config))
+ || GNUNET_OK != extension->load_config (config, NULL))
return GNUNET_SYSERR;
sec->extensions[i].type = extension->type;
@@ -200,11 +200,11 @@ TEH_handler_management_post_extensions (
const json_t *root)
{
MHD_RESULT ret;
- json_t *extensions;
+ const json_t *extensions;
struct SetExtensionsContext sec = {0};
struct GNUNET_JSON_Specification top_spec[] = {
- GNUNET_JSON_spec_json ("extensions",
- &extensions),
+ GNUNET_JSON_spec_object_const ("extensions",
+ &extensions),
GNUNET_JSON_spec_fixed_auto ("extensions_sig",
&sec.extensions_sig),
GNUNET_JSON_spec_end ()
@@ -223,31 +223,19 @@ TEH_handler_management_post_extensions (
return MHD_YES; /* failure */
}
- /* Ensure we have an object */
- if ((! json_is_object (extensions)) &&
- (! json_is_null (extensions)))
- {
- GNUNET_JSON_parse_free (top_spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid object");
- }
-
/* Verify the signature */
{
struct TALER_ExtensionManifestsHashP h_manifests;
if (GNUNET_OK !=
- TALER_JSON_extensions_manifests_hash (extensions, &h_manifests) ||
+ TALER_JSON_extensions_manifests_hash (extensions,
+ &h_manifests) ||
GNUNET_OK !=
TALER_exchange_offline_extension_manifests_hash_verify (
&h_manifests,
&TEH_master_public_key,
&sec.extensions_sig))
{
- GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
@@ -263,7 +251,6 @@ TEH_handler_management_post_extensions (
if (GNUNET_OK !=
verify_extensions_from_json (extensions, &sec))
{
- GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
@@ -306,7 +293,6 @@ CLEANUP:
}
}
GNUNET_free (sec.extensions);
- GNUNET_JSON_parse_free (top_spec);
return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_management_partners.c b/src/exchange/taler-exchange-httpd_management_partners.c
new file mode 100644
index 000000000..fc8a4207d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_management_partners.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_management_partners.c
+ * @brief Handle request to add exchange partner
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+MHD_RESULT
+TEH_handler_management_partners (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct TALER_MasterPublicKeyP partner_pub;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct GNUNET_TIME_Relative wad_frequency;
+ struct TALER_Amount wad_fee;
+ const char *partner_base_url;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("partner_pub",
+ &partner_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ TALER_JSON_spec_web_url ("partner_base_url",
+ &partner_base_url),
+ TALER_JSON_spec_amount ("wad_fee",
+ TEH_currency,
+ &wad_fee),
+ GNUNET_JSON_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_JSON_spec_timestamp ("end_date",
+ &end_date),
+ GNUNET_JSON_spec_relative_time ("wad_frequency",
+ &wad_frequency),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_partner_details_verify (
+ &partner_pub,
+ start_date,
+ end_date,
+ wad_frequency,
+ &wad_fee,
+ partner_base_url,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_partner (TEH_plugin->cls,
+ &partner_pub,
+ start_date,
+ end_date,
+ wad_frequency,
+ &wad_fee,
+ partner_base_url,
+ &master_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add_partner");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* FIXME-#7271: check for idempotency! */
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_DATA_CONFLICT,
+ NULL);
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_partners.c */
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c
index df351ad5f..f91f24c41 100644
--- a/src/exchange/taler-exchange-httpd_management_post_keys.c
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020-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
@@ -47,6 +47,16 @@ struct DenomSig
*/
struct TALER_MasterSignatureP master_sig;
+ /**
+ * Fee structure for this key, as per our configuration.
+ */
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /**
+ * The full public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
};
@@ -65,6 +75,11 @@ struct SigningSig
*/
struct TALER_MasterSignatureP master_sig;
+ /**
+ * Our meta data on this key.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
};
@@ -128,14 +143,9 @@ add_keys (void *cls,
{
struct DenomSig *d = &akc->d_sigs[i];
enum GNUNET_DB_QueryStatus qs;
- bool is_active = false;
struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
- struct TALER_DenominationPublicKey denom_pub;
/* For idempotency, check if the key is already active */
- memset (&denom_pub,
- 0,
- sizeof (denom_pub));
qs = TEH_plugin->lookup_denomination_key (
TEH_plugin->cls,
&d->h_denom_pub,
@@ -151,66 +161,9 @@ add_keys (void *cls,
"lookup denomination key");
return qs;
}
- if (0 == qs)
- {
- enum GNUNET_GenericReturnValue rv;
-
- rv = TEH_keys_load_fees (akc->ksh,
- &d->h_denom_pub,
- &denom_pub,
- &meta);
- switch (rv)
- {
- case GNUNET_SYSERR:
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- GNUNET_h2s (&d->h_denom_pub.hash));
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_NO:
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- GNUNET_h2s (&d->h_denom_pub.hash));
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_OK:
- break;
- }
- }
- else
- {
- is_active = true;
- }
-
- /* check signature is valid */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_denom_validity_verify (
- &d->h_denom_pub,
- meta.start,
- meta.expire_withdraw,
- meta.expire_deposit,
- meta.expire_legal,
- &meta.value,
- &meta.fees,
- &TEH_master_public_key,
- &d->master_sig))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
- GNUNET_h2s (&d->h_denom_pub.hash));
- if (! is_active)
- TALER_denom_pub_free (&denom_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (is_active)
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
+ /* FIXME: assert meta === d->meta might be good */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Denomination key %s already active, skipping\n",
GNUNET_h2s (&d->h_denom_pub.hash));
@@ -220,10 +173,9 @@ add_keys (void *cls,
qs = TEH_plugin->add_denomination_key (
TEH_plugin->cls,
&d->h_denom_pub,
- &denom_pub,
- &meta,
+ &d->denom_pub,
+ &d->meta,
&d->master_sig);
- TALER_denom_pub_free (&denom_pub);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -245,7 +197,6 @@ add_keys (void *cls,
{
struct SigningSig *s = &akc->s_sigs[i];
enum GNUNET_DB_QueryStatus qs;
- bool is_active = false;
struct TALER_EXCHANGEDB_SignkeyMetaData meta;
qs = TEH_plugin->lookup_signing_key (
@@ -263,47 +214,9 @@ add_keys (void *cls,
"lookup signing key");
return qs;
}
- if (0 == qs)
- {
- if (GNUNET_OK !=
- TEH_keys_get_timing (&s->exchange_pub,
- &meta))
- {
- /* For idempotency, check if the key is already active */
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
- TALER_B2S (&s->exchange_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- else
- {
- is_active = true; /* if we pass, it's active! */
- }
-
- /* check signature is valid */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_signkey_validity_verify (
- &s->exchange_pub,
- meta.start,
- meta.expire_sign,
- meta.expire_legal,
- &TEH_master_public_key,
- &s->master_sig))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
- TALER_B2S (&s->exchange_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (is_active)
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
+ /* FIXME: assert meta === d->meta might be good */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Signing key %s already active, skipping\n",
TALER_B2S (&s->exchange_pub));
@@ -312,7 +225,7 @@ add_keys (void *cls,
qs = TEH_plugin->activate_signing_key (
TEH_plugin->cls,
&s->exchange_pub,
- &meta,
+ &s->meta,
&s->master_sig);
if (qs < 0)
{
@@ -334,22 +247,40 @@ add_keys (void *cls,
}
+/**
+ * Clean up state in @a akc, but do not free @a akc itself
+ *
+ * @param[in,out] akc state to clean up
+ */
+static void
+cleanup_akc (struct AddKeysContext *akc)
+{
+ for (unsigned int i = 0; i<akc->nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc->d_sigs[i];
+
+ TALER_denom_pub_free (&d->denom_pub);
+ }
+ GNUNET_free (akc->d_sigs);
+ GNUNET_free (akc->s_sigs);
+}
+
+
MHD_RESULT
TEH_handler_management_post_keys (
struct MHD_Connection *connection,
const json_t *root)
{
- struct AddKeysContext akc;
- json_t *denom_sigs;
- json_t *signkey_sigs;
+ struct AddKeysContext akc = { 0 };
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("denom_sigs",
- &denom_sigs),
- GNUNET_JSON_spec_json ("signkey_sigs",
- &signkey_sigs),
+ GNUNET_JSON_spec_array_const ("denom_sigs",
+ &denom_sigs),
+ GNUNET_JSON_spec_array_const ("signkey_sigs",
+ &signkey_sigs),
GNUNET_JSON_spec_end ()
};
- bool ok;
MHD_RESULT ret;
{
@@ -363,34 +294,23 @@ TEH_handler_management_post_keys (
if (GNUNET_NO == res)
return MHD_YES; /* failure */
}
- if (! (json_is_array (denom_sigs) &&
- json_is_array (signkey_sigs)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "array expected for denom_sigs and signkey_sigs");
- }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /management/keys\n");
+ "Received POST /management/keys request\n");
+
akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
if (NULL == akc.ksh)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
"no key state (not even for management)");
}
+
akc.nd_sigs = json_array_size (denom_sigs);
akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
struct DenomSig);
- ok = true;
for (unsigned int i = 0; i<akc.nd_sigs; i++)
{
struct DenomSig *d = &akc.d_sigs[i];
@@ -407,27 +327,64 @@ TEH_handler_management_post_keys (
json_array_get (denom_sigs,
i),
ispec);
- if (GNUNET_SYSERR == res)
+ if (GNUNET_OK != res)
{
- ret = MHD_NO; /* hard failure */
- ok = false;
- break;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- if (GNUNET_NO == res)
+
+ res = TEH_keys_load_fees (akc.ksh,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta);
+ switch (res)
{
- ret = MHD_YES;
- ok = false;
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
break;
}
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ &d->h_denom_pub,
+ d->meta.start,
+ d->meta.expire_withdraw,
+ d->meta.expire_deposit,
+ d->meta.expire_legal,
+ &d->meta.value,
+ &d->meta.fees,
+ &TEH_master_public_key,
+ &d->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ }
}
- if (! ok)
- {
- GNUNET_free (akc.d_sigs);
- GNUNET_JSON_parse_free (spec);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- return ret;
- }
+
akc.ns_sigs = json_array_size (signkey_sigs);
akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
struct SigningSig);
@@ -447,27 +404,58 @@ TEH_handler_management_post_keys (
json_array_get (signkey_sigs,
i),
ispec);
- if (GNUNET_SYSERR == res)
+ if (GNUNET_OK != res)
{
- ret = MHD_NO; /* hard failure */
- ok = false;
- break;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- if (GNUNET_NO == res)
+ res = TEH_keys_get_timing (&s->exchange_pub,
+ &s->meta);
+ switch (res)
{
- ret = MHD_YES;
- ok = false;
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ /* For idempotency, check if the key is already active */
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
break;
}
- }
- if (! ok)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- GNUNET_free (akc.d_sigs);
- GNUNET_free (akc.s_sigs);
- GNUNET_JSON_parse_free (spec);
- return ret;
+
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ &s->exchange_pub,
+ s->meta.start,
+ s->meta.expire_sign,
+ s->meta.expire_legal,
+ &TEH_master_public_key,
+ &s->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ }
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received %u denomination and %u signing key signatures\n",
@@ -482,9 +470,7 @@ TEH_handler_management_post_keys (
&ret,
&add_keys,
&akc);
- GNUNET_free (akc.d_sigs);
- GNUNET_free (akc.s_sigs);
- GNUNET_JSON_parse_free (spec);
+ cleanup_akc (&akc);
if (GNUNET_SYSERR == res)
return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c
index 34825eda3..e0b8a3de8 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_disable.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_disable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-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
@@ -28,7 +28,7 @@
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -103,7 +103,7 @@ del_wire (void *cls,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if (0 == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (
connection,
@@ -114,7 +114,13 @@ del_wire (void *cls,
}
qs = TEH_plugin->update_wire (TEH_plugin->cls,
awc->payto_uri,
+ NULL,
+ NULL,
+ NULL,
awc->validity_end,
+ NULL,
+ NULL,
+ 0,
false);
if (qs < 0)
{
@@ -140,8 +146,8 @@ TEH_handler_management_post_wire_disable (
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig_del",
&awc.master_sig),
- GNUNET_JSON_spec_string ("payto_uri",
- &awc.payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &awc.payto_uri),
GNUNET_JSON_spec_timestamp ("validity_end",
&awc.validity_end),
GNUNET_JSON_spec_end ()
diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c
index 25ee0eeac..472e19d3e 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_enable.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_enable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2024 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
@@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -55,10 +55,35 @@ struct AddWireContext
const char *payto_uri;
/**
+ * (optional) address of a conversion service for this account.
+ */
+ const char *conversion_url;
+
+ /**
+ * Restrictions imposed when crediting this account.
+ */
+ const json_t *credit_restrictions;
+
+ /**
+ * Restrictions imposed when debiting this account.
+ */
+ const json_t *debit_restrictions;
+
+ /**
* Timestamp for checking against replay attacks.
*/
struct GNUNET_TIME_Timestamp validity_start;
+ /**
+ * Label to use for this bank. Default is empty.
+ */
+ const char *bank_label;
+
+ /**
+ * Priority of the bank in the list. Default 0.
+ */
+ int64_t priority;
+
};
@@ -111,15 +136,26 @@ add_wire (void *cls,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if (0 == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
qs = TEH_plugin->insert_wire (TEH_plugin->cls,
awc->payto_uri,
+ awc->conversion_url,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
awc->validity_start,
- &awc->master_sig_wire);
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority);
else
qs = TEH_plugin->update_wire (TEH_plugin->cls,
awc->payto_uri,
+ awc->conversion_url,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
awc->validity_start,
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority,
true);
if (qs < 0)
{
@@ -141,16 +177,34 @@ TEH_handler_management_post_wire (
struct MHD_Connection *connection,
const json_t *root)
{
- struct AddWireContext awc;
+ struct AddWireContext awc = {
+ .conversion_url = NULL
+ };
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
&awc.master_sig_wire),
GNUNET_JSON_spec_fixed_auto ("master_sig_add",
&awc.master_sig_add),
- GNUNET_JSON_spec_string ("payto_uri",
- &awc.payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &awc.payto_uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &awc.conversion_url),
+ NULL),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &awc.credit_restrictions),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &awc.debit_restrictions),
GNUNET_JSON_spec_timestamp ("validity_start",
&awc.validity_start),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &awc.bank_label),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_int64 ("priority",
+ &awc.priority),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -179,17 +233,23 @@ TEH_handler_management_post_wire (
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
msg);
+ GNUNET_JSON_parse_free (spec);
GNUNET_free (msg);
return ret;
}
}
if (GNUNET_OK !=
- TALER_exchange_offline_wire_add_verify (awc.payto_uri,
- awc.validity_start,
- &TEH_master_public_key,
- &awc.master_sig_add))
+ TALER_exchange_offline_wire_add_verify (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ awc.validity_start,
+ &TEH_master_public_key,
+ &awc.master_sig_add))
{
GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
@@ -198,11 +258,16 @@ TEH_handler_management_post_wire (
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
- TALER_exchange_wire_signature_check (awc.payto_uri,
- &TEH_master_public_key,
- &awc.master_sig_wire))
+ TALER_exchange_wire_signature_check (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ &TEH_master_public_key,
+ &awc.master_sig_wire))
{
GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
@@ -218,6 +283,7 @@ TEH_handler_management_post_wire (
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"payto:// URI `%s' is malformed\n",
awc.payto_uri);
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
@@ -237,6 +303,7 @@ TEH_handler_management_post_wire (
&ret,
&add_wire,
&awc);
+ GNUNET_JSON_parse_free (spec);
if (GNUNET_SYSERR == res)
return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_management_wire_fees.c b/src/exchange/taler-exchange-httpd_management_wire_fees.c
index dcfa87ef5..cb87592a5 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_fees.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_fees.c
@@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index 1e5c92e18..b31078f00 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -288,7 +288,7 @@ static MHD_RESULT
check_melt_valid (struct MHD_Connection *connection,
struct MeltContext *rmc)
{
- /* Baseline: check if deposits/refreshs are generally
+ /* Baseline: check if deposits/refreshes are generally
simply still allowed for this denomination */
struct TEH_DenominationKey *dk;
MHD_RESULT mret;
@@ -338,12 +338,12 @@ check_melt_valid (struct MHD_Connection *connection,
TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
NULL);
}
- switch (dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
break;
default:
diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h
index cae371f7c..318113c1f 100644
--- a/src/exchange/taler-exchange-httpd_metrics.h
+++ b/src/exchange/taler-exchange-httpd_metrics.h
@@ -34,18 +34,20 @@ enum TEH_MetricTypeRequest
TEH_MT_REQUEST_OTHER = 0,
TEH_MT_REQUEST_DEPOSIT = 1,
TEH_MT_REQUEST_WITHDRAW = 2,
- TEH_MT_REQUEST_MELT = 3,
- TEH_MT_REQUEST_PURSE_CREATE = 4,
- TEH_MT_REQUEST_PURSE_MERGE = 5,
- TEH_MT_REQUEST_RESERVE_PURSE = 6,
- TEH_MT_REQUEST_PURSE_DEPOSIT = 7,
- TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 8,
- TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 9,
- TEH_MT_REQUEST_IDEMPOTENT_MELT = 10,
- TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11,
- TEH_MT_REQUEST_BATCH_DEPOSIT = 12,
- TEH_MT_REQUEST_POLICY_FULFILLMENT = 13,
- TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */
+ TEH_MT_REQUEST_AGE_WITHDRAW = 3,
+ TEH_MT_REQUEST_MELT = 4,
+ TEH_MT_REQUEST_PURSE_CREATE = 5,
+ TEH_MT_REQUEST_PURSE_MERGE = 6,
+ TEH_MT_REQUEST_RESERVE_PURSE = 7,
+ TEH_MT_REQUEST_PURSE_DEPOSIT = 8,
+ TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9,
+ TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10,
+ TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11,
+ TEH_MT_REQUEST_IDEMPOTENT_MELT = 12,
+ TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13,
+ TEH_MT_REQUEST_BATCH_DEPOSIT = 14,
+ TEH_MT_REQUEST_POLICY_FULFILLMENT = 15,
+ TEH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */
};
/**
@@ -55,10 +57,12 @@ enum TEH_MetricTypeSuccess
{
TEH_MT_SUCCESS_DEPOSIT = 0,
TEH_MT_SUCCESS_WITHDRAW = 1,
- TEH_MT_SUCCESS_BATCH_WITHDRAW = 2,
- TEH_MT_SUCCESS_MELT = 3,
- TEH_MT_SUCCESS_REFRESH_REVEAL = 4,
- TEH_MT_SUCCESS_COUNT = 5 /* MUST BE LAST! */
+ TEH_MT_SUCCESS_AGE_WITHDRAW = 2,
+ TEH_MT_SUCCESS_BATCH_WITHDRAW = 3,
+ TEH_MT_SUCCESS_MELT = 4,
+ TEH_MT_SUCCESS_REFRESH_REVEAL = 5,
+ TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6,
+ TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */
};
/**
diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c
index f3da2c85c..2de9468fe 100644
--- a/src/exchange/taler-exchange-httpd_purses_create.c
+++ b/src/exchange/taler-exchange-httpd_purses_create.c
@@ -201,6 +201,7 @@ create_transaction (void *cls,
struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
bool balance_ok = false;
bool conflict = true;
+ bool too_late = true;
qs = TEH_make_coin_known (&coin->cpi,
connection,
@@ -215,6 +216,7 @@ create_transaction (void *cls,
&coin->coin_sig,
&coin->amount_minus_fee,
&balance_ok,
+ &too_late,
&conflict);
if (qs <= 0)
{
@@ -243,6 +245,15 @@ create_transaction (void *cls,
&coin->cpi.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ if (too_late)
+ {
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "too late to deposit on purse creation");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
if (conflict)
{
struct TALER_Amount amount;
@@ -419,7 +430,7 @@ TEH_handler_purses_create (
.pd.purse_pub = *purse_pub,
.exchange_timestamp = GNUNET_TIME_timestamp_get ()
};
- json_t *deposits;
+ const json_t *deposits;
json_t *deposit;
unsigned int idx;
struct GNUNET_JSON_Specification spec[] = {
@@ -438,8 +449,8 @@ TEH_handler_purses_create (
&pcc.purse_sig),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
&pcc.pd.h_contract_terms),
- GNUNET_JSON_spec_json ("deposits",
- &deposits),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
GNUNET_JSON_spec_timestamp ("purse_expiration",
&pcc.pd.purse_expiration),
GNUNET_JSON_spec_end ()
@@ -471,7 +482,6 @@ TEH_handler_purses_create (
pcc.exchange_timestamp))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW,
@@ -480,7 +490,6 @@ TEH_handler_purses_create (
if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER,
@@ -491,7 +500,6 @@ TEH_handler_purses_create (
(pcc.num_coins > TALER_MAX_FRESH_COINS) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -504,7 +512,6 @@ TEH_handler_purses_create (
if (NULL == keys)
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
@@ -536,7 +543,6 @@ TEH_handler_purses_create (
deposit);
if (GNUNET_OK != res)
{
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<idx; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -548,7 +554,6 @@ TEH_handler_purses_create (
&pcc.deposit_total))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (pcc.coins);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
@@ -568,7 +573,6 @@ TEH_handler_purses_create (
&pcc.purse_sig))
{
TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -586,7 +590,6 @@ TEH_handler_purses_create (
&pcc.econtract.econtract_sig)) )
{
TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -601,7 +604,6 @@ TEH_handler_purses_create (
TEH_plugin->preflight (TEH_plugin->cls))
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -623,7 +625,6 @@ TEH_handler_purses_create (
&create_transaction,
&pcc))
{
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -642,7 +643,6 @@ TEH_handler_purses_create (
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
return res;
}
}
diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c
new file mode 100644
index 000000000..5bf7c24c9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_purses_delete.c
@@ -0,0 +1,141 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_purses_delete.c
+ * @brief Handle DELETE /purses/$PID requests; parses the request and
+ * verifies the signature before handing deletion to the database.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_dbevents.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_purses_delete.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ bool found;
+ bool decided;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &purse_pub,
+ sizeof (purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+ args[0]);
+ }
+ TALER_MHD_parse_request_header_auto_t (connection,
+ "Taler-Purse-Signature",
+ &purse_sig);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_delete_verify (&purse_pub,
+ &purse_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_DELETE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->do_purse_delete (TEH_plugin->cls,
+ &purse_pub,
+ &purse_sig,
+ &decided,
+ &found);
+ if (qs <= 0)
+ {
+ TALER_LOG_WARNING (
+ "Failed to store delete purse information in database\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse delete");
+ }
+ }
+ if (! found)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ }
+ if (decided)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED,
+ NULL);
+ }
+ {
+ /* Possible minor optimization: integrate notification with
+ transaction above... */
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse deletion %s\n",
+ TALER_B2S (&purse_pub));
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+ /* success */
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_purses_delete.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_status.h b/src/exchange/taler-exchange-httpd_purses_delete.h
index 831b270f7..912dd43a8 100644
--- a/src/exchange/taler-exchange-httpd_reserves_status.h
+++ b/src/exchange/taler-exchange-httpd_purses_delete.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2022 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
@@ -14,30 +14,29 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-exchange-httpd_reserves_status.h
- * @brief Handle /reserves/$RESERVE_PUB STATUS requests
- * @author Florian Dold
- * @author Benedikt Mueller
+ * @file taler-exchange-httpd_purses_delete.h
+ * @brief Handle DELETE /purses/$PID requests
* @author Christian Grothoff
*/
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_STATUS_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_STATUS_H
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
+#define TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
+#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
- * Handle a POST "/reserves/$RID/status" request.
+ * Handle a DELETE "/purses/$PURSE_PUB" request.
*
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @param root uploaded body from the client
+ * @param rc request details about the request to handle
+ * @param args argument with the public key of the purse
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_reserves_status (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c
index dc1a256d7..8e4d5e41a 100644
--- a/src/exchange/taler-exchange-httpd_purses_deposit.c
+++ b/src/exchange/taler-exchange-httpd_purses_deposit.c
@@ -166,6 +166,7 @@ deposit_transaction (void *cls,
struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
bool balance_ok = false;
bool conflict = true;
+ bool too_late = true;
qs = TEH_make_coin_known (&coin->cpi,
connection,
@@ -180,18 +181,20 @@ deposit_transaction (void *cls,
&coin->coin_sig,
&coin->amount_minus_fee,
&balance_ok,
+ &too_late,
&conflict);
if (qs <= 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
+ GNUNET_break (0 != qs);
TALER_LOG_WARNING (
"Failed to store purse deposit information in database\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"do purse deposit");
- return qs;
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
if (! balance_ok)
{
@@ -203,6 +206,16 @@ deposit_transaction (void *cls,
&coin->cpi.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ if (too_late)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_DECIDED_ALREADY,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
if (conflict)
{
struct TALER_Amount amount;
@@ -316,12 +329,12 @@ TEH_handler_purses_deposit (
.purse_pub = purse_pub,
.exchange_timestamp = GNUNET_TIME_timestamp_get ()
};
- json_t *deposits;
+ const json_t *deposits;
json_t *deposit;
unsigned int idx;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("deposits",
- &deposits),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
GNUNET_JSON_spec_end ()
};
@@ -350,7 +363,6 @@ TEH_handler_purses_deposit (
(pcc.num_coins > TALER_MAX_FRESH_COINS) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -361,6 +373,8 @@ TEH_handler_purses_deposit (
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp create_timestamp;
struct GNUNET_TIME_Timestamp merge_timestamp;
+ bool was_deleted;
+ bool was_refunded;
qs = TEH_plugin->select_purse (
TEH_plugin->cls,
@@ -370,7 +384,9 @@ TEH_handler_purses_deposit (
&pcc.amount,
&pcc.deposit_total,
&pcc.h_contract_terms,
- &merge_timestamp);
+ &merge_timestamp,
+ &was_deleted,
+ &was_refunded);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -393,12 +409,16 @@ TEH_handler_purses_deposit (
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break; /* handled below */
}
- if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time))
+ if (was_refunded ||
+ was_deleted)
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
- NULL);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ was_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ GNUNET_TIME_timestamp2s (pcc.purse_expiration));
}
}
@@ -416,7 +436,6 @@ TEH_handler_purses_deposit (
deposit);
if (GNUNET_OK != res)
{
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<idx; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -428,7 +447,6 @@ TEH_handler_purses_deposit (
TEH_plugin->preflight (TEH_plugin->cls))
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -450,7 +468,6 @@ TEH_handler_purses_deposit (
&deposit_transaction,
&pcc))
{
- GNUNET_JSON_parse_free (spec);
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
@@ -482,7 +499,6 @@ TEH_handler_purses_deposit (
for (unsigned int i = 0; i<pcc.num_coins; i++)
TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
return res;
}
}
diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c
index 8384086b6..22328fe09 100644
--- a/src/exchange/taler-exchange-httpd_purses_get.c
+++ b/src/exchange/taler-exchange-httpd_purses_get.c
@@ -57,6 +57,12 @@ struct GetContext
struct GNUNET_DB_EventHandler *eh;
/**
+ * Subscription for refund event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *ehr;
+
+ /**
* Public key of our purse.
*/
struct TALER_PurseContractPublicKeyP purse_pub;
@@ -153,6 +159,12 @@ gc_cleanup (struct TEH_RequestContext *rc)
gc->eh);
gc->eh = NULL;
}
+ if (NULL != gc->ehr)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ gc->ehr);
+ gc->ehr = NULL;
+ }
GNUNET_free (gc);
}
@@ -207,6 +219,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
const char *const args[2])
{
struct GetContext *gc = rc->rh_ctx;
+ bool purse_deleted;
+ bool purse_refunded;
MHD_RESULT res;
if (NULL == gc)
@@ -242,36 +256,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
args[1]);
}
- {
- const char *long_poll_timeout_ms;
-
- long_poll_timeout_ms
- = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_ms)
- {
- unsigned int timeout_ms;
- char dummy;
- struct GNUNET_TIME_Relative timeout;
-
- if (1 != sscanf (long_poll_timeout_ms,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms (must be non-negative number)");
- }
- timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- gc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
- }
- }
-
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &gc->timeout);
if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
(NULL == gc->eh) )
{
@@ -298,6 +284,20 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
GNUNET_break (0);
gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
}
+ else
+ {
+ struct GNUNET_DB_EventHeaderP repr = {
+ .size = htons (sizeof (repr)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED),
+ };
+
+ gc->ehr = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (gc->timeout),
+ &repr,
+ &db_event_cb,
+ rc);
+ }
}
} /* end first-time initialization */
@@ -312,7 +312,9 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
&gc->amount,
&gc->deposited,
&gc->h_contract,
- &gc->merge_timestamp);
+ &gc->merge_timestamp,
+ &purse_deleted,
+ &purse_refunded);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -335,43 +337,17 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break; /* handled below */
}
- if (GNUNET_TIME_absolute_cmp (gc->timeout,
- >,
- gc->purse_expiration.abs_time))
- {
- /* Timeout too high, need to replace event handler */
- struct TALER_PurseEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (
- gc->wait_for_merge
- ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
- : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
- .purse_pub = gc->purse_pub
- };
- struct GNUNET_DB_EventHandler *eh2;
-
- gc->timeout = gc->purse_expiration.abs_time;
- eh2 = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (gc->timeout),
- &rep.header,
- &db_event_cb,
- rc);
- if (NULL == eh2)
- {
- GNUNET_break (0);
- gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
- }
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- gc->eh);
- gc->eh = eh2;
- }
}
- if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time))
+ if (purse_refunded ||
+ purse_deleted)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse refunded or deleted\n");
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ purse_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
GNUNET_TIME_timestamp2s (
gc->purse_expiration));
}
@@ -408,7 +384,10 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
if (0 <
TALER_amount_cmp (&gc->amount,
&gc->deposited))
+ {
+ /* amount > deposited: not yet fully paid */
dt = GNUNET_TIME_UNIT_ZERO_TS;
+ }
if (TALER_EC_NONE !=
(ec = TALER_exchange_online_purse_status_sign (
&TEH_keys_exchange_sign_,
@@ -417,10 +396,16 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
&gc->deposited,
&exchange_pub,
&exchange_sig)))
+ {
res = TALER_MHD_reply_with_ec (rc->connection,
ec,
NULL);
+ }
else
+ {
+ /* Make sure merge_timestamp is omitted if not yet merged */
+ if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time))
+ gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS;
res = TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
@@ -430,6 +415,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
&exchange_sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ gc->purse_expiration),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("merge_timestamp",
gc->merge_timestamp)),
@@ -437,6 +424,7 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
GNUNET_JSON_pack_timestamp ("deposit_timestamp",
dt))
);
+ }
}
return res;
}
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c
index d3bded6e0..fb5ce4d90 100644
--- a/src/exchange/taler-exchange-httpd_purses_merge.c
+++ b/src/exchange/taler-exchange-httpd_purses_merge.c
@@ -34,7 +34,6 @@
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_lib.h"
#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_wire.h"
/**
@@ -280,23 +279,48 @@ merge_transaction (void *cls,
bool in_conflict = true;
bool no_balance = true;
bool no_partner = true;
- const char *required;
+ char *required;
- required = TALER_KYCLOGIC_kyc_test_required (
+ qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
&pcc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_iterator,
- pcc);
+ pcc,
+ &required);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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 != required)
{
pcc->kyc.ok = false;
- return TEH_plugin->insert_kyc_requirement_for_account (
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
required,
&pcc->h_payto,
+ &pcc->reserve_pub,
&pcc->kyc.requirement_row);
+ GNUNET_free (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;
}
pcc->kyc.ok = true;
qs = TEH_plugin->do_purse_merge (
@@ -314,8 +338,7 @@ merge_transaction (void *cls,
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
- TALER_LOG_WARNING (
- "Failed to store merge purse information in database\n");
+ GNUNET_break (0);
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
@@ -347,13 +370,15 @@ merge_transaction (void *cls,
struct GNUNET_TIME_Timestamp merge_timestamp;
char *partner_url = NULL;
struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
pcc->purse_pub,
&merge_sig,
&merge_timestamp,
&partner_url,
- &reserve_pub);
+ &reserve_pub,
+ &refunded);
if (qs <= 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -367,18 +392,39 @@ merge_transaction (void *cls,
"select purse merge");
return qs;
}
- *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &merge_sig),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("partner_url",
- partner_url)),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub));
+ if (refunded)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ GNUNET_memcmp (&merge_sig,
+ &pcc->merge_sig))
+ {
+ *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* idempotent! */
+ *mhd_ret = reply_merge_success (connection,
+ pcc);
GNUNET_free (partner_url);
return GNUNET_DB_STATUS_HARD_ERROR;
}
@@ -398,8 +444,8 @@ TEH_handler_purses_merge (
.exchange_timestamp = GNUNET_TIME_timestamp_get ()
};
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("payto_uri",
- &pcc.payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &pcc.payto_uri),
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&pcc.reserve_sig),
GNUNET_JSON_spec_fixed_auto ("merge_sig",
@@ -602,53 +648,6 @@ TEH_handler_purses_merge (
}
}
- if (GNUNET_TIME_absolute_is_past (pcc.purse_expiration.abs_time))
- {
- struct TALER_PurseMergeSignatureP merge_sig;
- struct GNUNET_TIME_Timestamp merge_timestamp;
- char *partner_url = NULL;
- struct TALER_ReservePublicKeyP reserve_pub;
-
- qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
- pcc.purse_pub,
- &merge_sig,
- &merge_timestamp,
- &partner_url,
- &reserve_pub);
- if (qs <= 0)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
- NULL);
- }
- if (0 !=
- GNUNET_memcmp (&merge_sig,
- &pcc.merge_sig))
- {
- MHD_RESULT mhd_res;
-
- mhd_res = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &merge_sig),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("partner_url",
- partner_url)),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub));
- GNUNET_free (partner_url);
- return mhd_res;
- }
- GNUNET_free (partner_url);
- /* request was idempotent, return success! */
- return reply_merge_success (connection,
- &pcc);
- }
-
/* execute transaction */
{
MHD_RESULT mhd_ret;
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c
index d52dabda0..a5d5b2ab4 100644
--- a/src/exchange/taler-exchange-httpd_recoup-refresh.c
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2021 Taler Systems SA
+ Copyright (C) 2017-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
@@ -55,7 +55,7 @@ struct RecoupContext
/**
* Key used to blind the coin.
*/
- const union TALER_DenominationBlindingKeyP *coin_bks;
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
/**
* Signature of the coin requesting recoup.
@@ -175,8 +175,8 @@ verify_and_execute_recoup_refresh (
struct MHD_Connection *connection,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_ExchangeWithdrawValues *exchange_vals,
- const union TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_CsNonce *nonce,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
const struct TALER_CoinSpendSignatureP *coin_sig)
{
struct RecoupContext pc;
@@ -219,12 +219,12 @@ verify_and_execute_recoup_refresh (
}
/* check denomination signature */
- switch (dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
break;
default:
@@ -265,6 +265,7 @@ verify_and_execute_recoup_refresh (
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
+ nonce,
&coin->h_age_commitment,
&coin->coin_pub,
exchange_vals,
@@ -278,9 +279,6 @@ verify_and_execute_recoup_refresh (
TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
NULL);
}
- if (TALER_DENOMINATION_CS == blinded_planchet.cipher)
- blinded_planchet.details.cs_blinded_planchet.nonce
- = *nonce;
TALER_coin_ev_hash (&blinded_planchet,
&coin->denom_pub_hash,
&h_blind);
@@ -375,10 +373,11 @@ TEH_handler_recoup_refresh (struct MHD_Connection *connection,
{
enum GNUNET_GenericReturnValue ret;
struct TALER_CoinPublicInfo coin = {0};
- union TALER_DenominationBlindingKeyP coin_bks;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
struct TALER_CoinSpendSignatureP coin_sig;
struct TALER_ExchangeWithdrawValues exchange_vals;
- struct TALER_CsNonce nonce;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&coin.denom_pub_hash),
@@ -394,19 +393,17 @@ TEH_handler_recoup_refresh (struct MHD_Connection *connection,
GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
&coin.h_age_commitment),
&coin.no_age_commitment),
+ // FIXME: rename to just 'nonce'
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("cs_nonce",
&nonce),
- NULL),
+ &no_nonce),
GNUNET_JSON_spec_end ()
};
memset (&coin,
0,
sizeof (coin));
- memset (&nonce,
- 0,
- sizeof (nonce));
coin.coin_pub = *coin_pub;
ret = TALER_MHD_parse_json_data (connection,
root,
@@ -422,7 +419,9 @@ TEH_handler_recoup_refresh (struct MHD_Connection *connection,
&coin,
&exchange_vals,
&coin_bks,
- &nonce,
+ no_nonce
+ ? NULL
+ : &nonce,
&coin_sig);
GNUNET_JSON_parse_free (spec);
return res;
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index 349c2b94a..afbbd7474 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -58,7 +58,7 @@ struct RecoupContext
/**
* Key used to blind the coin.
*/
- const union TALER_DenominationBlindingKeyP *coin_bks;
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
/**
* Signature of the coin requesting recoup.
@@ -178,8 +178,8 @@ verify_and_execute_recoup (
struct MHD_Connection *connection,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_ExchangeWithdrawValues *exchange_vals,
- const union TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_CsNonce *nonce,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
const struct TALER_CoinSpendSignatureP *coin_sig)
{
struct RecoupContext pc;
@@ -221,12 +221,12 @@ verify_and_execute_recoup (
}
/* check denomination signature */
- switch (dk->denom_pub.cipher)
+ switch (dk->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
break;
default:
@@ -270,6 +270,7 @@ verify_and_execute_recoup (
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
+ nonce,
&coin->h_age_commitment,
&coin->coin_pub,
exchange_vals,
@@ -283,20 +284,9 @@ verify_and_execute_recoup (
TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
NULL);
}
- if (TALER_DENOMINATION_CS == blinded_planchet.cipher)
- blinded_planchet.details.cs_blinded_planchet.nonce
- = *nonce;
- if (GNUNET_OK !=
- TALER_coin_ev_hash (&blinded_planchet,
- &coin->denom_pub_hash,
- &pc.h_coin_ev))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- }
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &pc.h_coin_ev);
TALER_blinded_planchet_free (&blinded_planchet);
}
@@ -388,10 +378,11 @@ TEH_handler_recoup (struct MHD_Connection *connection,
{
enum GNUNET_GenericReturnValue ret;
struct TALER_CoinPublicInfo coin;
- union TALER_DenominationBlindingKeyP coin_bks;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
struct TALER_CoinSpendSignatureP coin_sig;
struct TALER_ExchangeWithdrawValues exchange_vals;
- struct TALER_CsNonce nonce;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&coin.denom_pub_hash),
@@ -407,19 +398,17 @@ TEH_handler_recoup (struct MHD_Connection *connection,
GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
&coin.h_age_commitment),
&coin.no_age_commitment),
+ // FIXME: should be renamed to just 'nonce'!
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("cs_nonce",
&nonce),
- NULL),
+ &no_nonce),
GNUNET_JSON_spec_end ()
};
memset (&coin,
0,
sizeof (coin));
- memset (&nonce,
- 0,
- sizeof (nonce));
coin.coin_pub = *coin_pub;
ret = TALER_MHD_parse_json_data (connection,
root,
@@ -435,7 +424,9 @@ TEH_handler_recoup (struct MHD_Connection *connection,
&coin,
&exchange_vals,
&coin_bks,
- &nonce,
+ no_nonce
+ ? NULL
+ : &nonce,
&coin_sig);
GNUNET_JSON_parse_free (spec);
return res;
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 08a85265c..5630051cf 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -111,9 +111,6 @@ struct RevealContext
/**
* Array of information about fresh coins being revealed.
*/
- /* FIXME: const would be nicer here, but we initialize
- the 'alg_values' in the verification
- routine; suboptimal to be fixed... */
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
/**
@@ -159,14 +156,17 @@ check_commitment (struct RevealContext *rctx,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
- struct TALER_CsNonce nonces[rctx->num_fresh_coins];
- unsigned int aoff = 0;
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonces[rctx->num_fresh_coins];
+ memset (nonces,
+ 0,
+ sizeof (nonces));
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
- if (dk->cipher != rctx->rcds[j].blinded_planchet.cipher)
+ if (dk->bsign_pub_key->cipher !=
+ rctx->rcds[j].blinded_planchet.blinded_message->cipher)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (
@@ -176,9 +176,9 @@ check_commitment (struct RevealContext *rctx,
NULL);
return GNUNET_SYSERR;
}
- switch (dk->cipher)
+ switch (dk->bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_INVALID:
+ case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (
connection,
@@ -186,44 +186,48 @@ check_commitment (struct RevealContext *rctx,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
NULL);
return GNUNET_SYSERR;
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
continue;
- case TALER_DENOMINATION_CS:
- nonces[aoff]
- = rctx->rcds[j].blinded_planchet.details.cs_blinded_planchet.nonce;
- aoff++;
+ case GNUNET_CRYPTO_BSA_CS:
+ nonces[j]
+ = (const union GNUNET_CRYPTO_BlindSessionNonce *)
+ &rctx->rcds[j].blinded_planchet.blinded_message->details.
+ cs_blinded_message.nonce;
break;
}
}
// OPTIMIZE: do this in batch later!
- aoff = 0;
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
struct TALER_ExchangeWithdrawValues *alg_values
= &rctx->rrcs[j].exchange_vals;
+ struct GNUNET_CRYPTO_BlindingInputValues *bi;
- alg_values->cipher = dk->cipher;
- switch (dk->cipher)
+ bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+ alg_values->blinding_inputs = bi;
+ bi->rc = 1;
+ bi->cipher = dk->bsign_pub_key->cipher;
+ switch (dk->bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_INVALID:
+ case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_assert (0);
return GNUNET_SYSERR;
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
continue;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
{
enum TALER_ErrorCode ec;
const struct TEH_CsDeriveData cdd = {
.h_denom_pub = &rctx->rrcs[j].h_denom_pub,
- .nonce = &nonces[aoff]
+ .nonce = &nonces[j]->cs_nonce
};
ec = TEH_keys_denomination_cs_r_pub (
&cdd,
true,
- &alg_values->details.cs_values);
+ &bi->details.cs_values);
if (TALER_EC_NONE != ec)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
@@ -232,7 +236,6 @@ check_commitment (struct RevealContext *rctx,
NULL);
return GNUNET_SYSERR;
}
- aoff++;
}
}
}
@@ -274,14 +277,11 @@ check_commitment (struct RevealContext *rctx,
&ts);
rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
struct TALER_RefreshCoinData);
- aoff = 0;
for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
{
- const struct TALER_DenominationPublicKey *dk
- = &rctx->dks[j]->denom_pub;
struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
struct TALER_CoinSpendPrivateKeyP coin_priv;
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
const struct TALER_ExchangeWithdrawValues *alg_value
= &rctx->rrcs[j].exchange_vals;
struct TALER_PlanchetDetail pd = {0};
@@ -314,8 +314,9 @@ check_commitment (struct RevealContext *rctx,
&acp,
&ts.key,
&nacp));
-
- TALER_age_commitment_hash (&nacp.commitment, &h);
+ TALER_age_commitment_hash (&nacp.commitment,
+ &h);
+ TALER_age_commitment_proof_free (&nacp);
hac = &h;
}
@@ -323,16 +324,11 @@ check_commitment (struct RevealContext *rctx,
TALER_planchet_prepare (rcd->dk,
alg_value,
&bks,
+ nonces[j],
&coin_priv,
hac,
&c_hash,
&pd));
- if (TALER_DENOMINATION_CS == dk->cipher)
- {
- pd.blinded_planchet.details.cs_blinded_planchet.nonce =
- nonces[aoff];
- aoff++;
- }
rcd->blinded_planchet = pd.blinded_planchet;
}
}
@@ -511,7 +507,7 @@ resolve_refreshes_reveal_denominations (
}
}
- old_dk = TEH_keys_denomination_by_hash2 (
+ old_dk = TEH_keys_denomination_by_hash_from_state (
ksh,
&rctx->melt.session.coin.denom_pub_hash,
connection,
@@ -536,13 +532,14 @@ resolve_refreshes_reveal_denominations (
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- dks[i] = TEH_keys_denomination_by_hash2 (ksh,
- &rrcs[i].h_denom_pub,
- connection,
- &ret);
+ dks[i] = TEH_keys_denomination_by_hash_from_state (ksh,
+ &rrcs[i].h_denom_pub,
+ connection,
+ &ret);
if (NULL == dks[i])
return ret;
- if ( (TALER_DENOMINATION_CS == dks[i]->denom_pub.cipher) &&
+ if ( (GNUNET_CRYPTO_BSA_CS ==
+ dks[i]->denom_pub.bsign_pub_key->cipher) &&
(rctx->no_rms) )
{
return TALER_MHD_reply_with_error (
@@ -724,7 +721,8 @@ clean_age:
rcd->blinded_planchet = rrc->blinded_planchet;
rcd->dk = &dks[i]->denom_pub;
- if (rcd->blinded_planchet.cipher != rcd->dk->cipher)
+ if (rcd->blinded_planchet.blinded_message->cipher !=
+ rcd->dk->bsign_pub_key->cipher)
{
GNUNET_break_op (0);
ret = TALER_MHD_REPLY_JSON_PACK (
@@ -761,8 +759,8 @@ clean_age:
csds[i].bp = &rcds[i].blinded_planchet;
}
ec = TEH_keys_denomination_batch_sign (
- csds,
rctx->num_fresh_coins,
+ csds,
true,
bss);
if (TALER_EC_NONE != ec)
@@ -773,12 +771,17 @@ clean_age:
NULL);
goto cleanup;
}
+
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
+ {
rrcs[i].coin_sig = bss[i];
+ rrcs[i].blinded_planchet = rcds[i].blinded_planchet;
+ }
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Signatures ready, starting DB interaction\n");
+
for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
{
bool changed;
@@ -795,12 +798,7 @@ clean_age:
NULL);
goto cleanup;
}
- for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
- rrc->blinded_planchet = rcds[i].blinded_planchet;
- }
qs = TEH_plugin->insert_refresh_reveal (
TEH_plugin->cls,
melt_serial_id,
@@ -865,7 +863,10 @@ cleanup:
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ struct TALER_ExchangeWithdrawValues *alg_values
+ = &rrcs[i].exchange_vals;
+ GNUNET_free (alg_values->blinding_inputs);
TALER_blinded_denom_sig_free (&rrc->coin_sig);
TALER_blinded_planchet_free (&rrc->blinded_planchet);
}
@@ -983,26 +984,26 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[2])
{
- json_t *coin_evs;
- json_t *transfer_privs;
- json_t *link_sigs;
- json_t *new_denoms_h;
- json_t *old_age_commitment;
+ const json_t *coin_evs;
+ const json_t *transfer_privs;
+ const json_t *link_sigs;
+ const json_t *new_denoms_h;
+ const json_t *old_age_commitment;
struct RevealContext rctx;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("transfer_pub",
&rctx.gamma_tp),
- GNUNET_JSON_spec_json ("transfer_privs",
- &transfer_privs),
- GNUNET_JSON_spec_json ("link_sigs",
- &link_sigs),
- GNUNET_JSON_spec_json ("coin_evs",
- &coin_evs),
- GNUNET_JSON_spec_json ("new_denoms_h",
- &new_denoms_h),
+ GNUNET_JSON_spec_array_const ("transfer_privs",
+ &transfer_privs),
+ GNUNET_JSON_spec_array_const ("link_sigs",
+ &link_sigs),
+ GNUNET_JSON_spec_array_const ("coin_evs",
+ &coin_evs),
+ GNUNET_JSON_spec_array_const ("new_denoms_h",
+ &new_denoms_h),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("old_age_commitment",
- &old_age_commitment),
+ GNUNET_JSON_spec_array_const ("old_age_commitment",
+ &old_age_commitment),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("rms",
@@ -1053,7 +1054,6 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
/* Note we do +1 as 1 row (cut-and-choose!) is missing! */
if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1)
{
- GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
@@ -1061,19 +1061,13 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
NULL);
}
- {
- MHD_RESULT res;
-
- res = handle_refreshes_reveal_json (rc->connection,
- &rctx,
- transfer_privs,
- link_sigs,
- new_denoms_h,
- old_age_commitment,
- coin_evs);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
+ return handle_refreshes_reveal_json (rc->connection,
+ &rctx,
+ transfer_privs,
+ link_sigs,
+ new_denoms_h,
+ old_age_commitment,
+ coin_evs);
}
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 33ead7c69..b8bcf7c60 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -158,6 +158,7 @@ refund_transaction (void *cls,
}
if (conflict)
{
+ GNUNET_break_op (0);
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
connection,
TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c
index f88f2c300..7bbebaad7 100644
--- a/src/exchange/taler-exchange-httpd_reserves_attest.c
+++ b/src/exchange/taler-exchange-httpd_reserves_attest.c
@@ -68,7 +68,7 @@ struct ReserveAttestContext
/**
* List of requested details.
*/
- json_t *details;
+ const json_t *details;
/**
* Client signature approving the request.
@@ -76,14 +76,14 @@ struct ReserveAttestContext
struct TALER_ReserveSignatureP reserve_sig;
/**
- * Attributes we are affirming.
+ * Attributes we are affirming. JSON object.
*/
json_t *json_attest;
/**
- * Error code encountered in interaction with KYC provider.
+ * Database error codes encountered.
*/
- enum TALER_ErrorCode ec;
+ enum GNUNET_DB_QueryStatus qs;
/**
* Set to true if we did not find the reserve.
@@ -140,8 +140,12 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
&exchange_sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
- GNUNET_JSON_pack_array_steal ("attest",
- rhc->json_attest));
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ now),
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ rhc->etime),
+ GNUNET_JSON_pack_object_steal ("attributes",
+ rhc->json_attest));
}
@@ -152,68 +156,68 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
* set based on the details requested by the client.
*
* @param cls our `struct ReserveAttestContext *`
- * @param provider_section KYC provider configuration section
- * @param provider_user_id UID at a provider (can be NULL)
- * @param legi_id legitimization process ID (can be NULL)
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
*/
static void
kyc_process_cb (void *cls,
+ const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
- const char *provider_user_id,
- const char *legi_id)
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
{
struct ReserveAttestContext *rsc = cls;
- struct GNUNET_TIME_Timestamp etime;
json_t *attrs;
+ json_t *val;
+ const char *name;
bool match = false;
- rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
- provider_user_id,
- legi_id,
- &etime,
- &attrs);
- if (TALER_EC_NONE != rsc->ec)
+ if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
return;
- if (GNUNET_TIME_absolute_is_past (etime.abs_time))
+ attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ json_object_foreach (attrs, name, val)
{
- json_decref (attrs);
- return;
- }
- {
- json_t *val;
- const char *name;
-
- json_object_foreach (attrs, name, val)
+ bool requested = false;
+ size_t idx;
+ json_t *str;
+
+ if (NULL != json_object_get (rsc->json_attest,
+ name))
+ continue; /* duplicate */
+ json_array_foreach (rsc->details, idx, str)
{
- bool requested = false;
- size_t idx;
- json_t *str;
-
- if (NULL != json_object_get (rsc->json_attest,
- name))
- continue; /* duplicate */
- json_array_foreach (rsc->details, idx, str)
+ if (0 == strcmp (json_string_value (str),
+ name))
{
- if (0 == strcmp (json_string_value (str),
- name))
- {
- requested = true;
- break;
- }
+ requested = true;
+ break;
}
- if (! requested)
- continue;
- match = true;
- GNUNET_assert (0 ==
- json_object_set (rsc->json_attest, /* NOT set_new! */
- name,
- val));
}
+ if (! requested)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Skipping attribute `%s': not requested\n",
+ name);
+ continue;
+ }
+ match = true;
+ GNUNET_assert (0 ==
+ json_object_set (rsc->json_attest, /* NOT set_new! */
+ name,
+ val));
}
json_decref (attrs);
if (! match)
return;
- rsc->etime = GNUNET_TIME_timestamp_min (etime,
+ rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
rsc->etime);
}
@@ -241,9 +245,9 @@ reserve_attest_transaction (void *cls,
struct ReserveAttestContext *rsc = cls;
enum GNUNET_DB_QueryStatus qs;
- rsc->json_attest = json_array ();
+ rsc->json_attest = json_object ();
GNUNET_assert (NULL != rsc->json_attest);
- qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
&rsc->h_payto,
&kyc_process_cb,
rsc);
@@ -255,7 +259,7 @@ reserve_attest_transaction (void *cls,
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "iterate_kyc_reference");
+ "select_kyc_attributes");
return qs;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
@@ -283,8 +287,8 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("request_timestamp",
&rsc.timestamp),
- GNUNET_JSON_spec_json ("details",
- &rsc.details),
+ GNUNET_JSON_spec_array_const ("details",
+ &rsc.details),
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&rsc.reserve_sig),
GNUNET_JSON_spec_end ()
@@ -373,16 +377,8 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
args[0]);
}
- if (TALER_EC_NONE != rsc.ec)
- {
- json_decref (rsc.json_attest);
- return TALER_MHD_reply_with_ec (rc->connection,
- rsc.ec,
- NULL);
- }
- mhd_ret = reply_reserve_attest_success (rc->connection,
- &rsc);
- return mhd_ret;
+ return reply_reserve_attest_success (rc->connection,
+ &rsc);
}
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c
index be36f1e73..bbf234428 100644
--- a/src/exchange/taler-exchange-httpd_reserves_close.c
+++ b/src/exchange/taler-exchange-httpd_reserves_close.c
@@ -27,7 +27,7 @@
#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
#include "taler_dbevents.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_reserves_close.h"
#include "taler-exchange-httpd_responses.h"
@@ -178,14 +178,13 @@ reserve_close_transaction (void *cls,
{
struct ReserveCloseContext *rcc = cls;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount balance;
char *payto_uri = NULL;
const struct TALER_WireFeeSet *wf;
qs = TEH_plugin->select_reserve_close_info (
TEH_plugin->cls,
rcc->reserve_pub,
- &balance,
+ &rcc->balance,
&payto_uri);
switch (qs)
{
@@ -226,19 +225,34 @@ reserve_close_transaction (void *cls,
(0 != strcmp (payto_uri,
rcc->payto_uri)) ) )
{
- const char *kyc_needed;
+ /* KYC check may be needed: we're not returning
+ the money to the account that funded the reserve
+ in the first place. */
+ char *kyc_needed;
TALER_payto_hash (rcc->payto_uri,
&rcc->kyc_payto);
rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- kyc_needed
- = TALER_KYCLOGIC_kyc_test_required (
- TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
- &rcc->kyc_payto,
- TEH_plugin->select_satisfied_kyc_processes,
- TEH_plugin->cls,
- &amount_it,
- rcc);
+ qs = TALER_KYCLOGIC_kyc_test_required (
+ TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
+ &rcc->kyc_payto,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &amount_it,
+ rcc,
+ &kyc_needed);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "iterate_reserve_close_info");
+ return qs;
+ }
if (rcc->qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs)
@@ -251,12 +265,27 @@ reserve_close_transaction (void *cls,
"iterate_reserve_close_info");
return qs;
}
- rcc->kyc.ok = false;
- return TEH_plugin->insert_kyc_requirement_for_account (
- TEH_plugin->cls,
- kyc_needed,
- &rcc->kyc_payto,
- &rcc->kyc.requirement_row);
+ if (NULL != kyc_needed)
+ {
+ rcc->kyc.ok = false;
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
+ TEH_plugin->cls,
+ kyc_needed,
+ &rcc->kyc_payto,
+ rcc->reserve_pub,
+ &rcc->kyc.requirement_row);
+ GNUNET_free (kyc_needed);
+ 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;
+ }
}
rcc->kyc.ok = true;
@@ -284,7 +313,7 @@ reserve_close_transaction (void *cls,
if (0 >
TALER_amount_subtract (&rcc->wire_amount,
- &balance,
+ &rcc->balance,
&wf->closing))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -302,7 +331,7 @@ reserve_close_transaction (void *cls,
payto_uri,
&rcc->reserve_sig,
rcc->timestamp,
- &balance,
+ &rcc->balance,
&wf->closing);
GNUNET_free (payto_uri);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -338,8 +367,8 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc,
GNUNET_JSON_spec_timestamp ("request_timestamp",
&rcc.timestamp),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("payto_uri",
- &rcc.payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &rcc.payto_uri),
NULL),
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&rcc.reserve_sig),
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c
index 88f7aca9c..0775a4c65 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.c
+++ b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -52,8 +52,12 @@ struct ReservePoller
struct MHD_Connection *connection;
/**
- * Subscription for the database event we are
- * waiting for.
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
*/
struct GNUNET_DB_EventHandler *eh;
@@ -63,6 +67,16 @@ struct ReservePoller
struct GNUNET_TIME_Absolute timeout;
/**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Balance of the reserve, set in the callback.
+ */
+ struct TALER_Amount balance;
+
+ /**
* True if we are still suspended.
*/
bool suspended;
@@ -84,13 +98,10 @@ static struct ReservePoller *rp_tail;
void
TEH_reserves_get_cleanup ()
{
- struct ReservePoller *rp;
-
- while (NULL != (rp = rp_head))
+ for (struct ReservePoller *rp = rp_head;
+ NULL != rp;
+ rp = rp->next)
{
- GNUNET_CONTAINER_DLL_remove (rp_head,
- rp_tail,
- rp);
if (rp->suspended)
{
rp->suspended = false;
@@ -115,11 +126,14 @@ rp_cleanup (struct TEH_RequestContext *rc)
if (NULL != rp->eh)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening\n");
+ "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
rp->eh);
rp->eh = NULL;
}
+ GNUNET_CONTAINER_DLL_remove (rp_head,
+ rp_tail,
+ rp);
GNUNET_free (rp);
}
@@ -137,26 +151,17 @@ db_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
- struct TEH_RequestContext *rc = cls;
- struct ReservePoller *rp = rc->rh_ctx;
+ struct ReservePoller *rp = cls;
struct GNUNET_AsyncScopeSave old_scope;
(void) extra;
(void) extra_size;
- if (NULL == rp)
- return; /* event triggered while main transaction
- was still running */
if (! rp->suspended)
return; /* might get multiple wake-up events */
- rp->suspended = false;
- GNUNET_async_scope_enter (&rc->async_scope_id,
+ GNUNET_async_scope_enter (&rp->rc->async_scope_id,
&old_scope);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming from long-polling on reserve\n");
TEH_check_invariants ();
- GNUNET_CONTAINER_DLL_remove (rp_head,
- rp_tail,
- rp);
+ rp->suspended = false;
MHD_resume_connection (rp->connection);
TALER_MHD_daemon_trigger ();
TEH_check_invariants ();
@@ -164,191 +169,95 @@ db_event_cb (void *cls,
}
-/**
- * Closure for #reserve_history_transaction.
- */
-struct ReserveHistoryContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Balance of the reserve, set in the callback.
- */
- struct TALER_Amount balance;
-
- /**
- * Set to true if we did not find the reserve.
- */
- bool not_found;
-};
-
-
-/**
- * Function implementing /reserves/ GET transaction.
- * Execute a /reserves/ GET. Given the public key of a reserve,
- * return the associated transaction history. 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.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @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
-reserve_balance_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveHistoryContext *rsc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
- &rsc->reserve_pub,
- &rsc->balance);
- 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,
- "get_reserve_balance");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- rsc->not_found = true;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- rsc->not_found = false;
- return qs;
-}
-
-
MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
- const char *const args[1])
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
{
- struct ReserveHistoryContext rsc;
- struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
- struct GNUNET_DB_EventHandler *eh = NULL;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &rsc.reserve_pub,
- sizeof (rsc.reserve_pub)))
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ if (NULL == rp)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
- args[0]);
+ rp = GNUNET_new (struct ReservePoller);
+ rp->connection = rc->connection;
+ rp->rc = rc;
+ rc->rh_ctx = rp;
+ rc->rh_cleaner = &rp_cleanup;
+ GNUNET_CONTAINER_DLL_insert (rp_head,
+ rp_tail,
+ rp);
+ rp->reserve_pub = *reserve_pub;
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &rp->timeout);
}
- {
- const char *long_poll_timeout_ms;
-
- long_poll_timeout_ms
- = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_ms)
- {
- unsigned int timeout_ms;
- char dummy;
- if (1 != sscanf (long_poll_timeout_ms,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms (must be non-negative number)");
- }
- timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- }
- }
- if ( (! GNUNET_TIME_relative_is_zero (timeout)) &&
- (NULL == rc->rh_ctx) )
+ if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
+ (NULL == rp->eh) )
{
struct TALER_ReserveEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
- .reserve_pub = rsc.reserve_pub
+ .reserve_pub = rp->reserve_pub
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening\n");
- eh = TEH_plugin->event_listen (TEH_plugin->cls,
- timeout,
- &rep.header,
- &db_event_cb,
- rc);
+ "Starting DB event listening until %s\n",
+ GNUNET_TIME_absolute2s (rp->timeout));
+ rp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (rp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rp);
}
{
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "get reserve balance",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_balance_transaction,
- &rsc))
- {
- if (NULL != eh)
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- eh);
- return mhd_ret;
- }
- }
- /* generate proper response */
- if (rsc.not_found)
- {
- struct ReservePoller *rp = rc->rh_ctx;
+ enum GNUNET_DB_QueryStatus qs;
- if ( (NULL != rp) ||
- (GNUNET_TIME_relative_is_zero (timeout)) )
+ qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
+ &rp->reserve_pub,
+ &rp->balance);
+ switch (qs)
{
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0); /* single-shot query should never have soft-errors */
return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
- args[0]);
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got reserve balance of %s\n",
+ TALER_amount2s (&rp->balance));
+ return TALER_MHD_REPLY_JSON_PACK (rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("balance",
+ &rp->balance));
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (! GNUNET_TIME_absolute_is_future (rp->timeout))
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Long-polling on reserve for %s\n",
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_remaining (rp->timeout),
+ true));
+ rp->suspended = true;
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long-polling on reserve for %s\n",
- GNUNET_STRINGS_relative_time_to_string (timeout,
- GNUNET_YES));
- rp = GNUNET_new (struct ReservePoller);
- rp->connection = rc->connection;
- rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
- rp->eh = eh;
- rc->rh_ctx = rp;
- rc->rh_cleaner = &rp_cleanup;
- rp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (rp_head,
- rp_tail,
- rp);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
}
- if (NULL != eh)
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- eh);
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("balance",
- &rsc.balance));
+ GNUNET_break (0);
+ return MHD_NO;
}
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h b/src/exchange/taler-exchange-httpd_reserves_get.h
index 30c6559f6..6c453d0cd 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.h
+++ b/src/exchange/taler-exchange-httpd_reserves_get.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
+ 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
@@ -43,11 +43,12 @@ TEH_reserves_get_cleanup (void);
* status of the reserve.
*
* @param rc request context
- * @param args array of additional options (length: 1, just the reserve_pub)
+ * @param reserve_pub public key of the reserve
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
- const char *const args[1]);
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c
index aa0f26a4a..ae983682a 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get_attest.c
+++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c
@@ -51,11 +51,6 @@ struct ReserveAttestContext
json_t *attributes;
/**
- * Error code encountered in interaction with KYC provider.
- */
- enum TALER_ErrorCode ec;
-
- /**
* Set to true if we did not find the reserve.
*/
bool not_found;
@@ -67,53 +62,52 @@ struct ReserveAttestContext
* legitimization processes for the given user.
*
* @param cls our `struct ReserveAttestContext *`
- * @param provider_section KYC provider configuration section
- * @param provider_user_id UID at a provider (can be NULL)
- * @param legi_id legitimization process ID (can be NULL)
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
*/
static void
kyc_process_cb (void *cls,
+ const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
- const char *provider_user_id,
- const char *legi_id)
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
{
struct ReserveAttestContext *rsc = cls;
- struct GNUNET_TIME_Timestamp etime;
json_t *attrs;
+ json_t *val;
+ const char *name;
- rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
- provider_user_id,
- legi_id,
- &etime,
- &attrs);
- if (TALER_EC_NONE != rsc->ec)
+ if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
return;
-
+ attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ json_object_foreach (attrs, name, val)
{
- json_t *val;
- const char *name;
+ bool duplicate = false;
+ size_t idx;
+ json_t *str;
- json_object_foreach (attrs, name, val)
+ json_array_foreach (rsc->attributes, idx, str)
{
- bool duplicate = false;
- size_t idx;
- json_t *str;
-
- json_array_foreach (rsc->attributes, idx, str)
+ if (0 == strcmp (json_string_value (str),
+ name))
{
- if (0 == strcmp (json_string_value (str),
- name))
- {
- duplicate = true;
- break;
- }
+ duplicate = true;
+ break;
}
- if (duplicate)
- continue;
- GNUNET_assert (0 ==
- json_array_append (rsc->attributes,
- json_string (name)));
}
+ if (duplicate)
+ continue;
+ GNUNET_assert (0 ==
+ json_array_append (rsc->attributes,
+ json_string (name)));
}
}
@@ -144,7 +138,7 @@ reserve_attest_transaction (void *cls,
rsc->attributes = json_array ();
GNUNET_assert (NULL != rsc->attributes);
- qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
&rsc->h_payto,
&kyc_process_cb,
rsc);
@@ -213,6 +207,7 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
&rsc))
{
json_decref (rsc.attributes);
+ rsc.attributes = NULL;
return mhd_ret;
}
}
@@ -220,23 +215,17 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
if (rsc.not_found)
{
json_decref (rsc.attributes);
+ rsc.attributes = NULL;
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
args[0]);
}
- if (TALER_EC_NONE != rsc.ec)
- {
- json_decref (rsc.attributes);
- return TALER_MHD_reply_with_ec (rc->connection,
- rsc.ec,
- NULL);
- }
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
- GNUNET_JSON_pack_object_steal ("attributes",
- rsc.attributes));
+ GNUNET_JSON_pack_array_steal ("details",
+ rsc.attributes));
}
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c b/src/exchange/taler-exchange-httpd_reserves_history.c
index ffdc6eaa4..056d4b0ef 100644
--- a/src/exchange/taler-exchange-httpd_reserves_history.c
+++ b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -15,7 +15,7 @@
*/
/**
* @file taler-exchange-httpd_reserves_history.c
- * @brief Handle /reserves/$RESERVE_PUB/history requests
+ * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
@@ -23,7 +23,6 @@
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
-#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
#include "taler_dbevents.h"
#include "taler-exchange-httpd_keys.h"
@@ -32,263 +31,486 @@
/**
- * How far do we allow a client's time to be off when
- * checking the request timestamp?
+ * Compile the history of a reserve into a JSON object.
+ *
+ * @param rh reserve history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
*/
-#define TIMESTAMP_TOLERANCE \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+static json_t *
+compile_reserve_history (
+ const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+ json_t *json_history;
+ json_history = json_array ();
+ GNUNET_assert (NULL != json_history);
+ for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+ {
+ const struct TALER_EXCHANGEDB_BankTransfer *bank =
+ pos->details.bank;
-/**
- * Closure for #reserve_history_transaction.
- */
-struct ReserveHistoryContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * Timestamp of the request.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Client signature approving the request.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * History of the reserve, set in the callback.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
- /**
- * Global fees applying to the request.
- */
- const struct TEH_GlobalFee *gf;
-
- /**
- * Current reserve balance.
- */
- struct TALER_Amount balance;
-};
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CREDIT"),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ bank->execution_date),
+ GNUNET_JSON_pack_string ("sender_account_url",
+ bank->sender_account_details),
+ GNUNET_JSON_pack_uint64 ("wire_reference",
+ bank->wire_reference),
+ TALER_JSON_pack_amount ("amount",
+ &bank->amount))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
+ {
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw
+ = pos->details.withdraw;
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "WITHDRAW"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &withdraw->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("h_coin_envelope",
+ &withdraw->h_coin_envelope),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &withdraw->denom_pub_hash),
+ TALER_JSON_pack_amount ("withdraw_fee",
+ &withdraw->withdraw_fee),
+ TALER_JSON_pack_amount ("amount",
+ &withdraw->amount_with_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+ {
+ const struct TALER_EXCHANGEDB_Recoup *recoup
+ = pos->details.recoup;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
-/**
- * Send reserve history to client.
- *
- * @param connection connection to the client
- * @param rhc reserve history to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_history_success (struct MHD_Connection *connection,
- const struct ReserveHistoryContext *rhc)
-{
- const struct TALER_EXCHANGEDB_ReserveHistory *rh = rhc->rh;
- json_t *json_history;
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ &recoup->coin.coin_pub,
+ &recoup->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP"),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &recoup->coin.coin_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+ {
+ const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
+ pos->details.closing;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_reserve_closed_sign (
+ &TEH_keys_exchange_sign_,
+ closing->execution_date,
+ &closing->amount,
+ &closing->closing_fee,
+ closing->receiver_account_details,
+ &closing->wtid,
+ &pos->details.closing->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSING"),
+ GNUNET_JSON_pack_string ("receiver_account_details",
+ closing->receiver_account_details),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &closing->wtid),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ closing->execution_date),
+ TALER_JSON_pack_amount ("amount",
+ &closing->amount),
+ TALER_JSON_pack_amount ("closing_fee",
+ &closing->closing_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ const struct TALER_EXCHANGEDB_PurseMerge *merge =
+ pos->details.merge;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MERGE"),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &merge->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge->merge_pub),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ merge->min_age),
+ GNUNET_JSON_pack_uint64 ("flags",
+ merge->flags),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &merge->purse_pub),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &merge->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge->merge_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ merge->purse_expiration),
+ TALER_JSON_pack_amount ("purse_fee",
+ &merge->purse_fee),
+ TALER_JSON_pack_amount ("amount",
+ &merge->amount_with_fee),
+ GNUNET_JSON_pack_bool ("merged",
+ merge->merged))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_HistoryRequest *history =
+ pos->details.history;
- json_history = TEH_RESPONSE_compile_reserve_history (rh);
- if (NULL == json_history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("balance",
- &rhc->balance),
- GNUNET_JSON_pack_array_steal ("history",
- json_history));
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "HISTORY"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &history->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ history->request_timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &history->history_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_OpenRequest *orq =
+ pos->details.open_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "OPEN"),
+ GNUNET_JSON_pack_uint64 ("requested_min_purses",
+ orq->purse_limit),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &orq->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ orq->request_timestamp),
+ GNUNET_JSON_pack_timestamp ("requested_expiration",
+ orq->reserve_expiration),
+ TALER_JSON_pack_amount ("open_fee",
+ &orq->open_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_CloseRequest *crq =
+ pos->details.close_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSE"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &crq->reserve_sig),
+ GNUNET_is_zero (&crq->target_account_h_payto)
+ ? GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("h_payto",
+ NULL))
+ : GNUNET_JSON_pack_data_auto ("h_payto",
+ &crq->target_account_h_payto),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ crq->request_timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ }
+ }
+
+ return json_history;
}
/**
- * Function implementing /reserves/$RID/history transaction. Given the public
- * key of a reserve, return the associated transaction history. 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.
+ * Add the headers we want to set for every /keys response.
*
- * @param cls a `struct ReserveHistoryContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
*/
-static enum GNUNET_DB_QueryStatus
-reserve_history_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ TALER_MHD_add_global_headers (response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
{
- struct ReserveHistoryContext *rsc = cls;
- enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
+ uint64_t start_off = 0;
+ struct TALER_Amount balance;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
- if (! TALER_amount_is_zero (&rsc->gf->fees.history))
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
{
- bool balance_ok = false;
- bool idempotent = true;
-
- qs = TEH_plugin->insert_history_request (TEH_plugin->cls,
- rsc->reserve_pub,
- &rsc->reserve_sig,
- rsc->timestamp,
- &rsc->gf->fees.history,
- &balance_ok,
- &idempotent);
- 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,
- "get_reserve_history");
- }
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- if (! balance_ok)
+ struct TALER_ReserveSignatureP reserve_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+ &reserve_sig,
+ required);
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_history_verify (start_off,
+ reserve_pub,
+ &reserve_sig))
{
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_GET_RESERVE_HISTORY_ERROR_INSUFFICIENT_BALANCE,
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
NULL);
}
- if (idempotent)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Idempotent /reserves/history request observed. Is caching working?\n");
- }
}
- qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
- rsc->reserve_pub,
- &rsc->balance,
- &rsc->rh);
- 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,
- "get_reserve_history");
- }
- return qs;
-}
-
-MHD_RESULT
-TEH_handler_reserves_history (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root)
-{
- struct ReserveHistoryContext rsc;
- MHD_RESULT mhd_ret;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rsc.timestamp),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rsc.reserve_sig),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_TIME_Timestamp now;
-
- rsc.reserve_pub = reserve_pub;
+ /* Get etag */
{
- enum GNUNET_GenericReturnValue res;
+ const char *etags;
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
{
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = 0;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
}
- if (GNUNET_NO == res)
+ else
{
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
+ etag_in = start_off;
}
}
- now = GNUNET_TIME_timestamp_get ();
- if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
- rsc.timestamp.abs_time,
- TIMESTAMP_TOLERANCE))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
- NULL);
- }
+
{
- struct TEH_KeyStateHandle *keys;
+ enum GNUNET_DB_QueryStatus qs;
- keys = TEH_keys_get_state ();
- if (NULL == keys)
+ qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
+ reserve_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &rh);
+ switch (qs)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
}
- rsc.gf = TEH_keys_global_fee_by_time (keys,
- rsc.timestamp);
}
- if (NULL == rsc.gf)
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
{
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- NULL);
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
}
- if (GNUNET_OK !=
- TALER_wallet_reserve_history_verify (rsc.timestamp,
- &rsc.gf->fees.history,
- reserve_pub,
- &rsc.reserve_sig))
+ if (NULL == rh)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE,
- NULL);
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ http_status = MHD_HTTP_NO_CONTENT;
}
- rsc.rh = NULL;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "get reserve history",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_history_transaction,
- &rsc))
+ else
{
- return mhd_ret;
+ json_t *history;
+
+ http_status = MHD_HTTP_OK;
+ history = compile_reserve_history (rh);
+ TEH_plugin->free_reserve_history (TEH_plugin->cls,
+ rh);
+ rh = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
}
- if (NULL == rsc.rh)
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
{
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
- NULL);
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
}
- mhd_ret = reply_reserve_history_success (rc->connection,
- &rsc);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rsc.rh);
- return mhd_ret;
}
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h b/src/exchange/taler-exchange-httpd_reserves_history.h
index e02cb4d9b..e1bd7ae1b 100644
--- a/src/exchange/taler-exchange-httpd_reserves_history.h
+++ b/src/exchange/taler-exchange-httpd_reserves_history.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2020 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
@@ -17,26 +17,27 @@
* @file taler-exchange-httpd_reserves_history.h
* @brief Handle /reserves/$RESERVE_PUB/history requests
* @author Florian Dold
+ * @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
#define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
#include <microhttpd.h>
+#include "taler_mhd_lib.h"
#include "taler-exchange-httpd.h"
/**
- * Handle a POST "/reserves/$RID/history" request.
+ * Handle a GET "/reserves/$RID/history" request.
*
* @param rc request context
* @param reserve_pub public key of the reserve
- * @param root uploaded body from the client
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_reserves_history (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c
index 6909c862a..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;
@@ -303,7 +306,7 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc,
const json_t *root)
{
struct ReserveOpenContext rsc;
- json_t *payments;
+ const json_t *payments;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("request_timestamp",
&rsc.timestamp),
@@ -313,8 +316,8 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc,
&rsc.reserve_sig),
GNUNET_JSON_spec_uint32 ("purse_limit",
&rsc.purse_limit),
- GNUNET_JSON_spec_json ("payments",
- &payments),
+ GNUNET_JSON_spec_array_const ("payments",
+ &payments),
TALER_JSON_spec_amount ("reserve_payment",
TEH_currency,
&rsc.reserve_payment),
@@ -403,7 +406,6 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc,
if (NULL == keys)
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
cleanup_rsc (&rsc);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.c b/src/exchange/taler-exchange-httpd_reserves_purse.c
index cc3ffeb15..5e06db206 100644
--- a/src/exchange/taler-exchange-httpd_reserves_purse.c
+++ b/src/exchange/taler-exchange-httpd_reserves_purse.c
@@ -189,24 +189,48 @@ purse_transaction (void *cls,
{
struct ReservePurseContext *rpc = cls;
enum GNUNET_DB_QueryStatus qs;
+ char *required;
- const char *required;
-
- required = TALER_KYCLOGIC_kyc_test_required (
+ qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
&rpc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_iterator,
- rpc);
+ rpc,
+ &required);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return 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 GNUNET_DB_STATUS_HARD_ERROR;
+ }
if (NULL != required)
{
rpc->kyc.ok = false;
- return TEH_plugin->insert_kyc_requirement_for_account (
+ qs = TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
required,
&rpc->h_payto,
+ rpc->reserve_pub,
&rpc->kyc.requirement_row);
+ GNUNET_free (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;
}
rpc->kyc.ok = true;
@@ -230,8 +254,7 @@ purse_transaction (void *cls,
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
- TALER_LOG_WARNING (
- "Failed to store purse purse information in database\n");
+ GNUNET_break (0);
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
@@ -340,6 +363,7 @@ purse_transaction (void *cls,
struct GNUNET_TIME_Timestamp merge_timestamp;
char *partner_url;
struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
TEH_plugin->rollback (TEH_plugin->cls);
qs = TEH_plugin->select_purse_merge (
@@ -348,7 +372,8 @@ purse_transaction (void *cls,
&merge_sig,
&merge_timestamp,
&partner_url,
- &reserve_pub);
+ &reserve_pub,
+ &refunded);
if (qs <= 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
@@ -361,6 +386,18 @@ purse_transaction (void *cls,
"select purse merge");
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ if (refunded)
+ {
+ /* This is a bit of a strange case ... */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
*mhd_ret
= TALER_MHD_REPLY_JSON_PACK (
connection,
diff --git a/src/exchange/taler-exchange-httpd_reserves_status.c b/src/exchange/taler-exchange-httpd_reserves_status.c
deleted file mode 100644
index 4e7b4f47c..000000000
--- a/src/exchange/taler-exchange-httpd_reserves_status.c
+++ /dev/null
@@ -1,243 +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 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_reserves_status.c
- * @brief Handle /reserves/$RESERVE_PUB STATUS requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler_mhd_lib.h"
-#include "taler_json_lib.h"
-#include "taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_status.h"
-#include "taler-exchange-httpd_responses.h"
-
-/**
- * How far do we allow a client's time to be off when
- * checking the request timestamp?
- */
-#define TIMESTAMP_TOLERANCE \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
-
-
-/**
- * Closure for #reserve_status_transaction.
- */
-struct ReserveStatusContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * History of the reserve, set in the callback.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
- /**
- * Sum of incoming transactions within the returned history.
- * (currently not used).
- */
- struct TALER_Amount balance_in;
-
- /**
- * Sum of outgoing transactions within the returned history.
- * (currently not used).
- */
- struct TALER_Amount balance_out;
-
- /**
- * Current reserve balance.
- */
- struct TALER_Amount balance;
-};
-
-
-/**
- * Send reserve status to client.
- *
- * @param connection connection to the client
- * @param rhc reserve history to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_status_success (struct MHD_Connection *connection,
- const struct ReserveStatusContext *rhc)
-{
- const struct TALER_EXCHANGEDB_ReserveHistory *rh = rhc->rh;
- json_t *json_history;
-
- json_history = TEH_RESPONSE_compile_reserve_history (rh);
- if (NULL == json_history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("balance",
- &rhc->balance),
- GNUNET_JSON_pack_array_steal ("history",
- json_history));
-}
-
-
-/**
- * Function implementing /reserves/ STATUS transaction.
- * Execute a /reserves/ STATUS. Given the public key of a reserve,
- * return the associated transaction history. 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.
- *
- * @param cls a `struct ReserveStatusContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserve_status_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveStatusContext *rsc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_reserve_status (TEH_plugin->cls,
- rsc->reserve_pub,
- &rsc->balance_in,
- &rsc->balance_out,
- &rsc->rh);
- 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,
- "get_reserve_status");
- }
- qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
- rsc->reserve_pub,
- &rsc->balance);
- 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,
- "get_reserve_balance");
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_status (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root)
-{
- struct ReserveStatusContext rsc;
- MHD_RESULT mhd_ret;
- struct GNUNET_TIME_Timestamp timestamp;
- struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &timestamp),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &reserve_sig),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_TIME_Timestamp now;
-
- rsc.reserve_pub = reserve_pub;
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- now = GNUNET_TIME_timestamp_get ();
- if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
- timestamp.abs_time,
- TIMESTAMP_TOLERANCE))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
- NULL);
- }
- if (GNUNET_OK !=
- TALER_wallet_reserve_status_verify (timestamp,
- reserve_pub,
- &reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE,
- NULL);
- }
- rsc.rh = NULL;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "get reserve status",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_status_transaction,
- &rsc))
- {
- return mhd_ret;
- }
- if (NULL == rsc.rh)
- {
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
- NULL);
- }
- mhd_ret = reply_reserve_status_success (rc->connection,
- &rsc);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rsc.rh);
- return mhd_ret;
-}
-
-
-/* end of taler-exchange-httpd_reserves_status.c */
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 3a42ba756..8993ea50f 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -23,494 +23,17 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <microhttpd.h>
#include <zlib.h>
#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_plugin.h"
#include "taler_util.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_keys.h"
-/**
- * Compile the transaction history of a coin into a JSON object.
- *
- * @param coin_pub public key of the coin
- * @param tl transaction history to JSON-ify
- * @return json representation of the @a rh, NULL on error
- */
-json_t *
-TEH_RESPONSE_compile_transaction_history (
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl)
-{
- json_t *history;
-
- history = json_array ();
- if (NULL == history)
- {
- GNUNET_break (0); /* out of memory!? */
- return NULL;
- }
- for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
- NULL != pos;
- pos = pos->next)
- {
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- {
- const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
- pos->details.deposit;
- struct TALER_MerchantWireHashP h_wire;
-
- TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
- &deposit->wire_salt,
- &h_wire);
-#if ENABLE_SANITY_CHECKS
- /* internal sanity check before we hand out a bogus sig... */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (
- &deposit->amount_with_fee,
- &deposit->deposit_fee,
- &h_wire,
- &deposit->h_contract_terms,
- &deposit->h_age_commitment,
- &deposit->h_policy,
- &deposit->h_denom_pub,
- deposit->timestamp,
- &deposit->merchant_pub,
- deposit->refund_deadline,
- coin_pub,
- &deposit->csig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "DEPOSIT"),
- TALER_JSON_pack_amount ("amount",
- &deposit->amount_with_fee),
- TALER_JSON_pack_amount ("deposit_fee",
- &deposit->deposit_fee),
- GNUNET_JSON_pack_timestamp ("timestamp",
- deposit->timestamp),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- deposit->refund_deadline)),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &deposit->merchant_pub),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &deposit->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_pack_allow_null (
- deposit->no_age_commitment ?
- GNUNET_JSON_pack_string (
- "h_age_commitment", NULL) :
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- &deposit->h_age_commitment)),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &deposit->csig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_MELT:
- {
- const struct TALER_EXCHANGEDB_MeltListEntry *melt =
- pos->details.melt;
- const struct TALER_AgeCommitmentHash *phac = NULL;
-
-#if ENABLE_SANITY_CHECKS
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- &melt->amount_with_fee,
- &melt->melt_fee,
- &melt->rc,
- &melt->h_denom_pub,
- &melt->h_age_commitment,
- coin_pub,
- &melt->coin_sig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
-
- /* Age restriction is optional. We communicate a NULL value to
- * JSON_PACK below */
- if (! melt->no_age_commitment)
- phac = &melt->h_age_commitment;
-
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "MELT"),
- TALER_JSON_pack_amount ("amount",
- &melt->amount_with_fee),
- TALER_JSON_pack_amount ("melt_fee",
- &melt->melt_fee),
- GNUNET_JSON_pack_data_auto ("rc",
- &melt->rc),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- phac)),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &melt->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- {
- const struct TALER_EXCHANGEDB_RefundListEntry *refund =
- pos->details.refund;
- struct TALER_Amount value;
-
-#if ENABLE_SANITY_CHECKS
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (
- coin_pub,
- &refund->h_contract_terms,
- refund->rtransaction_id,
- &refund->refund_amount,
- &refund->merchant_pub,
- &refund->merchant_sig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 >
- TALER_amount_subtract (&value,
- &refund->refund_amount,
- &refund->refund_fee))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "REFUND"),
- TALER_JSON_pack_amount ("amount",
- &value),
- TALER_JSON_pack_amount ("refund_fee",
- &refund->refund_fee),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &refund->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &refund->merchant_pub),
- GNUNET_JSON_pack_uint64 ("rtransaction_id",
- refund->rtransaction_id),
- GNUNET_JSON_pack_data_auto ("merchant_sig",
- &refund->merchant_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.old_coin_recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_refresh_sign (
- &TEH_keys_exchange_sign_,
- pr->timestamp,
- &pr->value,
- &pr->coin.coin_pub,
- &pr->old_coin_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
- the denomination key's RSA signature over coin_pub, but as the
- wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. */
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "OLD-COIN-RECOUP"),
- TALER_JSON_pack_amount ("amount",
- &pr->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &pr->coin.coin_pub),
- GNUNET_JSON_pack_timestamp ("timestamp",
- pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_RECOUP:
- {
- const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
- pos->details.recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_sign (
- &TEH_keys_exchange_sign_,
- recoup->timestamp,
- &recoup->value,
- coin_pub,
- &recoup->reserve_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP"),
- TALER_JSON_pack_amount ("amount",
- &recoup->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &recoup->coin_sig),
- GNUNET_JSON_pack_data_auto ("coin_blind",
- &recoup->coin_blind),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_JSON_pack_timestamp ("timestamp",
- recoup->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.recoup_refresh;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_refresh_sign (
- &TEH_keys_exchange_sign_,
- pr->timestamp,
- &pr->value,
- coin_pub,
- &pr->old_coin_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key
- hash and the denomination key's RSA signature over coin_pub, but as
- the wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. */
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP-REFRESH"),
- TALER_JSON_pack_amount ("amount",
- &pr->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("old_coin_pub",
- &pr->old_coin_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &pr->coin_sig),
- GNUNET_JSON_pack_data_auto ("coin_blind",
- &pr->coin_blind),
- GNUNET_JSON_pack_timestamp ("timestamp",
- pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
-
- case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
- {
- struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
- = pos->details.purse_deposit;
- const struct TALER_AgeCommitmentHash *phac = NULL;
-
- if (! pd->no_age_commitment)
- phac = &pd->h_age_commitment;
-
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "PURSE-DEPOSIT"),
- TALER_JSON_pack_amount ("amount",
- &pd->amount),
- GNUNET_JSON_pack_string ("exchange_base_url",
- NULL == pd->exchange_base_url
- ? TEH_base_url
- : pd->exchange_base_url),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- phac)),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &pd->purse_pub),
- GNUNET_JSON_pack_bool ("refunded",
- pd->refunded),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &pd->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
-
- case TALER_EXCHANGEDB_TT_PURSE_REFUND:
- {
- const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
- pos->details.purse_refund;
- struct TALER_Amount value;
- enum TALER_ErrorCode ec;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (0 >
- TALER_amount_subtract (&value,
- &prefund->refund_amount,
- &prefund->refund_fee))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- ec = TALER_exchange_online_purse_refund_sign (
- &TEH_keys_exchange_sign_,
- &value,
- &prefund->refund_fee,
- coin_pub,
- &prefund->purse_pub,
- &epub,
- &esig);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "PURSE-REFUND"),
- TALER_JSON_pack_amount ("amount",
- &value),
- TALER_JSON_pack_amount ("refund_fee",
- &prefund->refund_fee),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &prefund->purse_pub))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
-
- case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
- {
- struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
- = pos->details.reserve_open;
-
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RESERVE-OPEN-DEPOSIT"),
- TALER_JSON_pack_amount ("coin_contribution",
- &role->coin_contribution),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &role->reserve_sig),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &role->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- }
- }
- return history;
-}
-
-
MHD_RESULT
TEH_RESPONSE_reply_unknown_denom_pub_hash (
struct MHD_Connection *connection,
@@ -644,437 +167,131 @@ TEH_RESPONSE_reply_coin_insufficient_funds (
const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
- struct TALER_EXCHANGEDB_TransactionList *tl;
- enum GNUNET_DB_QueryStatus qs;
- json_t *history;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- if (GNUNET_OK !=
- TEH_plugin->start_read_only (TEH_plugin->cls,
- "get_coin_transactions"))
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- }
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- coin_pub,
- &tl);
- TEH_plugin->rollback (TEH_plugin->cls);
- if (0 > qs)
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- }
-
- history = TEH_RESPONSE_compile_transaction_history (coin_pub,
- tl);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- if (NULL == history)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- "Failed to generated proof of insufficient funds");
- }
return TALER_MHD_REPLY_JSON_PACK (
connection,
TALER_ErrorCode_get_http_status_safe (ec),
TALER_JSON_pack_ec (ec),
GNUNET_JSON_pack_data_auto ("coin_pub",
coin_pub),
+ // FIXME - #7267: to be kept only for some of the error types!
GNUNET_JSON_pack_data_auto ("h_denom_pub",
- h_denom_pub),
- GNUNET_JSON_pack_array_steal ("history",
- history));
+ h_denom_pub));
}
-json_t *
-TEH_RESPONSE_compile_reserve_history (
- const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+MHD_RESULT
+TEH_RESPONSE_reply_coin_conflicting_contract (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantWireHashP *h_wire)
{
- json_t *json_history;
-
- json_history = json_array ();
- for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
- NULL != pos;
- pos = pos->next)
- {
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
- {
- const struct TALER_EXCHANGEDB_BankTransfer *bank =
- pos->details.bank;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CREDIT"),
- GNUNET_JSON_pack_timestamp ("timestamp",
- bank->execution_date),
- GNUNET_JSON_pack_string ("sender_account_url",
- bank->sender_account_details),
- GNUNET_JSON_pack_uint64 ("wire_reference",
- bank->wire_reference),
- TALER_JSON_pack_amount ("amount",
- &bank->amount))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
- {
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw
- = pos->details.withdraw;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "WITHDRAW"),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &withdraw->reserve_sig),
- GNUNET_JSON_pack_data_auto ("h_coin_envelope",
- &withdraw->h_coin_envelope),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &withdraw->denom_pub_hash),
- TALER_JSON_pack_amount ("withdraw_fee",
- &withdraw->withdraw_fee),
- TALER_JSON_pack_amount ("amount",
- &withdraw->amount_with_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_RECOUP_COIN:
- {
- const struct TALER_EXCHANGEDB_Recoup *recoup
- = pos->details.recoup;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_sign (
- &TEH_keys_exchange_sign_,
- recoup->timestamp,
- &recoup->value,
- &recoup->coin.coin_pub,
- &recoup->reserve_pub,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP"),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_timestamp ("timestamp",
- recoup->timestamp),
- TALER_JSON_pack_amount ("amount",
- &recoup->value),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &recoup->coin.coin_pub))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
- {
- const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
- pos->details.closing;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ h_wire),
+ TALER_JSON_pack_ec (ec));
+}
- if (TALER_EC_NONE !=
- TALER_exchange_online_reserve_closed_sign (
- &TEH_keys_exchange_sign_,
- closing->execution_date,
- &closing->amount,
- &closing->closing_fee,
- closing->receiver_account_details,
- &closing->wtid,
- &pos->details.closing->reserve_pub,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CLOSING"),
- GNUNET_JSON_pack_string ("receiver_account_details",
- closing->receiver_account_details),
- GNUNET_JSON_pack_data_auto ("wtid",
- &closing->wtid),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_timestamp ("timestamp",
- closing->execution_date),
- TALER_JSON_pack_amount ("amount",
- &closing->amount),
- TALER_JSON_pack_amount ("closing_fee",
- &closing->closing_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_PURSE_MERGE:
- {
- const struct TALER_EXCHANGEDB_PurseMerge *merge =
- pos->details.merge;
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "MERGE"),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &merge->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &merge->merge_pub),
- GNUNET_JSON_pack_uint64 ("min_age",
- merge->min_age),
- GNUNET_JSON_pack_uint64 ("flags",
- merge->flags),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &merge->purse_pub),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &merge->reserve_sig),
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge->merge_timestamp),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- merge->purse_expiration),
- TALER_JSON_pack_amount ("amount",
- &merge->amount_with_fee),
- TALER_JSON_pack_amount ("purse_fee",
- &merge->purse_fee),
- GNUNET_JSON_pack_bool ("merged",
- merge->merged))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
- {
- const struct TALER_EXCHANGEDB_HistoryRequest *history =
- pos->details.history;
+MHD_RESULT
+TEH_RESPONSE_reply_coin_denomination_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *prev_denom_pub,
+ const struct TALER_DenominationSignature *prev_denom_sig)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ TALER_JSON_pack_denom_pub ("prev_denom_pub",
+ prev_denom_pub),
+ TALER_JSON_pack_denom_sig ("prev_denom_sig",
+ prev_denom_sig)
+ );
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "HISTORY"),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &history->reserve_sig),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- history->request_timestamp),
- TALER_JSON_pack_amount ("amount",
- &history->history_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
+}
- case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
- {
- const struct TALER_EXCHANGEDB_OpenRequest *orq =
- pos->details.open_request;
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "OPEN"),
- GNUNET_JSON_pack_uint64 ("requested_min_purses",
- orq->purse_limit),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &orq->reserve_sig),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- orq->request_timestamp),
- GNUNET_JSON_pack_timestamp ("requested_expiration",
- orq->reserve_expiration),
- TALER_JSON_pack_amount ("open_fee",
- &orq->open_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
+MHD_RESULT
+TEH_RESPONSE_reply_coin_age_commitment_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ enum TALER_EXCHANGEDB_CoinKnownStatus status,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment)
+{
+ const char *conflict_detail;
- case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
- {
- const struct TALER_EXCHANGEDB_CloseRequest *crq =
- pos->details.close_request;
+ switch (status)
+ {
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CLOSE"),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &crq->reserve_sig),
- GNUNET_is_zero (&crq->target_account_h_payto)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("h_payto",
- NULL))
- : GNUNET_JSON_pack_data_auto ("h_payto",
- &crq->target_account_h_payto),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- crq->request_timestamp))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- }
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL:
+ conflict_detail = "expected NULL age commitment hash";
+ h_age_commitment = NULL;
+ break;
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL:
+ conflict_detail = "expected non-NULL age commitment hash";
+ break;
+ case TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS:
+ conflict_detail = "expected age commitment hash differs";
+ break;
+ default:
+ GNUNET_assert (0);
}
- return json_history;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_JSON_pack_ec (ec),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ coin_pub),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("expected_age_commitment_hash",
+ h_age_commitment)),
+ GNUNET_JSON_pack_string ("conflict_detail",
+ conflict_detail)
+ );
}
-/**
- * Send reserve history information to client with the
- * message that we have insufficient funds for the
- * requested withdraw operation.
- *
- * @param connection connection to the client
- * @param ec error code to return
- * @param ebalance expected balance based on our database
- * @param withdraw_amount amount that the client requested to withdraw
- * @param rh reserve history to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_insufficient_funds (
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_insufficient_balance (
struct MHD_Connection *connection,
enum TALER_ErrorCode ec,
- const struct TALER_Amount *ebalance,
- const struct TALER_Amount *withdraw_amount,
- const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+ const struct TALER_Amount *reserve_balance,
+ const struct TALER_Amount *balance_required,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
{
- json_t *json_history;
-
- json_history = TEH_RESPONSE_compile_reserve_history (rh);
- if (NULL == json_history)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_RESERVE_HISTORY_ERROR_INSUFFICIENT_FUNDS,
- NULL);
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_CONFLICT,
TALER_JSON_pack_ec (ec),
TALER_JSON_pack_amount ("balance",
- ebalance),
+ reserve_balance),
TALER_JSON_pack_amount ("requested_amount",
- withdraw_amount),
- GNUNET_JSON_pack_array_steal ("history",
- json_history));
+ balance_required));
}
MHD_RESULT
-TEH_RESPONSE_reply_reserve_insufficient_balance (
+TEH_RESPONSE_reply_reserve_age_restriction_required (
struct MHD_Connection *connection,
- enum TALER_ErrorCode ec,
- const struct TALER_Amount *balance_required,
- const struct TALER_ReservePublicKeyP *reserve_pub)
+ uint16_t maximum_allowed_age)
{
- struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
- struct TALER_Amount balance;
- enum GNUNET_DB_QueryStatus qs;
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_plugin->start_read_only (TEH_plugin->cls,
- "get_reserve_history on insufficient balance"))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- }
- /* The reserve does not have the required amount (actual
- * amount + withdraw fee) */
- qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
- reserve_pub,
- &balance,
- &rh);
- TEH_plugin->rollback (TEH_plugin->cls);
- if ( (qs < 0) ||
- (NULL == rh) )
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "reserve history");
- }
- mhd_ret = reply_reserve_insufficient_funds (
+ return TALER_MHD_REPLY_JSON_PACK (
connection,
- ec,
- &balance,
- balance_required,
- rh);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rh);
- return mhd_ret;
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED),
+ GNUNET_JSON_pack_uint64 ("maximum_allowed_age",
+ maximum_allowed_age));
}
@@ -1128,6 +345,7 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED),
GNUNET_JSON_pack_data_auto ("h_payto",
h_payto),
GNUNET_JSON_pack_uint64 ("requirement_row",
@@ -1135,4 +353,57 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
}
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+ enum TALER_AmlDecisionState status)
+{
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+
+ switch (status)
+ {
+ case TALER_AML_NORMAL:
+ GNUNET_break (0);
+ return MHD_NO;
+ case TALER_AML_PENDING:
+ ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING;
+ break;
+ case TALER_AML_FROZEN:
+ ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN;
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ TALER_JSON_pack_ec (ec));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_not_modified (
+ struct MHD_Connection *connection,
+ const char *etags,
+ TEH_RESPONSE_SetHeaders cb,
+ void *cb_cls)
+{
+ MHD_RESULT ret;
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ cb (cb_cls,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etags));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+}
+
+
/* end of taler-exchange-httpd_responses.c */
diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h
index ba6577b29..24b24621f 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -24,24 +24,14 @@
*/
#ifndef TALER_EXCHANGE_HTTPD_RESPONSES_H
#define TALER_EXCHANGE_HTTPD_RESPONSES_H
+
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler_error_codes.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_db.h"
-#include <gnunet/gnunet_mhd_compat.h>
-
-
-/**
- * Compile the history of a reserve into a JSON object.
- *
- * @param rh reserve history to JSON-ify
- * @return json representation of the @a rh, NULL on error
- */
-json_t *
-TEH_RESPONSE_compile_reserve_history (
- const struct TALER_EXCHANGEDB_ReserveHistory *rh);
+#include "taler_exchangedb_plugin.h"
/**
@@ -64,6 +54,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
@@ -72,9 +63,24 @@ 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);
+/**
+ * Return error message indicating that a reserve requires age
+ * restriction to be set during withdraw, that is: the age-withdraw
+ * protocol MUST be used with commitment to an admissible age.
+ *
+ * @param connection connection to the client
+ * @param maximum_allowed_age the balance required for the operation
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+ struct MHD_Connection *connection,
+ uint16_t maximum_allowed_age);
+
/**
* Send information that a KYC check must be
@@ -92,6 +98,19 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
/**
+ * Send information that an AML process is blocking
+ * the operation right now.
+ *
+ * @param connection connection to the client
+ * @param status current AML status
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+ enum TALER_AmlDecisionState status);
+
+
+/**
* Send assertion that the given denomination key hash
* is not usable (typically expired) at this time.
*
@@ -142,6 +161,68 @@ TEH_RESPONSE_reply_coin_insufficient_funds (
const struct TALER_CoinSpendPublicKeyP *coin_pub);
/**
+ * Send proof that a request is invalid to client because of
+ * an conflict with the provided denomination (the exchange had seen
+ * this coin before, signed by a different denomination).
+ * This function will create a message with the denomination's public key
+ * that was seen before.
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param coin_pub the public key of the coin
+ * @param prev_denom_pub the denomination of the coin, as seen previously
+ * @param prev_denom_sig the signature with the denomination key over the coin
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_denomination_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *prev_denom_pub,
+ const struct TALER_DenominationSignature *prev_denom_sig);
+
+/**
+ * Send the salted hash of the merchant's bank account from conflicting
+ * contract. With this information, the merchant's private key and
+ * the hash of the contract terms, the client can retrieve more details
+ * about the conflicting deposit
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param h_wire the salted hash of the merchant's bank account
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_conflicting_contract (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantWireHashP *h_wire);
+
+/**
+ * Send proof that a request is invalid to client because of
+ * a conflicting value for the age commitment hash of a coin.
+ * This function will create a message with the conflicting
+ * hash value for the age commitment of the given coin.
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param cks specific conflict type
+ * @param h_denom_pub hash of the denomination of the coin
+ * @param coin_pub public key of the coin
+ * @param h_age_commitment hash of the age commitment as found in the database
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_age_commitment_conflict (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ enum TALER_EXCHANGEDB_CoinKnownStatus cks,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment);
+
+/**
* Fundamental details about a purse.
*/
struct TEH_PurseDetails
@@ -187,16 +268,32 @@ TEH_RESPONSE_reply_purse_created (
/**
- * Compile the transaction history of a coin into a JSON object.
+ * Callback used to set headers in a response.
*
- * @param coin_pub public key of the coin
- * @param tl transaction history to JSON-ify
- * @return json representation of the @a rh, NULL on error
+ * @param cls closure
+ * @param[in,out] resp response to modify
*/
-json_t *
-TEH_RESPONSE_compile_transaction_history (
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl);
+typedef void
+(*TEH_RESPONSE_SetHeaders)(void *cls,
+ struct MHD_Response *resp);
+
+
+/**
+ * Generate a HTTP "Not modified" response with the
+ * given @a etags.
+ *
+ * @param connection connection to queue response on
+ * @param etags ETag header to set
+ * @param cb callback to modify response headers
+ * @param cb_cls closure for @a cb
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_not_modified (
+ struct MHD_Connection *connection,
+ const char *etags,
+ TEH_RESPONSE_SetHeaders cb,
+ void *cb_cls);
#endif
diff --git a/src/exchange/taler-exchange-httpd_spa.c b/src/exchange/taler-exchange-httpd_spa.c
new file mode 100644
index 000000000..60bed3d28
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_spa.c
@@ -0,0 +1,362 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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 EXCHANGEABILITY 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 taler-exchange-httpd_spa.c
+ * @brief logic to load the single page app (/)
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resource from the WebUi.
+ */
+struct WebuiFile
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct WebuiFile *prev;
+
+ /**
+ * Path this resource matches.
+ */
+ char *path;
+
+ /**
+ * SPA resource, compressed.
+ */
+ struct MHD_Response *zspa;
+
+ /**
+ * SPA resource, vanilla.
+ */
+ struct MHD_Response *spa;
+
+};
+
+
+/**
+ * Resources of the WebuUI, kept in a DLL.
+ */
+static struct WebuiFile *webui_head;
+
+/**
+ * Resources of the WebuUI, kept in a DLL.
+ */
+static struct WebuiFile *webui_tail;
+
+
+MHD_RESULT
+TEH_handler_spa (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct WebuiFile *w = NULL;
+ const char *infix = args[0];
+
+ if ( (NULL == infix) ||
+ (0 == strcmp (infix,
+ "")) )
+ infix = "index.html";
+ for (struct WebuiFile *pos = webui_head;
+ NULL != pos;
+ pos = pos->next)
+ if (0 == strcmp (infix,
+ pos->path))
+ {
+ w = pos;
+ break;
+ }
+ if (NULL == w)
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ if ( (MHD_YES ==
+ TALER_MHD_can_compress (rc->connection)) &&
+ (NULL != w->zspa) )
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ w->zspa);
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ w->spa);
+}
+
+
+/**
+ * Function called on each file to load for the WebUI.
+ *
+ * @param cls NULL
+ * @param dn name of the file to load
+ */
+static enum GNUNET_GenericReturnValue
+build_webui (void *cls,
+ const char *dn)
+{
+ static struct
+ {
+ const char *ext;
+ const char *mime;
+ } mime_map[] = {
+ {
+ .ext = "css",
+ .mime = "text/css"
+ },
+ {
+ .ext = "html",
+ .mime = "text/html"
+ },
+ {
+ .ext = "js",
+ .mime = "text/javascript"
+ },
+ {
+ .ext = "jpg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "jpeg",
+ .mime = "image/jpeg"
+ },
+ {
+ .ext = "png",
+ .mime = "image/png"
+ },
+ {
+ .ext = "svg",
+ .mime = "image/svg+xml"
+ },
+ {
+ .ext = NULL,
+ .mime = NULL
+ },
+ };
+ int fd;
+ struct stat sb;
+ struct MHD_Response *zspa = NULL;
+ struct MHD_Response *spa;
+ const char *ext;
+ const char *mime;
+
+ (void) cls;
+ /* finally open template */
+ fd = open (dn,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+
+ mime = NULL;
+ ext = strrchr (dn, '.');
+ if (NULL == ext)
+ {
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ ext++;
+ for (unsigned int i = 0; NULL != mime_map[i].ext; i++)
+ if (0 == strcasecmp (ext,
+ mime_map[i].ext))
+ {
+ mime = mime_map[i].mime;
+ break;
+ }
+
+ {
+ void *in;
+ ssize_t r;
+ size_t csize;
+
+ in = GNUNET_malloc_large (sb.st_size);
+ if (NULL == in)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ r = read (fd,
+ in,
+ sb.st_size);
+ if ( (-1 == r) ||
+ (sb.st_size != (size_t) r) )
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ dn);
+ GNUNET_free (in);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ csize = (size_t) r;
+ if (MHD_YES ==
+ TALER_MHD_body_compress (&in,
+ &csize))
+ {
+ zspa = MHD_create_response_from_buffer (csize,
+ in,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL != zspa)
+ {
+ if (MHD_NO ==
+ MHD_add_response_header (zspa,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (zspa);
+ zspa = NULL;
+ }
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (zspa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+ }
+ }
+ else
+ {
+ GNUNET_free (in);
+ }
+ }
+
+ spa = MHD_create_response_from_fd (sb.st_size,
+ fd);
+ if (NULL == spa)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ dn);
+ GNUNET_break (0 == close (fd));
+ if (NULL != zspa)
+ {
+ MHD_destroy_response (zspa);
+ zspa = NULL;
+ }
+ return GNUNET_SYSERR;
+ }
+ if (NULL != mime)
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (spa,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ mime));
+
+ {
+ struct WebuiFile *w;
+ const char *fn;
+
+ fn = strrchr (dn, '/');
+ GNUNET_assert (NULL != fn);
+ w = GNUNET_new (struct WebuiFile);
+ w->path = GNUNET_strdup (fn + 1);
+ w->spa = spa;
+ w->zspa = zspa;
+ GNUNET_CONTAINER_DLL_insert (webui_head,
+ webui_tail,
+ w);
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_spa_init ()
+{
+ char *dn;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%sexchange/spa/",
+ path);
+ GNUNET_free (path);
+ }
+
+ if (-1 ==
+ GNUNET_DISK_directory_scan (dn,
+ &build_webui,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load WebUI from `%s'\n",
+ dn);
+ GNUNET_free (dn);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (dn);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Nicely shut down.
+ */
+void __attribute__ ((destructor))
+get_spa_fini ()
+{
+ struct WebuiFile *w;
+
+ while (NULL != (w = webui_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (webui_head,
+ webui_tail,
+ w);
+ if (NULL != w->spa)
+ {
+ MHD_destroy_response (w->spa);
+ w->spa = NULL;
+ }
+ if (NULL != w->zspa)
+ {
+ MHD_destroy_response (w->zspa);
+ w->zspa = NULL;
+ }
+ GNUNET_free (w->path);
+ GNUNET_free (w);
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_spa.h b/src/exchange/taler-exchange-httpd_spa.h
new file mode 100644
index 000000000..4147a853b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_spa.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 EXCHANGEABILITY 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 taler-exchange-httpd_spa.h
+ * @brief logic to preload and serve static files
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_SPA_H
+#define TALER_EXCHANGE_HTTPD_SPA_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Return our single-page-app user interface (see contrib/wallet-core/).
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TEH_handler_spa (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Preload and compress SPA files.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_spa_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_transfers_get.c b/src/exchange/taler-exchange-httpd_transfers_get.c
index 2a6dc8776..18d96f955 100644
--- a/src/exchange/taler-exchange-httpd_transfers_get.c
+++ b/src/exchange/taler-exchange-httpd_transfers_get.c
@@ -59,14 +59,20 @@ struct AggregatedDepositDetail
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Total value of the coin in the deposit.
+ * Total value of the coin in the deposit (after
+ * refunds).
*/
struct TALER_Amount deposit_value;
/**
- * Fees charged by the exchange for the deposit of this coin.
+ * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds).
*/
struct TALER_Amount deposit_fee;
+
+ /**
+ * Total amount refunded for this coin.
+ */
+ struct TALER_Amount refund_total;
};
@@ -120,6 +126,13 @@ reply_transfer_details (struct MHD_Connection *connection,
&wdd_pos->h_contract_terms),
GNUNET_JSON_pack_data_auto ("coin_pub",
&wdd_pos->coin_pub),
+
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("refund_total",
+ TALER_amount_is_zero (
+ &wdd_pos->refund_total)
+ ? NULL
+ : &wdd_pos->refund_total)),
TALER_JSON_pack_amount ("deposit_value",
&wdd_pos->deposit_value),
TALER_JSON_pack_amount ("deposit_fee",
@@ -248,6 +261,31 @@ struct WtidTransactionContext
/**
+ * Callback that totals up the applicable refunds.
+ *
+ * @param cls a `struct TALER_Amount` where we keep the total
+ * @param amount_with_fee amount being refunded
+ */
+static enum GNUNET_GenericReturnValue
+add_refunds (void *cls,
+ const struct TALER_Amount *amount_with_fee)
+
+{
+ struct TALER_Amount *total = cls;
+
+ if (0 >
+ TALER_amount_add (total,
+ total,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Function called with the results of the lookup of the individual deposits
* that were aggregated for the given wire transfer.
*
@@ -260,29 +298,96 @@ struct WtidTransactionContext
* @param h_contract_terms which proposal was this payment about
* @param denom_pub denomination public key of the @a coin_pub (ignored)
* @param coin_pub which public key was this payment about
- * @param deposit_value amount contributed by this coin in total
+ * @param deposit_value amount contributed by this coin in total (including fee)
* @param deposit_fee deposit fee charged by exchange for this coin
*/
static void
-handle_deposit_data (void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const char *account_payto_uri,
- const struct TALER_PaytoHashP *h_payto,
- struct GNUNET_TIME_Timestamp exec_time,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee)
+handle_deposit_data (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee)
{
struct WtidTransactionContext *ctx = cls;
+ struct TALER_Amount total_refunds;
+ struct TALER_Amount dval;
+ struct TALER_Amount dfee;
+ enum GNUNET_DB_QueryStatus qs;
(void) rowid;
(void) denom_pub;
(void) h_payto;
if (GNUNET_SYSERR == ctx->is_valid)
return;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &total_refunds));
+ qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls,
+ coin_pub,
+ merchant_pub,
+ h_contract_terms,
+ &add_refunds,
+ &total_refunds);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (1 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* Refunds exceeded total deposit? not OK! */
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (0 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* total_refunds == deposit_value;
+ in this case, the total contributed to the
+ wire transfer is zero (as are fees) */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dval));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dfee));
+
+ }
+ else
+ {
+ /* Compute deposit value by subtracting refunds */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&dval,
+ deposit_value,
+ &total_refunds));
+ if (-1 ==
+ TALER_amount_cmp (&dval,
+ deposit_fee))
+ {
+ /* dval < deposit_fee, so after refunds less than
+ the deposit fee remains; reduce deposit fee to
+ the remaining value of the coin */
+ dfee = dval;
+ }
+ else
+ {
+ /* Partial refund, deposit fee remains */
+ dfee = *deposit_fee;
+ }
+ }
+
if (GNUNET_NO == ctx->is_valid)
{
/* First one we encounter, setup general information in 'ctx' */
@@ -292,8 +397,8 @@ handle_deposit_data (void *cls,
ctx->is_valid = GNUNET_YES;
if (0 >
TALER_amount_subtract (&ctx->total,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
@@ -317,8 +422,8 @@ handle_deposit_data (void *cls,
}
if (0 >
TALER_amount_subtract (&delta,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
@@ -339,8 +444,9 @@ handle_deposit_data (void *cls,
struct AggregatedDepositDetail *wdd;
wdd = GNUNET_new (struct AggregatedDepositDetail);
- wdd->deposit_value = *deposit_value;
- wdd->deposit_fee = *deposit_fee;
+ wdd->deposit_value = dval;
+ wdd->deposit_fee = dfee;
+ wdd->refund_total = total_refunds;
wdd->h_contract_terms = *h_contract_terms;
wdd->coin_pub = *coin_pub;
GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c
deleted file mode 100644
index 34010462d..000000000
--- a/src/exchange/taler-exchange-httpd_wire.c
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2022 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_wire.c
- * @brief Handle /wire requests
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_json_lib.h>
-#include "taler_dbevents.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_wire.h"
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include <jansson.h>
-
-/**
- * Information we track about wire fees.
- */
-struct WireFeeSet
-{
-
- /**
- * Kept in a DLL.
- */
- struct WireFeeSet *next;
-
- /**
- * Kept in a DLL.
- */
- struct WireFeeSet *prev;
-
- /**
- * Actual fees.
- */
- struct TALER_WireFeeSet fees;
-
- /**
- * Start date of fee validity (inclusive).
- */
- struct GNUNET_TIME_Timestamp start_date;
-
- /**
- * End date of fee validity (exclusive).
- */
- struct GNUNET_TIME_Timestamp end_date;
-
- /**
- * Wire method the fees apply to.
- */
- char *method;
-};
-
-
-/**
- * State we keep per thread to cache the /wire response.
- */
-struct WireStateHandle
-{
- /**
- * Cached reply for /wire response.
- */
- struct MHD_Response *wire_reply;
-
- /**
- * ETag for this response (if any).
- */
- char *etag;
-
- /**
- * head of DLL of wire fees.
- */
- struct WireFeeSet *wfs_head;
-
- /**
- * Tail of DLL of wire fees.
- */
- struct WireFeeSet *wfs_tail;
-
- /**
- * Earliest timestamp of all the wire methods when we have no more fees.
- */
- struct GNUNET_TIME_Absolute cache_expiration;
-
- /**
- * @e cache_expiration time, formatted.
- */
- char dat[128];
-
- /**
- * For which (global) wire_generation was this data structure created?
- * Used to check when we are outdated and need to be re-generated.
- */
- uint64_t wire_generation;
-
- /**
- * HTTP status to return with this response.
- */
- unsigned int http_status;
-
-};
-
-
-/**
- * Stores the latest generation of our wire response.
- */
-static struct WireStateHandle *wire_state;
-
-/**
- * Handler listening for wire updates by other exchange
- * services.
- */
-static struct GNUNET_DB_EventHandler *wire_eh;
-
-/**
- * Counter incremented whenever we have a reason to re-build the #wire_state
- * because something external changed.
- */
-static uint64_t wire_generation;
-
-
-/**
- * Free memory associated with @a wsh
- *
- * @param[in] wsh wire state to destroy
- */
-static void
-destroy_wire_state (struct WireStateHandle *wsh)
-{
- struct WireFeeSet *wfs;
-
- while (NULL != (wfs = wsh->wfs_head))
- {
- GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
- wsh->wfs_tail,
- wfs);
- GNUNET_free (wfs->method);
- GNUNET_free (wfs);
- }
- MHD_destroy_response (wsh->wire_reply);
- GNUNET_free (wsh->etag);
- GNUNET_free (wsh);
-}
-
-
-/**
- * Function called whenever another exchange process has updated
- * the wire data in the database.
- *
- * @param cls NULL
- * @param extra unused
- * @param extra_size number of bytes in @a extra unused
- */
-static void
-wire_update_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- (void) cls;
- (void) extra;
- (void) extra_size;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /wire update event\n");
- TEH_check_invariants ();
- wire_generation++;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_wire_init ()
-{
- struct GNUNET_DB_EventHeaderP es = {
- .size = htons (sizeof (es)),
- .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
- };
-
- wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
- GNUNET_TIME_UNIT_FOREVER_REL,
- &es,
- &wire_update_event_cb,
- NULL);
- if (NULL == wire_eh)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-void
-TEH_wire_done ()
-{
- if (NULL != wire_state)
- {
- destroy_wire_state (wire_state);
- wire_state = NULL;
- }
- if (NULL != wire_eh)
- {
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- wire_eh);
- wire_eh = NULL;
- }
-}
-
-
-/**
- * Add information about a wire account to @a cls.
- *
- * @param cls a `json_t *` object to expand with wire account details
- * @param payto_uri the exchange bank account URI to add
- * @param master_sig master key signature affirming that this is a bank
- * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
- */
-static void
-add_wire_account (void *cls,
- const char *payto_uri,
- const struct TALER_MasterSignatureP *master_sig)
-{
- json_t *a = cls;
-
- if (0 !=
- json_array_append_new (
- a,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- payto_uri),
- GNUNET_JSON_pack_data_auto ("master_sig",
- master_sig))))
- {
- GNUNET_break (0); /* out of memory!? */
- return;
- }
-}
-
-
-/**
- * Closure for #add_wire_fee().
- */
-struct AddContext
-{
- /**
- * Wire method the fees are for.
- */
- char *wire_method;
-
- /**
- * Wire state we are building.
- */
- struct WireStateHandle *wsh;
-
- /**
- * Array to append the fee to.
- */
- json_t *a;
-
- /**
- * Context we hash "everything" we add into. This is used
- * to compute the etag. Technically, we only hash the
- * master_sigs, as they imply the rest.
- */
- struct GNUNET_HashContext *hc;
-
- /**
- * Set to the maximum end-date seen.
- */
- struct GNUNET_TIME_Absolute max_seen;
-};
-
-
-/**
- * Add information about a wire account to @a cls.
- *
- * @param cls a `struct AddContext`
- * @param fees the wire fees we charge
- * @param start_date from when are these fees valid (start date)
- * @param end_date until when are these fees valid (end date, exclusive)
- * @param master_sig master key signature affirming that this is the correct
- * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
- */
-static void
-add_wire_fee (void *cls,
- const struct TALER_WireFeeSet *fees,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct AddContext *ac = cls;
- struct WireFeeSet *wfs;
-
- GNUNET_CRYPTO_hash_context_read (ac->hc,
- master_sig,
- sizeof (*master_sig));
- ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
- end_date.abs_time);
- wfs = GNUNET_new (struct WireFeeSet);
- wfs->start_date = start_date;
- wfs->end_date = end_date;
- wfs->fees = *fees;
- wfs->method = GNUNET_strdup (ac->wire_method);
- GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
- ac->wsh->wfs_tail,
- wfs);
- if (0 !=
- json_array_append_new (
- ac->a,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("wire_fee",
- &fees->wire),
- TALER_JSON_pack_amount ("closing_fee",
- &fees->closing),
- GNUNET_JSON_pack_timestamp ("start_date",
- start_date),
- GNUNET_JSON_pack_timestamp ("end_date",
- end_date),
- GNUNET_JSON_pack_data_auto ("sig",
- master_sig))))
- {
- GNUNET_break (0); /* out of memory!? */
- return;
- }
-}
-
-
-/**
- * Create the /wire response from our database state.
- *
- * @return NULL on error
- */
-static struct WireStateHandle *
-build_wire_state (void)
-{
- json_t *wire_accounts_array;
- json_t *wire_fee_object;
- uint64_t wg = wire_generation; /* must be obtained FIRST */
- enum GNUNET_DB_QueryStatus qs;
- struct WireStateHandle *wsh;
- struct GNUNET_HashContext *hc;
-
- wsh = GNUNET_new (struct WireStateHandle);
- wsh->wire_generation = wg;
- wire_accounts_array = json_array ();
- GNUNET_assert (NULL != wire_accounts_array);
- qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
- &add_wire_account,
- wire_accounts_array);
- if (0 > qs)
- {
- GNUNET_break (0);
- json_decref (wire_accounts_array);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_wire_accounts");
- return wsh;
- }
- if (0 == json_array_size (wire_accounts_array))
- {
- json_decref (wire_accounts_array);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED,
- NULL);
- return wsh;
- }
- wire_fee_object = json_object ();
- GNUNET_assert (NULL != wire_fee_object);
- wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
- hc = GNUNET_CRYPTO_hash_context_start ();
- {
- json_t *account;
- size_t index;
-
- json_array_foreach (wire_accounts_array, index, account) {
- char *wire_method;
- const char *payto_uri = json_string_value (json_object_get (account,
- "payto_uri"));
-
- GNUNET_assert (NULL != payto_uri);
- wire_method = TALER_payto_get_method (payto_uri);
- if (NULL == wire_method)
- {
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
- payto_uri);
- json_decref (wire_accounts_array);
- json_decref (wire_fee_object);
- GNUNET_CRYPTO_hash_context_abort (hc);
- return wsh;
- }
- if (NULL == json_object_get (wire_fee_object,
- wire_method))
- {
- struct AddContext ac = {
- .wire_method = wire_method,
- .wsh = wsh,
- .a = json_array (),
- .hc = hc
- };
-
- GNUNET_assert (NULL != ac.a);
- qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
- wire_method,
- &add_wire_fee,
- &ac);
- if (0 > qs)
- {
- GNUNET_break (0);
- json_decref (ac.a);
- json_decref (wire_fee_object);
- json_decref (wire_accounts_array);
- GNUNET_free (wire_method);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_wire_fees");
- GNUNET_CRYPTO_hash_context_abort (hc);
- return wsh;
- }
- if (0 == json_array_size (ac.a))
- {
- json_decref (ac.a);
- json_decref (wire_accounts_array);
- json_decref (wire_fee_object);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
- wire_method);
- GNUNET_free (wire_method);
- GNUNET_CRYPTO_hash_context_abort (hc);
- return wsh;
- }
- wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen,
- wsh->cache_expiration);
- GNUNET_assert (0 ==
- json_object_set_new (wire_fee_object,
- wire_method,
- ac.a));
- }
- GNUNET_free (wire_method);
- }
- }
-
-
- wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("accounts",
- wire_accounts_array),
- GNUNET_JSON_pack_object_steal ("fees",
- wire_fee_object),
- GNUNET_JSON_pack_data_auto ("master_public_key",
- &TEH_master_public_key));
- {
- struct GNUNET_TIME_Timestamp m;
-
- m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
- TALER_MHD_get_date_string (m.abs_time,
- wsh->dat);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setting 'Expires' header for '/wire' to '%s'\n",
- wsh->dat);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (wsh->wire_reply,
- MHD_HTTP_HEADER_EXPIRES,
- wsh->dat));
- }
- /* Set cache control headers: our response varies depending on these headers */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (wsh->wire_reply,
- MHD_HTTP_HEADER_VARY,
- MHD_HTTP_HEADER_ACCEPT_ENCODING));
- /* Information is always public, revalidate after 1 day */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (wsh->wire_reply,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "public,max-age=86400"));
-
- {
- struct GNUNET_HashCode h;
- char etag[sizeof (h) * 2];
- char *end;
-
- GNUNET_CRYPTO_hash_context_finish (hc,
- &h);
- end = GNUNET_STRINGS_data_to_string (&h,
- sizeof (h),
- etag,
- sizeof (etag));
- *end = '\0';
- wsh->etag = GNUNET_strdup (etag);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (wsh->wire_reply,
- MHD_HTTP_HEADER_ETAG,
- etag));
- }
- wsh->http_status = MHD_HTTP_OK;
- return wsh;
-}
-
-
-void
-TEH_wire_update_state (void)
-{
- struct GNUNET_DB_EventHeaderP es = {
- .size = htons (sizeof (es)),
- .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
- };
-
- TEH_plugin->event_notify (TEH_plugin->cls,
- &es,
- NULL,
- 0);
- wire_generation++;
-}
-
-
-/**
- * Return the current key state for this thread. Possibly
- * re-builds the key state if we have reason to believe
- * that something changed.
- *
- * @return NULL on error
- */
-struct WireStateHandle *
-get_wire_state (void)
-{
- struct WireStateHandle *old_wsh;
-
- old_wsh = wire_state;
- if ( (NULL == old_wsh) ||
- (old_wsh->wire_generation < wire_generation) )
- {
- struct WireStateHandle *wsh;
-
- TEH_check_invariants ();
- wsh = build_wire_state ();
- wire_state = wsh;
- if (NULL != old_wsh)
- destroy_wire_state (old_wsh);
- TEH_check_invariants ();
- return wsh;
- }
- return old_wsh;
-}
-
-
-MHD_RESULT
-TEH_handler_wire (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- struct WireStateHandle *wsh;
-
- (void) args;
- wsh = get_wire_state ();
- if (NULL == wsh)
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- NULL);
- {
- const char *etag;
-
- etag = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- if ( (NULL != etag) &&
- (MHD_HTTP_OK == wsh->http_status) &&
- (NULL != wsh->etag) &&
- (0 == strcmp (etag,
- wsh->etag)) )
- {
- MHD_RESULT ret;
- struct MHD_Response *resp;
-
- resp = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- TALER_MHD_add_global_headers (resp);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_EXPIRES,
- wsh->dat));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- wsh->etag));
- ret = MHD_queue_response (rc->connection,
- MHD_HTTP_NOT_MODIFIED,
- resp);
- GNUNET_break (MHD_YES == ret);
- MHD_destroy_response (resp);
- return ret;
- }
- }
- return MHD_queue_response (rc->connection,
- wsh->http_status,
- wsh->wire_reply);
-}
-
-
-const struct TALER_WireFeeSet *
-TEH_wire_fees_by_time (
- struct GNUNET_TIME_Timestamp ts,
- const char *method)
-{
- struct WireStateHandle *wsh = get_wire_state ();
-
- for (struct WireFeeSet *wfs = wsh->wfs_head;
- NULL != wfs;
- wfs = wfs->next)
- {
- if (0 != strcmp (method,
- wfs->method))
- continue;
- if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
- >,
- ts)) ||
- (GNUNET_TIME_timestamp_cmp (ts,
- >=,
- wfs->end_date)) )
- continue;
- return &wfs->fees;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No wire fees for method `%s' at %s configured\n",
- method,
- GNUNET_TIME_timestamp2s (ts));
- return NULL;
-}
-
-
-/* end of taler-exchange-httpd_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_wire.h
deleted file mode 100644
index 75595fe69..000000000
--- a/src/exchange/taler-exchange-httpd_wire.h
+++ /dev/null
@@ -1,84 +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_wire.h
- * @brief Handle /wire requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_WIRE_H
-#define TALER_EXCHANGE_HTTPD_WIRE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Clean up wire subsystem.
- */
-void
-TEH_wire_done (void);
-
-
-/**
- * Look up wire fee structure by @a ts.
- *
- * @param ts timestamp to lookup wire fees at
- * @param method wire method to lookup fees for
- * @return the wire fee details, or
- * NULL if none are configured for @a ts and @a method
- */
-const struct TALER_WireFeeSet *
-TEH_wire_fees_by_time (
- struct GNUNET_TIME_Timestamp ts,
- const char *method);
-
-
-/**
- * Initialize wire subsystem.
- *
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_wire_init (void);
-
-
-/**
- * Something changed in the database. Rebuild the wire replies. This function
- * should be called if the exchange learns about a new signature from our
- * master key.
- *
- * (We do not do so immediately, but merely signal to all threads that they
- * need to rebuild their wire state upon the next call to
- * #TEH_handler_wire()).
- */
-void
-TEH_wire_update_state (void);
-
-
-/**
- * Handle a "/wire" request.
- *
- * @param rc request context
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_wire (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
deleted file mode 100644
index 253773639..000000000
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ /dev/null
@@ -1,516 +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 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_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 reserve
- * from which we are withdrawing.
- */
- struct TALER_PaytoHashP h_payto;
-
- /**
- * Current time for the DB transaction.
- */
- struct GNUNET_TIME_Timestamp now;
-
-};
-
-
-/**
- * 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_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 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;
- uint64_t ruuid;
- const struct TALER_CsNonce *nonce;
- const struct TALER_BlindedPlanchet *bp;
-
- wc->now = GNUNET_TIME_timestamp_get ();
- qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
- &wc->collectable.reserve_pub,
- &wc->h_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)
- {
- const char *kyc_required;
-
- kyc_required = TALER_KYCLOGIC_kyc_test_required (
- TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
- &wc->h_payto,
- TEH_plugin->select_satisfied_kyc_processes,
- TEH_plugin->cls,
- &withdraw_amount_cb,
- wc);
- if (NULL != kyc_required)
- {
- /* insert KYC requirement into DB! */
- wc->kyc.ok = false;
- return TEH_plugin->insert_kyc_requirement_for_account (
- TEH_plugin->cls,
- kyc_required,
- &wc->h_payto,
- &wc->kyc.requirement_row);
- }
- }
- 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,
- &found,
- &balance_ok,
- &nonce_ok,
- &ruuid);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "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 (! balance_ok)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *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_hash2 (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_payto,
- &wc.kyc);
- {
- 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-kyc-aml-pep-trigger.sh b/src/exchange/taler-exchange-kyc-aml-pep-trigger.sh
new file mode 100755
index 000000000..9baa32baf
--- /dev/null
+++ b/src/exchange/taler-exchange-kyc-aml-pep-trigger.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# This file is in the public domain.
+# This is an example of how to trigger AML if the
+# KYC attributes include '{"pep":true}'
+#
+# To be used as a script for the KYC_AML_TRIGGER.
+test "false" = $(jq .pep -)
diff --git a/src/exchange/taler-exchange-transfer.c b/src/exchange/taler-exchange-transfer.c
index 5a4aace9c..9724b41fc 100644
--- a/src/exchange/taler-exchange-transfer.c
+++ b/src/exchange/taler-exchange-transfer.c
@@ -406,25 +406,17 @@ batch_done (void)
* except for irrecoverable errors.
*
* @param cls `struct WirePrepareData` we are working on
- * @param http_status_code #MHD_HTTP_OK on success
- * @param ec taler error code
- * @param row_id unique ID of the wire transfer in the bank's records
- * @param wire_timestamp when did the transfer happen
+ * @param tr transfer response
*/
static void
wire_confirm_cb (void *cls,
- unsigned int http_status_code,
- enum TALER_ErrorCode ec,
- uint64_t row_id,
- struct GNUNET_TIME_Timestamp wire_timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
struct WirePrepareData *wpd = cls;
enum GNUNET_DB_QueryStatus qs;
- (void) row_id;
- (void) wire_timestamp;
wpd->eh = NULL;
- switch (http_status_code)
+ switch (tr->http_status)
{
case MHD_HTTP_OK:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -435,11 +427,12 @@ wire_confirm_cb (void *cls,
/* continued below */
break;
case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transaction %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
qs = db_plugin->wire_prepare_data_mark_failed (db_plugin->cls,
wpd->row_id);
/* continued below */
@@ -456,7 +449,7 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Wire transfer %llu failed (%u), trying again\n",
(unsigned long long) wpd->row_id,
- http_status_code);
+ tr->http_status);
wpd->eh = TALER_BANK_transfer (ctx,
wpd->wa->auth,
&wpd[1],
@@ -468,8 +461,8 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transaction %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
cleanup_wpd ();
db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
@@ -479,8 +472,8 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
db_plugin->rollback (db_plugin->cls);
cleanup_wpd ();
global_ret = EXIT_FAILURE;
@@ -563,9 +556,9 @@ wire_prepare_cb (void *cls,
}
wpd = GNUNET_malloc (sizeof (struct WirePrepareData)
+ buf_size);
- memcpy (&wpd[1],
- buf,
- buf_size);
+ GNUNET_memcpy (&wpd[1],
+ buf,
+ buf_size);
wpd->buf_size = buf_size;
wpd->row_id = rowid;
GNUNET_CONTAINER_DLL_insert (wpd_head,
@@ -582,7 +575,7 @@ wire_prepare_cb (void *cls,
GNUNET_break (0);
cleanup_wpd ();
db_plugin->rollback (db_plugin->cls);
- global_ret = EXIT_NOTCONFIGURED;
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c
index e9b28030b..da5d9c098 100644
--- a/src/exchange/taler-exchange-wirewatch.c
+++ b/src/exchange/taler-exchange-wirewatch.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2016--2022 Taler Systems SA
+ Copyright (C) 2016--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
@@ -52,6 +52,23 @@ static const struct TALER_EXCHANGEDB_AccountInfo *ai;
static struct TALER_BANK_CreditHistoryHandle *hh;
/**
+ * Set to true if the request for history did actually
+ * return transaction items.
+ */
+static bool hh_returned_data;
+
+/**
+ * Set to true if the request for history did not
+ * succeed because the account was unknown.
+ */
+static bool hh_account_404;
+
+/**
+ * When did we start the last @e hh request?
+ */
+static struct GNUNET_TIME_Absolute hh_start_time;
+
+/**
* Until when is processing this wire plugin delayed?
*/
static struct GNUNET_TIME_Absolute delayed_until;
@@ -95,6 +112,12 @@ static struct GNUNET_TIME_Absolute shard_end_time;
static struct GNUNET_TIME_Relative shard_delay;
/**
+ * How long did we take to finish the last shard
+ * for this account?
+ */
+static struct GNUNET_TIME_Relative longpoll_timeout;
+
+/**
* Name of our job in the shard table.
*/
static char *job_name;
@@ -154,6 +177,11 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
static struct GNUNET_TIME_Relative wirewatch_idle_sleep_interval;
/**
+ * How long do we sleep on serialization conflicts?
+ */
+static struct GNUNET_TIME_Relative wirewatch_conflict_sleep_interval;
+
+/**
* Modulus to apply to group shards. The shard size must ultimately be a
* multiple of the batch size. Thus, if this is not a multiple of the
* #MAXIMUM_BATCH_SIZE, the batch size will be set to the #shard_size.
@@ -210,6 +238,8 @@ shutdown_task (void *cls)
if (NULL != hh)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "History request cancelled on shutdown\n");
TALER_BANK_credit_history_cancel (hh);
hh = NULL;
}
@@ -218,13 +248,16 @@ shutdown_task (void *cls)
db_plugin->rollback (db_plugin->cls);
started_transaction = false;
}
- qs = db_plugin->abort_shard (db_plugin->cls,
- job_name,
- shard_start,
- shard_end);
- if (qs <= 0)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to abort work shard on shutdown\n");
+ if (shard_open)
+ {
+ qs = db_plugin->abort_shard (db_plugin->cls,
+ job_name,
+ shard_start,
+ shard_end);
+ if (qs <= 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to abort work shard on shutdown\n");
+ }
GNUNET_free (job_name);
if (NULL != ctx)
{
@@ -263,7 +296,7 @@ add_account_cb (void *cls,
if (! in_ai->credit_enabled)
return; /* not enabled for us, skip */
if ( (NULL != account_section) &&
- (0 != strcasecmp (ai->section_name,
+ (0 != strcasecmp (in_ai->section_name,
account_section)) )
return; /* not enabled for us, skip */
if (NULL != ai)
@@ -320,8 +353,6 @@ exchange_serve_process_config (void)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for credit!\n");
- TALER_EXCHANGEDB_plugin_unload (db_plugin);
- db_plugin = NULL;
return GNUNET_SYSERR;
}
TALER_EXCHANGEDB_find_accounts (&add_account_cb,
@@ -331,9 +362,6 @@ exchange_serve_process_config (void)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No accounts enabled for credit!\n");
GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_INVALIDARGUMENT;
- TALER_EXCHANGEDB_plugin_unload (db_plugin);
- db_plugin = NULL;
return GNUNET_SYSERR;
}
return GNUNET_OK;
@@ -377,6 +405,8 @@ handle_soft_error (void)
}
/* Reset to beginning of transaction, and go again
from there. */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Encountered soft error, resetting start point to batch start\n");
latest_row_off = batch_start;
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
@@ -420,7 +450,6 @@ schedule_transfers (void)
static void
transaction_completed (void)
{
- GNUNET_assert (! started_transaction);
if ( (batch_start + batch_size ==
latest_row_off) &&
(batch_size < MAXIMUM_BATCH_SIZE) )
@@ -448,6 +477,29 @@ transaction_completed (void)
GNUNET_SCHEDULER_shutdown ();
return;
}
+ if (! (hh_returned_data || hh_account_404) )
+ {
+ /* Enforce long-polling delay even if the server ignored it
+ and returned earlier */
+ struct GNUNET_TIME_Relative latency;
+ struct GNUNET_TIME_Relative left;
+
+ latency = GNUNET_TIME_absolute_get_duration (hh_start_time);
+ left = GNUNET_TIME_relative_subtract (longpoll_timeout,
+ latency);
+ if (! (test_mode ||
+ GNUNET_TIME_relative_is_zero (left)) )
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Server did not respect long-polling, enforcing client-side by sleeping for %s\n",
+ GNUNET_TIME_relative2s (left,
+ true));
+ delayed_until = GNUNET_TIME_relative_to_absolute (left);
+ }
+ if (hh_account_404)
+ delayed_until = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MILLISECONDS);
+ if (test_mode)
+ delayed_until = GNUNET_TIME_UNIT_ZERO_ABS;
GNUNET_assert (NULL == task);
schedule_transfers ();
}
@@ -475,6 +527,7 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
transaction_completed ();
return;
}
+ hh_returned_data = true;
/* check serial IDs for range constraints */
for (unsigned int i = 0; i<details_length; i++)
{
@@ -506,34 +559,30 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
}
lroff = cd->serial_id;
}
- if (GNUNET_OK !=
- db_plugin->start_read_committed (db_plugin->cls,
- "wirewatch check for incoming wire transfers"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- started_transaction = true;
-
- for (unsigned int i = 0; i<details_length; i++)
+ if (0 != details_length)
{
- const struct TALER_BANK_CreditDetails *cd = &details[i];
+ enum GNUNET_DB_QueryStatus qss[details_length];
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
- /* FIXME #7276: Consider using Postgres multi-valued insert here,
- for up to 15x speed-up according to
- https://dba.stackexchange.com/questions/224989/multi-row-insert-vs-transactional-single-row-inserts#225006
- (Note: this may require changing both the
- plugin API as well as modifying how this function is called.) */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Importing %u transactions\n",
+ details_length);
+ for (unsigned int i = 0; i<details_length; i++)
+ {
+ const struct TALER_BANK_CreditDetails *cd = &details[i];
+ struct TALER_EXCHANGEDB_ReserveInInfo *res = &reserves[i];
+
+ res->reserve_pub = &cd->reserve_pub;
+ res->balance = &cd->amount;
+ res->execution_time = cd->execution_date;
+ res->sender_account_details = cd->debit_account_uri;
+ res->exchange_account_name = ai->section_name;
+ res->wire_reference = cd->serial_id;
+ }
qs = db_plugin->reserves_in_insert (db_plugin->cls,
- &cd->reserve_pub,
- &cd->amount,
- cd->execution_date,
- cd->debit_account_uri,
- ai->section_name,
- cd->serial_id);
+ reserves,
+ details_length,
+ qss);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -542,34 +591,53 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got DB soft error for reserves_in_insert. Rolling back.\n");
+ "Got DB soft error for reserves_in_insert (%u). Rolling back.\n",
+ details_length);
handle_soft_error ();
return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Either wirewatch was freshly started after the system was
- shutdown and we're going over an incomplete shard again
- after being restarted, or the shard lock period was too
- short (number of workers set incorrectly?) and a 2nd
- wirewatcher has been stealing our work while we are still
- at it. */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempted to import transaction %llu (%s) twice. "
- "This should happen rarely (if not, ask for support).\n",
- (unsigned long long) cd->serial_id,
- job_name);
- db_plugin->rollback (db_plugin->cls);
- started_transaction = false;
- /* already existed, ok, let's just continue */
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Imported transaction %llu.",
- (unsigned long long) cd->serial_id);
- /* normal case */
+ default:
break;
}
- progress = true;
+ for (unsigned int i = 0; i<details_length; i++)
+ {
+ const struct TALER_BANK_CreditDetails *cd = &details[i];
+
+ switch (qss[i])
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got DB soft error for batch_reserves_in_insert(%u). Rolling back.\n",
+ i);
+ handle_soft_error ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Either wirewatch was freshly started after the system was
+ shutdown and we're going over an incomplete shard again
+ after being restarted, or the shard lock period was too
+ short (number of workers set incorrectly?) and a 2nd
+ wirewatcher has been stealing our work while we are still
+ at it. */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempted to import transaction %llu (%s) twice. "
+ "This should happen rarely (if not, ask for support).\n",
+ (unsigned long long) cd->serial_id,
+ job_name);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported transaction %llu.\n",
+ (unsigned long long) cd->serial_id);
+ /* normal case */
+ progress = true;
+ break;
+ }
+ }
}
+
latest_row_off = lroff;
shard_done = (shard_end <= latest_row_off);
if (shard_done)
@@ -596,6 +664,7 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* normal case */
+ progress = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Completed shard %s (%llu,%llu] after %s\n",
job_name,
@@ -606,35 +675,6 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
true));
break;
}
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Committing %s progress (%llu,%llu] at %llu\n (%s)",
- job_name,
- (unsigned long long) shard_start,
- (unsigned long long) shard_end,
- (unsigned long long) latest_row_off,
- shard_done
- ? "shard done"
- : "shard incomplete");
- qs = db_plugin->commit (db_plugin->cls);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* reduce transaction size to reduce rollback probability */
- handle_soft_error ();
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- started_transaction = false;
- /* normal case */
- break;
- }
- if (shard_done)
- {
shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time);
shard_open = false;
transaction_completed ();
@@ -660,19 +700,20 @@ history_cb (void *cls,
(void) cls;
GNUNET_assert (NULL == task);
hh = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"History request returned with HTTP status %u\n",
reply->http_status);
switch (reply->http_status)
{
case MHD_HTTP_OK:
- process_reply (reply->details.success.details,
- reply->details.success.details_length);
+ process_reply (reply->details.ok.details,
+ reply->details.ok.details_length);
return;
case MHD_HTTP_NO_CONTENT:
transaction_completed ();
return;
case MHD_HTTP_NOT_FOUND:
+ hh_account_404 = true;
if (ignore_account_404)
{
transaction_completed ();
@@ -680,12 +721,12 @@ history_cb (void *cls,
}
break;
default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error fetching history: %s (%u)\n",
+ TALER_ErrorCode_get_hint (reply->ec),
+ reply->http_status);
break;
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Error fetching history: %s (%u)\n",
- TALER_ErrorCode_get_hint (reply->ec),
- reply->http_status);
if (! exit_on_error)
{
transaction_completed ();
@@ -707,15 +748,18 @@ continue_with_shard (void *cls)
shard_end - latest_row_off);
GNUNET_assert (NULL == hh);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting credit history staring from %llu\n",
+ "Requesting credit history starting from %llu\n",
(unsigned long long) latest_row_off);
+ hh_start_time = GNUNET_TIME_absolute_get ();
+ hh_returned_data = false;
+ hh_account_404 = false;
hh = TALER_BANK_credit_history (ctx,
ai->auth,
latest_row_off,
limit,
test_mode
? GNUNET_TIME_UNIT_ZERO
- : LONGPOLL_TIMEOUT,
+ : longpoll_timeout,
&history_cb,
NULL);
if (NULL == hh)
@@ -729,6 +773,11 @@ continue_with_shard (void *cls)
}
+/**
+ * Reserve a shard for us to work on.
+ *
+ * @param cls NULL
+ */
static void
lock_shard (void *cls)
{
@@ -792,12 +841,25 @@ lock_shard (void *cls)
{
struct GNUNET_TIME_Relative rdelay;
- rdelay = GNUNET_TIME_randomize (wirewatch_idle_sleep_interval);
+ wirewatch_conflict_sleep_interval
+ = GNUNET_TIME_STD_BACKOFF (wirewatch_conflict_sleep_interval);
+ rdelay = GNUNET_TIME_randomize (wirewatch_conflict_sleep_interval);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Serialization error tying to obtain shard %s, will try again in %s!\n",
job_name,
GNUNET_STRINGS_relative_time_to_string (rdelay,
true));
+#if 1
+ if (GNUNET_TIME_relative_cmp (rdelay,
+ >,
+ GNUNET_TIME_UNIT_SECONDS))
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Delay would have been for %s\n",
+ GNUNET_TIME_relative2s (rdelay,
+ true));
+ rdelay = GNUNET_TIME_relative_min (rdelay,
+ GNUNET_TIME_UNIT_SECONDS);
+#endif
delayed_until = GNUNET_TIME_relative_to_absolute (rdelay);
}
GNUNET_assert (NULL == task);
@@ -810,7 +872,7 @@ lock_shard (void *cls)
job_name,
GNUNET_STRINGS_relative_time_to_string (
wirewatch_idle_sleep_interval,
- GNUNET_YES));
+ true));
delayed_until = GNUNET_TIME_relative_to_absolute (
wirewatch_idle_sleep_interval);
shard_open = false;
@@ -819,6 +881,7 @@ lock_shard (void *cls)
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* continued below */
+ wirewatch_conflict_sleep_interval = GNUNET_TIME_UNIT_ZERO;
break;
}
shard_end_time = GNUNET_TIME_relative_to_absolute (delay);
@@ -834,9 +897,19 @@ lock_shard (void *cls)
if ( (shard_open) &&
(shard_start == last_shard_start) &&
(shard_end == last_shard_end) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Continuing from %llu\n",
+ (unsigned long long) latest_row_off);
GNUNET_break (latest_row_off >= batch_start); /* resume where we left things */
+ }
else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resetting shard start to original start point (%d)\n",
+ shard_open ? 1 : 0);
latest_row_off = batch_start;
+ }
shard_open = true;
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
@@ -877,6 +950,7 @@ run (void *cls,
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NO_RESTART;
return;
}
rc = GNUNET_CURL_gnunet_rc_create (ctx);
@@ -905,6 +979,11 @@ main (int argc,
"exit-on-error",
"terminate wirewatch if we failed to download information from the bank",
&exit_on_error),
+ GNUNET_GETOPT_option_relative_time ('f',
+ "longpoll-timeout",
+ "DELAY",
+ "what is the timeout when asking the bank about new transactions, specify with unit (e.g. --longpoll-timeout=30s)",
+ &longpoll_timeout),
GNUNET_GETOPT_option_flag ('I',
"ignore-not-found",
"continue, even if the bank account of the exchange was not found",
@@ -930,6 +1009,7 @@ main (int argc,
};
enum GNUNET_GenericReturnValue ret;
+ longpoll_timeout = LONGPOLL_TIMEOUT;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
diff --git a/src/exchange/test_taler_exchange_httpd.conf b/src/exchange/test_taler_exchange_httpd.conf
index 9bd4851fb..7e7ff8b45 100644
--- a/src/exchange/test_taler_exchange_httpd.conf
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -13,6 +13,8 @@ TINY_AMOUNT = EUR:0.01
[exchange]
+AML_THRESHOLD = EUR:1000000
+
# Directory with our terms of service.
TERMS_DIR = ../../contrib/tos
@@ -67,7 +69,7 @@ ENABLE_CREDIT = YES
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_GATEWAY_URL = "http://localhost:8082/3/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/3/taler-wire-gateway/"
# Coins for the tests.
[coin_eur_ct_1_rsa]
diff --git a/src/exchange/test_taler_exchange_httpd.sh b/src/exchange/test_taler_exchange_httpd.sh
index e8dc46af8..0fe71f3ad 100755
--- a/src/exchange/test_taler_exchange_httpd.sh
+++ b/src/exchange/test_taler_exchange_httpd.sh
@@ -1,7 +1,7 @@
#!/bin/bash
#
# This file is part of TALER
-# Copyright (C) 2015-2020 Taler Systems SA
+# Copyright (C) 2015-2020, 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
@@ -23,6 +23,8 @@
# Clear environment from variables that override config.
unset XDG_DATA_HOME
unset XDG_CONFIG_HOME
+
+set -eu
#
echo -n "Launching exchange ..."
PREFIX=
@@ -30,7 +32,7 @@ PREFIX=
#PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p"
# Setup database
-taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null
+taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null || exit 77
# Run Exchange HTTPD (in background)
$PREFIX taler-exchange-httpd -c test_taler_exchange_httpd.conf 2> test-exchange.log &