[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-exchange] 01/02: add idempotency check for batch-deposit on kyc f
From: |
gnunet |
Subject: |
[taler-exchange] 01/02: add idempotency check for batch-deposit on kyc failure |
Date: |
Sun, 01 Sep 2024 17:10:44 +0200 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository exchange.
commit 01373461c56e6561ed066c9d4b492cd9091929c1
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Sep 1 16:50:30 2024 +0200
add idempotency check for batch-deposit on kyc failure
---
src/exchange/taler-exchange-httpd_batch-deposit.c | 52 ++++-----
src/exchangedb/Makefile.am | 1 +
.../exchange_do_check_deposit_idempotent.sql | 123 +++++++++++++++++++++
src/exchangedb/perf_deposits_get_ready.c | 3 +-
src/exchangedb/perf_select_refunds_by_coin.c | 2 +-
src/exchangedb/pg_do_check_deposit_idempotent.c | 112 +++++++++++++++++++
src/exchangedb/pg_do_check_deposit_idempotent.h | 45 ++++++++
src/exchangedb/plugin_exchangedb_postgres.c | 3 +
src/exchangedb/procedures.sql.in | 1 +
src/include/taler_exchangedb_plugin.h | 17 +++
10 files changed, 324 insertions(+), 35 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c
b/src/exchange/taler-exchange-httpd_batch-deposit.c
index b18acd8aa..9cf58e4a5 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.c
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -451,43 +451,31 @@ static bool
check_request_idempotent (
struct BatchDepositContext *bdc)
{
-#if FIXME_PLACEHOLDER
const struct TEH_RequestContext *rc = bdc->rc;
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_idempotent;
- for (unsigned int i = 0; i<bwc->planchets_length; i++)
+ qs = TEH_plugin->do_check_deposit_idempotent (
+ TEH_plugin->cls,
+ &bdc->bd,
+ &bdc->exchange_timestamp,
+ &is_idempotent);
+ if (0 > qs)
{
- 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;
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_withdraw_info"));
+ return true;
}
- /* generate idempotent reply */
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
- bwc->phase = BDC_PHASE_GENERATE_REPLY_SUCCESS;
+ if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (! is_idempotent) )
+ return false;
+ bdc->phase = BDC_PHASE_REPLY_SUCCESS;
return true;
-#else
- GNUNET_break (0); // NOT IMPLEMENTED
- return false;
-#endif
}
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index a81c2d722..724370efb 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -130,6 +130,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
pg_get_drain_profit.h pg_get_drain_profit.c \
pg_get_purse_deposit.h pg_get_purse_deposit.c \
+ pg_do_check_deposit_idempotent.h pg_do_check_deposit_idempotent.c \
pg_insert_contract.h pg_insert_contract.c \
pg_select_contract.h pg_select_contract.c \
pg_select_purse_merge.h pg_select_purse_merge.c \
diff --git a/src/exchangedb/exchange_do_check_deposit_idempotent.sql
b/src/exchangedb/exchange_do_check_deposit_idempotent.sql
new file mode 100644
index 000000000..1e7414dc0
--- /dev/null
+++ b/src/exchangedb/exchange_do_check_deposit_idempotent.sql
@@ -0,0 +1,123 @@
+--
+-- 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/>
+--
+CREATE OR REPLACE FUNCTION exchange_do_check_deposit_idempotent(
+ -- For batch_deposits
+ IN in_shard INT8,
+ IN in_merchant_pub BYTEA,
+ IN in_wallet_timestamp INT8,
+ IN in_exchange_timestamp INT8,
+ IN in_refund_deadline INT8,
+ IN in_wire_deadline INT8,
+ IN in_h_contract_terms BYTEA,
+ IN in_wallet_data_hash BYTEA, -- can be NULL
+ IN in_wire_salt BYTEA,
+ IN in_wire_target_h_payto BYTEA,
+ IN in_policy_details_serial_id INT8, -- can be NULL
+ IN in_policy_blocked BOOLEAN,
+ -- For wire_targets
+ IN in_receiver_wire_account TEXT,
+ -- For coin_deposits
+ IN ina_coin_pub BYTEA[],
+ IN ina_coin_sig BYTEA[],
+ IN ina_amount_with_fee taler_amount[],
+ OUT out_exchange_timestamp INT8,
+ OUT out_is_idempotent BOOL
+ )
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ wtsi INT8; -- wire target serial id
+ bdsi INT8; -- batch_deposits serial id
+ i INT4;
+ ini_amount_with_fee taler_amount;
+ ini_coin_pub BYTEA;
+ ini_coin_sig BYTEA;
+BEGIN
+-- Shards:
+-- SELECT wire_targets (by h_payto);
+-- INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT
idempotency check;
+-- PERFORM[] coin_deposits (by coin_pub), ON CONFLICT idempotency
check;
+
+out_exchange_timestamp = in_exchange_timestamp;
+
+-- First, get the 'wtsi'
+SELECT wire_target_serial_id
+ INTO wtsi
+ FROM wire_targets
+ WHERE wire_target_h_payto=in_wire_target_h_payto
+ AND payto_uri=in_receiver_wire_account;
+
+IF NOT FOUND
+THEN
+ out_is_idempotent = FALSE;
+ RETURN;
+END IF;
+
+
+-- Idempotency check: see if an identical record exists.
+-- We do select over merchant_pub, h_contract_terms and wire_target_h_payto
+-- first to maximally increase the chance of using the existing index.
+SELECT
+ exchange_timestamp
+ ,batch_deposit_serial_id
+ INTO
+ out_exchange_timestamp
+ ,bdsi
+ FROM batch_deposits
+ WHERE shard=in_shard
+ AND merchant_pub=in_merchant_pub
+ AND h_contract_terms=in_h_contract_terms
+ AND wire_target_h_payto=in_wire_target_h_payto
+ -- now check the rest, too
+ AND ( (wallet_data_hash=in_wallet_data_hash) OR
+ (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
+ AND wire_salt=in_wire_salt
+ AND wallet_timestamp=in_wallet_timestamp
+ AND refund_deadline=in_refund_deadline
+ AND wire_deadline=in_wire_deadline
+ AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
+ (policy_details_serial_id IS NULL AND in_policy_details_serial_id IS
NULL) );
+
+IF NOT FOUND
+THEN
+ out_is_idempotent=FALSE;
+ RETURN;
+END IF;
+
+
+-- Check each coin
+
+FOR i IN 1..array_length(ina_coin_pub,1)
+LOOP
+ ini_coin_pub = ina_coin_pub[i];
+ ini_coin_sig = ina_coin_sig[i];
+ ini_amount_with_fee = ina_amount_with_fee[i];
+
+ PERFORM FROM coin_deposits
+ WHERE batch_deposit_serial_id=bdsi
+ AND coin_pub=ini_coin_pub
+ AND coin_sig=ini_coin_sig
+ AND amount_with_fee=ini_amount_with_fee;
+ IF NOT FOUND
+ THEN
+ out_is_idempotent=FALSE;
+ RETURN;
+ END IF;
+END LOOP; -- end FOR all coins
+
+out_is_idempotent=TRUE;
+
+END $$;
diff --git a/src/exchangedb/perf_deposits_get_ready.c
b/src/exchangedb/perf_deposits_get_ready.c
index 005ea6843..1cb8c595a 100644
--- a/src/exchangedb/perf_deposits_get_ready.c
+++ b/src/exchangedb/perf_deposits_get_ready.c
@@ -68,8 +68,6 @@ static int result;
*/
static struct TALER_EXCHANGEDB_Plugin *plugin;
-static struct TALER_DenomFeeSet fees;
-
static struct TALER_MerchantWireHashP h_wire_wt;
/**
@@ -203,6 +201,7 @@ run (void *cls)
unsigned int *perm;
unsigned long long duration_sq;
struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct TALER_DenomFeeSet fees;
struct GNUNET_CRYPTO_BlindingInputValues bi = {
.cipher = GNUNET_CRYPTO_BSA_RSA,
.rc = 0
diff --git a/src/exchangedb/perf_select_refunds_by_coin.c
b/src/exchangedb/perf_select_refunds_by_coin.c
index 84825d6d7..6b9592919 100644
--- a/src/exchangedb/perf_select_refunds_by_coin.c
+++ b/src/exchangedb/perf_select_refunds_by_coin.c
@@ -67,7 +67,6 @@ static int result;
*/
static struct TALER_EXCHANGEDB_Plugin *plugin;
-static struct TALER_DenomFeeSet fees;
static struct TALER_MerchantWireHashP h_wire_wt;
@@ -231,6 +230,7 @@ run (void *cls)
unsigned long long duration_sq;
struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ struct TALER_DenomFeeSet fees;
unsigned int count = 0;
ref = GNUNET_new_array (ROUNDS + 1,
diff --git a/src/exchangedb/pg_do_check_deposit_idempotent.c
b/src/exchangedb/pg_do_check_deposit_idempotent.c
new file mode 100644
index 000000000..8a15200e0
--- /dev/null
+++ b/src/exchangedb/pg_do_check_deposit_idempotent.c
@@ -0,0 +1,112 @@
+/*
+ This file is part of TALER
+ 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 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_do_deposit.c
+ * @brief Implementation of the do_deposit 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_do_check_deposit_idempotent.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_check_deposit_idempotent (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *is_idempotent)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (&bd->merchant_pub);
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (bd->num_cdis)];
+ const struct TALER_CoinSpendSignatureP *coin_sigs[GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount amounts_with_fee[GNUNET_NZL (bd->num_cdis)];
+ struct GNUNET_PQ_QueryParam params[] = {
+ /* data for batch_deposits */
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_auto_from_type (&bd->merchant_pub),
+ GNUNET_PQ_query_param_timestamp (&bd->wallet_timestamp),
+ GNUNET_PQ_query_param_timestamp (exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (&bd->refund_deadline),
+ GNUNET_PQ_query_param_timestamp (&bd->wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (&bd->h_contract_terms),
+ (bd->no_wallet_data_hash)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (&bd->wallet_data_hash),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_salt),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_target_h_payto),
+ (0 == bd->policy_details_serial_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&bd->policy_details_serial_id),
+ GNUNET_PQ_query_param_bool (bd->policy_blocked),
+ /* to create entry in wire_targets */
+ GNUNET_PQ_query_param_string (bd->receiver_wire_account),
+ /* arrays for coin_deposits */
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_sigs,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (bd->num_cdis,
+ amounts_with_fee,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_time;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ &no_time),
+ GNUNET_PQ_result_spec_bool ("is_idempotent",
+ is_idempotent),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int i = 0; i < bd->num_cdis; i++)
+ {
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bd->cdis[i];
+
+ amounts_with_fee[i] = cdi->amount_with_fee;
+ coin_pubs[i] = &cdi->coin.coin_pub;
+ coin_sigs[i] = &cdi->csig;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do deposit %u = %s\n",
+ i,
+ TALER_B2S (&cdi->coin.coin_pub));
+ }
+ PREPARE (pg,
+ "call_check_deposit_idempotent",
+ "SELECT "
+ " out_exchange_timestamp AS exchange_timestamp"
+ ",out_is_idempotent AS is_idempotent"
+ " FROM exchange_do_check_deposit_idempotent"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+
"call_check_deposit_idempotent",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_check_deposit_idempotent.h
b/src/exchangedb/pg_do_check_deposit_idempotent.h
new file mode 100644
index 000000000..8aa00bba6
--- /dev/null
+++ b/src/exchangedb/pg_do_check_deposit_idempotent.h
@@ -0,0 +1,45 @@
+/*
+ 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_do_check_deposit_idempotent.h
+ * @brief implementation of the do_check_deposit_idempotent function for
Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_CHECK_DEPOSIT_IDEMPOTENT_H
+#define PG_DO_CHECK_DEPOSIT_IDEMPOTENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Check ifdeposit operation is idempotent to existing one.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bd batch deposit operation details
+ * @param[in,out] exchange_timestamp time to use for the deposit (possibly
updated)
+ * @param[out] is_idempotent set to true if the request is idempotent
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_check_deposit_idempotent (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *is_idempotent);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c
b/src/exchangedb/plugin_exchangedb_postgres.c
index 3915e3a46..00c4d519d 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -38,6 +38,7 @@
#include "pg_delete_aggregation_transient.h"
#include "pg_get_link_data.h"
#include "pg_helper.h"
+#include "pg_do_check_deposit_idempotent.h"
#include "pg_do_reserve_open.h"
#include "pg_get_coin_transactions.h"
#include "pg_get_expired_reserves.h"
@@ -744,6 +745,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_select_aml_decisions;
plugin->select_deposit_amounts_for_kyc_check
= &TEH_PG_select_deposit_amounts_for_kyc_check;
+ plugin->do_check_deposit_idempotent
+ = &TEH_PG_do_check_deposit_idempotent;
plugin->insert_signkey_revocation
= &TEH_PG_insert_signkey_revocation;
plugin->select_aml_attributes
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
index 5339bbb7c..b7c29ae7f 100644
--- a/src/exchangedb/procedures.sql.in
+++ b/src/exchangedb/procedures.sql.in
@@ -27,6 +27,7 @@ SET search_path TO exchange;
#include "exchange_do_batch_withdraw_insert.sql"
#include "exchange_do_age_withdraw.sql"
#include "exchange_do_deposit.sql"
+#include "exchange_do_check_deposit_idempotent.sql"
#include "exchange_do_melt.sql"
#include "exchange_do_select_deposits_missing_wire.sql"
#include "exchange_do_select_justification_for_missing_wire.sql"
diff --git a/src/include/taler_exchangedb_plugin.h
b/src/include/taler_exchangedb_plugin.h
index 278994287..0b0e5fb0b 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -4217,6 +4217,23 @@ struct TALER_EXCHANGEDB_Plugin
bool *ctr_conflict);
+ /**
+ * Check ifdeposit operation is idempotent to existing one.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bd batch deposit operation details
+ * @param[in,out] exchange_timestamp time to use for the deposit (possibly
updated)
+ * @param[out] is_idempotent set to true if the request is idempotent
+ * @return query execution status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*do_check_deposit_idempotent)(
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *is_idempotent);
+
+
/**
* Perform melt operation, checking for sufficient balance
* of the coin and possibly persisting the melt details.
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.