aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2024-09-01 15:32:58 +0200
committerChristian Grothoff <christian@grothoff.org>2024-09-01 15:32:58 +0200
commit39e51a95be1539467efc146bf72e88631414ef9d (patch)
tree8c8a8371c50d0430c32d93a69836b5f1b15ad52e
parent262ecc1dec0d764878b02c3eaee83da3370f1592 (diff)
downloadexchange-39e51a95be1539467efc146bf72e88631414ef9d.tar.xz
work on #9039
-rw-r--r--src/exchange/taler-exchange-httpd.c1
-rw-r--r--src/exchange/taler-exchange-httpd_batch-deposit.c896
-rw-r--r--src/exchange/taler-exchange-httpd_batch-deposit.h8
-rw-r--r--src/exchange/taler-exchange-httpd_config.h2
-rw-r--r--src/exchangedb/Makefile.am1
-rw-r--r--src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c153
-rw-r--r--src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h51
-rw-r--r--src/exchangedb/pg_select_merge_amounts_for_kyc_check.c1
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c3
-rw-r--r--src/include/taler_exchangedb_plugin.h22
-rw-r--r--src/kyclogic/kyclogic_api.c6
-rw-r--r--src/lib/exchange_api_handle.c21
12 files changed, 919 insertions, 246 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 36c567ebd..52e8ef6af 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -2585,6 +2585,7 @@ do_shutdown (void *cls)
my_mhd = TALER_MHD_daemon_stop ();
TEH_resume_keys_requests (true);
+ TEH_batch_deposit_cleanup ();
TEH_age_withdraw_cleanup ();
TEH_batch_withdraw_cleanup ();
TEH_reserves_close_cleanup ();
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c
index 84f27dd94..b18acd8aa 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-2023 Taler Systems SA
+ Copyright (C) 2014-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
@@ -31,6 +31,7 @@
#include "taler_extensions_policy.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_batch-deposit.h"
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_lib.h"
@@ -44,34 +45,34 @@ struct BatchDepositContext
{
/**
- * Array with the individual coin deposit fees.
+ * Kept in a DLL.
*/
- struct TALER_Amount *deposit_fees;
+ struct BatchDepositContext *next;
/**
- * Our timestamp (when we received the request).
- * Possibly updated by the transaction if the
- * request is idempotent (was repeated).
+ * Kept in a DLL.
*/
- struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct BatchDepositContext *prev;
/**
- * Details about the batch deposit operation.
+ * The request we are working on.
*/
- struct TALER_EXCHANGEDB_BatchDeposit bd;
+ struct TEH_RequestContext *rc;
+ /**
+ * Handle for the legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
/**
- * Total amount that is accumulated with this deposit,
- * without fee.
+ * Array with the individual coin deposit fees.
*/
- struct TALER_Amount accumulated_total_without_fee;
+ struct TALER_Amount *deposit_fees;
/**
- * True, if no policy was present in the request. Then
- * @e policy_json is NULL and @e h_policy will be all zero.
+ * Information about deposited coins.
*/
- bool has_no_policy;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
/**
* Additional details for policy extension relevant for this
@@ -79,12 +80,16 @@ struct BatchDepositContext
*/
json_t *policy_json;
+
/**
- * If @e policy_json was present, the corresponding policy extension
- * calculates these details. These will be persisted in the policy_details
- * table.
+ * Response to return, if set.
*/
- struct TALER_PolicyDetails policy_details;
+ struct MHD_Response *response;
+
+ /**
+ * KYC status of the reserve used for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
/**
* Hash over @e policy_details, might be all zero
@@ -102,63 +107,161 @@ struct BatchDepositContext
*/
uint64_t policy_details_serial_id;
+ /**
+ * 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;
+
+ /**
+ * Total amount that is accumulated with this deposit,
+ * without fee.
+ */
+ struct TALER_Amount accumulated_total_without_fee;
+
+ /**
+ * Details about the batch deposit operation.
+ */
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+
+ /**
+ * If @e policy_json was present, the corresponding policy extension
+ * calculates these details. These will be persisted in the policy_details
+ * table.
+ */
+ struct TALER_PolicyDetails policy_details;
+
+ /**
+ * HTTP status to return with @e response, or 0.
+ */
+ unsigned int http_status;
+
+ /**
+ * Our current state in the state machine.
+ */
+ enum
+ {
+ BDC_PHASE_INIT = 0,
+ BDC_PHASE_PARSE = 1,
+ BDC_PHASE_POLICY = 2,
+ BDC_PHASE_KYC = 3,
+ BDC_PHASE_TRANSACT = 4,
+ BDC_PHASE_REPLY_SUCCESS = 5,
+ BDC_PHASE_SUSPENDED,
+ BDC_PHASE_CHECK_KYC_RESULT,
+ BDC_PHASE_GENERATE_REPLY_FAILURE,
+ BDC_PHASE_RETURN_YES,
+ BDC_PHASE_RETURN_NO,
+ } phase;
+
+ /**
+ * True, if no policy was present in the request. Then
+ * @e policy_json is NULL and @e h_policy will be all zero.
+ */
+ bool has_no_policy;
};
/**
+ * Head of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_head;
+
+/**
+ * Tail of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_tail;
+
+
+void
+TEH_batch_deposit_cleanup ()
+{
+ struct BatchDepositContext *bdc;
+
+ while (NULL != (bdc = bdc_head))
+ {
+ GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase);
+ bdc->phase = BDC_PHASE_RETURN_NO;
+ MHD_resume_connection (bdc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (bdc_head,
+ bdc_tail,
+ bdc);
+ }
+}
+
+
+/**
+ * Terminate the main loop by returning the final
+ * result.
+ *
+ * @param[in,out] bdc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct BatchDepositContext *bdc,
+ MHD_RESULT mres)
+{
+ bdc->phase = (MHD_YES == mres)
+ ? BDC_PHASE_RETURN_YES
+ : BDC_PHASE_RETURN_NO;
+}
+
+
+/**
* Send confirmation of batch 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 coins had sufficient
* (residual) value for the specified transaction and that it will execute the
* requested batch deposit operation with the given wiring details.
*
- * @param connection connection to the client
- * @param dc information about the batch deposit
- * @return MHD result code
+ * @param[in,out] bdc information about the batch deposit
*/
-static MHD_RESULT
-reply_batch_deposit_success (
- struct MHD_Connection *connection,
- const struct BatchDepositContext *dc)
+static void
+bdc_phase_reply_success (
+ struct BatchDepositContext *bdc)
{
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
enum TALER_ErrorCode ec;
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
- for (unsigned int i = 0; i<bd->num_cdis; i++)
+ for (unsigned int i = 0; i<bdc->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,
+ &bdc->h_wire,
+ bdc->has_no_policy ? NULL : &bdc->h_policy,
+ bdc->exchange_timestamp,
bd->wire_deadline,
bd->refund_deadline,
- &dc->accumulated_total_without_fee,
+ &bdc->accumulated_total_without_fee,
bd->num_cdis,
csigs,
- &dc->bd.merchant_pub,
+ &bdc->bd.merchant_pub,
&pub,
&sig)))
{
GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_ec (bdc->rc->connection,
+ ec,
+ NULL));
+ return;
}
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- dc->exchange_timestamp),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig));
+ finish_loop (bdc,
+ TALER_MHD_REPLY_JSON_PACK (
+ bdc->rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ bdc->exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig)));
}
@@ -180,8 +283,8 @@ batch_deposit_transaction (void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
- struct BatchDepositContext *dc = cls;
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ struct BatchDepositContext *bdc = cls;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
uint32_t bad_balance_coin_index = UINT32_MAX;
bool balance_ok;
@@ -189,26 +292,26 @@ batch_deposit_transaction (void *cls,
/* If the deposit has a policy associated to it, persist it. This will
* insert or update the record. */
- if (! dc->has_no_policy)
+ if (! bdc->has_no_policy)
{
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);
+ &bdc->policy_details,
+ &bdc->bd.policy_details_serial_id,
+ &bdc->accumulated_total_without_fee,
+ &bdc->policy_details.fulfillment_state);
if (qs < 0)
return qs;
- dc->bd.policy_blocked =
- dc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
+ bdc->bd.policy_blocked =
+ bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
}
/* FIXME: replace by batch insert! */
- for (unsigned int i = 0; i<bd->num_cdis; i++)
+ for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
{
const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
- = &bd->cdis[i];
+ = &bdc->cdis[i];
uint64_t known_coin_id;
qs = TEH_make_coin_known (&cdi->coin,
@@ -226,7 +329,7 @@ batch_deposit_transaction (void *cls,
qs = TEH_plugin->do_deposit (
TEH_plugin->cls,
bd,
- &dc->exchange_timestamp,
+ &bdc->exchange_timestamp,
&balance_ok,
&bad_balance_coin_index,
&in_conflict);
@@ -236,10 +339,11 @@ batch_deposit_transaction (void *cls,
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");
+ *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,
@@ -261,10 +365,11 @@ batch_deposit_transaction (void *cls,
{
TALER_LOG_WARNING (
"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");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
return qs;
}
@@ -277,16 +382,16 @@ batch_deposit_transaction (void *cls,
}
if (! balance_ok)
{
- GNUNET_assert (bad_balance_coin_index < bd->num_cdis);
+ GNUNET_assert (bad_balance_coin_index < bdc->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));
+ TALER_B2S (&bdc->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);
+ &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+ &bdc->cdis[bad_balance_coin_index].coin.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
@@ -295,12 +400,288 @@ batch_deposit_transaction (void *cls,
/**
+ * Run database transaction.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_transact (struct BatchDepositContext *bdc)
+{
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure"));
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (bdc->rc->connection,
+ "execute batch deposit",
+ TEH_MT_REQUEST_BATCH_DEPOSIT,
+ &mhd_ret,
+ &batch_deposit_transaction,
+ bdc))
+ {
+ finish_loop (bdc,
+ mhd_ret);
+ return;
+ }
+ bdc->phase++;
+}
+
+
+/**
+ * Check if the @a bdc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param bdc parsed request data
+ * @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 BatchDepositContext *bdc)
+{
+#if FIXME_PLACEHOLDER
+ const struct TEH_RequestContext *rc = bdc->rc;
+
+ for (unsigned int i = 0; i<bwc->planchets_length; i++)
+ {
+ struct PlanchetContext *pc = &bwc->planchets[i];
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+
+ qs = TEH_plugin->get_withdraw_info (
+ TEH_plugin->cls,
+ &pc->collectable.h_coin_envelope,
+ &collectable);
+ if (0 > qs)
+ {
+ /* FIXME: soft error not handled correctly! */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ finish_loop (bwc,
+ TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_withdraw_info"));
+ return true;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
+ pc->collectable = collectable;
+ }
+ /* generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
+ bwc->phase = BDC_PHASE_GENERATE_REPLY_SUCCESS;
+ return true;
+#else
+ GNUNET_break (0); // NOT IMPLEMENTED
+ return false;
+#endif
+}
+
+
+/**
+ * Check the KYC result.
+ *
+ * @param bdc storage for request processing
+ */
+static void
+bdc_phase_check_kyc_result (struct BatchDepositContext *bdc)
+{
+ /* return final positive response */
+ if (! bdc->kyc.ok)
+ {
+ if (check_request_idempotent (bdc))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request is idempotent!\n");
+ return;
+ }
+ /* KYC required */
+ finish_loop (bdc,
+ TEH_RESPONSE_reply_kyc_required (
+ bdc->rc->connection,
+ &bdc->bd.wire_target_h_payto,
+ &bdc->kyc));
+ return;
+ }
+ bdc->phase = BDC_PHASE_TRANSACT;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+deposit_legi_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct BatchDepositContext *bdc = cls;
+
+ bdc->lch = NULL;
+ GNUNET_assert (BDC_PHASE_SUSPENDED ==
+ bdc->phase);
+ MHD_resume_connection (bdc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (bdc_head,
+ bdc_tail,
+ bdc);
+ TALER_MHD_daemon_trigger ();
+ if (NULL != lcr->response)
+ {
+ bdc->response = lcr->response;
+ bdc->http_status = lcr->http_status;
+ bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE;
+ return;
+ }
+ bdc->kyc = lcr->kyc;
+ bdc->phase = BDC_PHASE_CHECK_KYC_RESULT;
+}
+
+
+/**
+ * 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
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposit_amount_cb (
+ void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct BatchDepositContext *bdc = cls;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signaling amount %s for KYC check during deposit\n",
+ TALER_amount2s (&bdc->accumulated_total_without_fee));
+ ret = cb (cb_cls,
+ &bdc->accumulated_total_without_fee,
+ bdc->exchange_timestamp.abs_time);
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ qs = TEH_plugin->select_deposit_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &bdc->bd.wire_target_h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this deposit and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+ return qs;
+}
+
+
+/**
+ * Run KYC check.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_kyc (struct BatchDepositContext *bdc)
+{
+ if (GNUNET_YES != TEH_enable_kyc)
+ {
+ bdc->phase++;
+ return;
+ }
+ /* FIXME: this fails to check that the
+ merchant_pub used in this request
+ matches the registered public key */
+ bdc->lch = TEH_legitimization_check (
+ &bdc->rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
+ bdc->bd.receiver_wire_account,
+ &bdc->bd.wire_target_h_payto,
+ NULL,
+ &deposit_amount_cb,
+ bdc,
+ &deposit_legi_cb,
+ bdc);
+ GNUNET_assert (NULL != bdc->lch);
+ GNUNET_CONTAINER_DLL_insert (bdc_head,
+ bdc_tail,
+ bdc);
+ MHD_suspend_connection (bdc->rc->connection);
+ bdc->phase = BDC_PHASE_SUSPENDED;
+}
+
+
+/**
+ * Handle policy.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_policy (struct BatchDepositContext *bdc)
+{
+ const char *error_hint = NULL;
+
+ if (bdc->has_no_policy)
+ {
+ bdc->phase++;
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_extensions_create_policy_details (
+ TEH_currency,
+ bdc->policy_json,
+ &bdc->policy_details,
+ &error_hint))
+ {
+ GNUNET_break_op (0);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
+ error_hint));
+ return;
+ }
+
+ TALER_deposit_policy_hash (bdc->policy_json,
+ &bdc->h_policy);
+ bdc->phase++;
+}
+
+
+/**
* Parse per-coin deposit information from @a jcoin
* into @a deposit. Fill in generic information from
* @a ctx.
*
- * @param connection connection we are handling
- * @param dc information about the overall batch
+ * @param bdc information about the overall batch
* @param jcoin coin data to parse
* @param[out] cdi where to store the result
* @param[out] deposit_fee where to write the deposit fee
@@ -308,13 +689,12 @@ batch_deposit_transaction (void *cls,
* #GNUNET_SYSERR on failure and no error could be returned
*/
static enum GNUNET_GenericReturnValue
-parse_coin (struct MHD_Connection *connection,
- const struct BatchDepositContext *dc,
+parse_coin (const struct BatchDepositContext *bdc,
json_t *jcoin,
struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
struct TALER_Amount *deposit_fee)
{
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("contribution",
TEH_currency,
@@ -336,7 +716,7 @@ parse_coin (struct MHD_Connection *connection,
enum GNUNET_GenericReturnValue res;
if (GNUNET_OK !=
- (res = TALER_MHD_parse_json_data (connection,
+ (res = TALER_MHD_parse_json_data (bdc->rc->connection,
jcoin,
spec)))
return res;
@@ -345,9 +725,10 @@ parse_coin (struct MHD_Connection *connection,
struct TEH_DenominationKey *dk;
MHD_RESULT mret;
- dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
- connection,
- &mret);
+ dk = TEH_keys_denomination_by_hash (
+ &cdi->coin.denom_pub_hash,
+ bdc->rc->connection,
+ &mret);
if (NULL == dk)
{
GNUNET_JSON_parse_free (spec);
@@ -361,10 +742,11 @@ parse_coin (struct MHD_Connection *connection,
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
- NULL))
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+ NULL))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -374,7 +756,7 @@ parse_coin (struct MHD_Connection *connection,
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
+ bdc->rc->connection,
&cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
"DEPOSIT"))
@@ -387,7 +769,7 @@ parse_coin (struct MHD_Connection *connection,
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
+ bdc->rc->connection,
&cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
"DEPOSIT"))
@@ -400,7 +782,7 @@ parse_coin (struct MHD_Connection *connection,
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
+ bdc->rc->connection,
&cdi->coin.denom_pub_hash,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
"DEPOSIT"))
@@ -413,10 +795,11 @@ parse_coin (struct MHD_Connection *connection,
/* denomination cipher and denomination signature cipher not the same */
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL))
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -441,10 +824,11 @@ parse_coin (struct MHD_Connection *connection,
TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL))
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -455,10 +839,11 @@ parse_coin (struct MHD_Connection *connection,
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
- NULL))
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+ NULL))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -468,13 +853,13 @@ parse_coin (struct MHD_Connection *connection,
TALER_wallet_deposit_verify (
&cdi->amount_with_fee,
deposit_fee,
- &dc->h_wire,
+ &bdc->h_wire,
&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,
+ NULL != bdc->policy_json ? &bdc->h_policy : NULL,
&cdi->coin.denom_pub_hash,
bd->wallet_timestamp,
&bd->merchant_pub,
@@ -485,10 +870,11 @@ parse_coin (struct MHD_Connection *connection,
TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
GNUNET_JSON_parse_free (spec);
return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
- TALER_B2S (&cdi->coin.coin_pub)))
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
+ TALER_B2S (&cdi->coin.coin_pub)))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -496,15 +882,19 @@ parse_coin (struct MHD_Connection *connection,
}
-MHD_RESULT
-TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[])
+/**
+ * Run processing phase that parses the request.
+ *
+ * @param[in,out] bdc request context
+ * @param root JSON object that was POSTed
+ */
+static void
+bdc_phase_parse (struct BatchDepositContext *bdc,
+ const json_t *root)
{
- struct MHD_Connection *connection = rc->connection;
- struct BatchDepositContext dc = { 0 };
- struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
+ struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
const json_t *coins;
+ const json_t *policy_json;
bool no_refund_deadline = true;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_payto_uri ("merchant_payto_uri",
@@ -522,9 +912,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
GNUNET_JSON_spec_array_const ("coins",
&coins),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("policy",
- &dc.policy_json),
- &dc.has_no_policy),
+ GNUNET_JSON_spec_object_const ("policy",
+ &policy_json),
+ &bdc->has_no_policy),
GNUNET_JSON_spec_timestamp ("timestamp",
&bd->wallet_timestamp),
GNUNET_JSON_spec_mark_optional (
@@ -536,24 +926,31 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
GNUNET_JSON_spec_end ()
};
- (void) args;
{
enum GNUNET_GenericReturnValue res;
- res = TALER_MHD_parse_json_data (connection,
+ res = TALER_MHD_parse_json_data (bdc->rc->connection,
root,
spec);
if (GNUNET_SYSERR == res)
{
+ /* hard failure */
GNUNET_break (0);
- return MHD_NO; /* hard failure */
+ finish_loop (bdc,
+ MHD_NO);
+ return;
}
if (GNUNET_NO == res)
{
+ /* failure */
GNUNET_break_op (0);
- return MHD_YES; /* failure */
+ finish_loop (bdc,
+ MHD_YES);
+ return;
}
}
+ bdc->policy_json
+ = json_incref ((json_t *) policy_json);
/* validate merchant's wire details (as far as we can) */
{
@@ -566,12 +963,14 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
- ret = TALER_MHD_reply_with_error (connection,
+ ret = TALER_MHD_reply_with_error (bdc->rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
emsg);
GNUNET_free (emsg);
- return ret;
+ finish_loop (bdc,
+ ret);
+ return;
}
}
if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
@@ -580,156 +979,183 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
{
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);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
+ NULL));
+ return;
}
if (GNUNET_TIME_absolute_is_never (bd->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);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
+ NULL));
+ return;
}
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));
+ &bdc->h_wire);
- /* handle policy, if present */
- 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);
- }
bd->num_cdis = json_array_size (coins);
if (0 == bd->num_cdis)
{
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,
- "coins");
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coins"));
+ return;
}
if (TALER_MAX_FRESH_COINS < bd->num_cdis)
{
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,
- "coins");
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coins"));
+ return;
}
+ bdc->cdis
+ = GNUNET_new_array (bd->num_cdis,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+ bdc->deposit_fees
+ = GNUNET_new_array (bd->num_cdis,
+ struct TALER_Amount);
+ bd->cdis = bdc->cdis;
+ for (unsigned i = 0; i<bd->num_cdis; i++)
{
- struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
- GNUNET_NZL (bd->num_cdis)];
- struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount amount_without_fee;
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,
- &dc,
- json_array_get (coins,
- i),
- &cdis[i],
- &deposit_fees[i]);
- if (GNUNET_OK != res)
- break;
- 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));
- }
+ res = parse_coin (bdc,
+ json_array_get (coins,
+ i),
+ &bdc->cdis[i],
+ &bdc->deposit_fees[i]);
if (GNUNET_OK != res)
{
- for (unsigned int j = 0; j<i; j++)
- TALER_denom_sig_free (&cdis[j].coin.denom_sig);
- GNUNET_JSON_parse_free (spec);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ finish_loop (bdc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
}
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (
+ &amount_without_fee,
+ &bdc->cdis[i].amount_with_fee,
+ &bdc->deposit_fees[i]));
- 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");
- }
+ GNUNET_assert (0 <=
+ TALER_amount_add (
+ &bdc->accumulated_total_without_fee,
+ &bdc->accumulated_total_without_fee,
+ &amount_without_fee));
+ }
- /* 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;
- }
- }
+ GNUNET_JSON_parse_free (spec);
+ bdc->phase++;
+}
- /* generate regular response */
- {
- 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;
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+bdc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct BatchDepositContext *bdc = rc->rh_ctx;
+
+ if (NULL != bdc->lch)
+ {
+ TEH_legitimization_check_cancel (bdc->lch);
+ bdc->lch = NULL;
+ }
+ for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
+ TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig);
+ GNUNET_free (bdc->cdis);
+ GNUNET_free (bdc->deposit_fees);
+ json_decref (bdc->policy_json);
+ GNUNET_free (bdc);
+}
+
+
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct BatchDepositContext *bdc = rc->rh_ctx;
+
+ (void) args;
+ if (NULL == bdc)
+ {
+ bdc = GNUNET_new (struct BatchDepositContext);
+ bdc->rc = rc;
+ rc->rh_ctx = bdc;
+ rc->rh_cleaner = &bdc_cleaner;
+ bdc->phase = BDC_PHASE_PARSE;
+ bdc->exchange_timestamp = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &bdc->accumulated_total_without_fee));
+ }
+ while (1)
+ {
+ switch (bdc->phase)
+ {
+ case BDC_PHASE_INIT:
+ GNUNET_break (0);
+ bdc->phase = BDC_PHASE_RETURN_NO;
+ break;
+ case BDC_PHASE_PARSE:
+ bdc_phase_parse (bdc,
+ root);
+ break;
+ case BDC_PHASE_POLICY:
+ bdc_phase_policy (bdc);
+ break;
+ case BDC_PHASE_KYC:
+ bdc_phase_kyc (bdc);
+ break;
+ case BDC_PHASE_TRANSACT:
+ bdc_phase_transact (bdc);
+ break;
+ case BDC_PHASE_REPLY_SUCCESS:
+ bdc_phase_reply_success (bdc);
+ break;
+ case BDC_PHASE_SUSPENDED:
+ return MHD_YES;
+ case BDC_PHASE_CHECK_KYC_RESULT:
+ bdc_phase_check_kyc_result (bdc);
+ break;
+ case BDC_PHASE_GENERATE_REPLY_FAILURE:
+ return MHD_queue_response (bdc->rc->connection,
+ bdc->http_status,
+ bdc->response);
+ case BDC_PHASE_RETURN_YES:
+ return MHD_YES;
+ case BDC_PHASE_RETURN_NO:
+ return MHD_NO;
}
}
}
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.h b/src/exchange/taler-exchange-httpd_batch-deposit.h
index 187fb9f20..b5b7b0a32 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.h
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.h
@@ -29,6 +29,14 @@
/**
+ * Resumes all suspended batch deposit requests
+ * during cleanup.
+ */
+void
+TEH_batch_deposit_cleanup (void);
+
+
+/**
* Handle a "/batch-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
diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h
index 036409778..486a8b83b 100644
--- a/src/exchange/taler-exchange-httpd_config.h
+++ b/src/exchange/taler-exchange-httpd_config.h
@@ -41,7 +41,7 @@
*
* Returned via both /config and /keys endpoints.
*/
-#define EXCHANGE_PROTOCOL_VERSION "20:0:3"
+#define EXCHANGE_PROTOCOL_VERSION "21:0:4"
/**
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index ab9e666ca..a81c2d722 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -124,6 +124,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_select_withdraw_amounts_for_kyc_check.h pg_select_withdraw_amounts_for_kyc_check.c \
pg_select_merge_amounts_for_kyc_check.h pg_select_merge_amounts_for_kyc_check.c \
pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \
+ pg_select_deposit_amounts_for_kyc_check.h pg_select_deposit_amounts_for_kyc_check.c \
pg_lookup_aml_history.h pg_lookup_aml_history.c \
pg_lookup_kyc_history.h pg_lookup_kyc_history.c \
pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
diff --git a/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c
new file mode 100644
index 000000000..c34fd1da5
--- /dev/null
+++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.c
@@ -0,0 +1,153 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_deposit_amounts_for_kyc_check.c
+ * @brief Implementation of the select_deposit_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_deposit_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_deposit_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_relevant_deposit_events",
+ "SELECT"
+ " cd.amount_with_fee AS amount"
+ ",bd.exchange_timestamp AS date"
+ " FROM batch_deposits bd"
+ " JOIN coin_deposits cd"
+ " USING (batch_deposit_serial_id)"
+ " WHERE wire_target_h_payto=$1"
+ " AND bd.exchange_timestamp >= $2"
+ " ORDER BY bd.exchange_timestamp DESC");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_deposit_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h
new file mode 100644
index 000000000..7e4ab31b2
--- /dev/null
+++ b/src/exchangedb/pg_select_deposit_amounts_for_kyc_check.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_deposit_amounts_for_kyc_check.h
+ * @brief implementation of the select_deposit_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_DEPOSIT_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on deposited amounts after @a time_limit which are relevant for a
+ * KYC trigger for a merchant identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount,
+ * in reverse chronological order (or until @a kac aborts
+ * by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_deposit_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
index 417d78ec7..8df91a398 100644
--- a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
@@ -131,7 +131,6 @@ TEH_PG_select_merge_amounts_for_kyc_check (
};
enum GNUNET_DB_QueryStatus qs;
-
PREPARE (pg,
"select_kyc_relevant_merge_events",
"SELECT"
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index c13a7d193..3915e3a46 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -55,6 +55,7 @@
#include "pg_lookup_records_by_table.h"
#include "pg_lookup_kyc_status_by_token.h"
#include "pg_lookup_serial_by_table.h"
+#include "pg_select_deposit_amounts_for_kyc_check.h"
#include "pg_lookup_pending_legitimization.h"
#include "pg_lookup_completed_legitimization.h"
#include "pg_lookup_active_legitimization.h"
@@ -741,6 +742,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_get_wire_fees;
plugin->select_aml_decisions
= &TEH_PG_select_aml_decisions;
+ plugin->select_deposit_amounts_for_kyc_check
+ = &TEH_PG_select_deposit_amounts_for_kyc_check;
plugin->insert_signkey_revocation
= &TEH_PG_insert_signkey_revocation;
plugin->select_aml_attributes
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index f4b5d8556..278994287 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -7164,6 +7164,28 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * Call @a kac on deposited amounts after @a time_limit which are relevant for a
+ * KYC trigger for a merchant identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount,
+ * in reverse chronological order (or until @a kac aborts
+ * by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_deposit_amounts_for_kyc_check)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+ /**
* Store automated legitimization outcome.
*
* @param cls closure
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 7ea6b2b82..5f5b45f92 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -2777,6 +2777,12 @@ TALER_KYCLOGIC_kyc_test_required (
rule->timeframe);
}
+ if (! have_threshold)
+ {
+ *triggered_rule = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
{
struct GNUNET_TIME_Absolute now
= GNUNET_TIME_absolute_get ();
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 5890fa75e..329e2c2e6 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -40,12 +40,12 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define EXCHANGE_PROTOCOL_CURRENT 19
+#define EXCHANGE_PROTOCOL_CURRENT 21
/**
* How many versions are we backwards compatible with?
*/
-#define EXCHANGE_PROTOCOL_AGE 2
+#define EXCHANGE_PROTOCOL_AGE 4
/**
* Set to 1 for extra debug logging.
@@ -975,7 +975,7 @@ decode_keys_json (const json_t *resp_obj,
EXITIF (1);
}
{
- const json_t *hard_limits;
+ const json_t *hard_limits = NULL;
struct GNUNET_JSON_Specification sspec[] = {
TALER_JSON_spec_currency_specification (
"currency_specification",
@@ -989,9 +989,11 @@ decode_keys_json (const json_t *resp_obj,
"stefan_log",
currency,
&key_data->stefan_log),
- GNUNET_JSON_spec_array_const (
- "hard_limits",
- &hard_limits),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "hard_limits",
+ &hard_limits),
+ NULL),
GNUNET_JSON_spec_double (
"stefan_lin",
&key_data->stefan_lin),
@@ -1010,9 +1012,10 @@ decode_keys_json (const json_t *resp_obj,
eline);
EXITIF (1);
}
- if (GNUNET_OK !=
- parse_hard_limits (hard_limits,
- key_data))
+ if ( (NULL != hard_limits) &&
+ (GNUNET_OK !=
+ parse_hard_limits (hard_limits,
+ key_data)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Parsing hard limits of /keys failed\n");