commit 8aca4ab356fa801c8c5d6ec944388f5623679ee1
parent edc43c410067887be7b67114969c5a7fe90ac582
Author: Florian Dold <dold@taler.net>
Date: Mon, 28 Sep 2026 21:34:51 +0200
merchantdb: settle orders from signed aggregate transfer details
An aggregate can arrive before depositcheck visits each included
deposit. Accept that signed evidence for the matching contract, exchange
and account while retaining permanent settlement errors.
Preserve individual deposit signatures and queue settlement
notifications only when a confirmation first becomes complete.
Diffstat:
2 files changed, 48 insertions(+), 16 deletions(-)
diff --git a/src/backenddb/insert_transfer_details.sql b/src/backenddb/insert_transfer_details.sql
@@ -42,17 +42,23 @@ AS $$
DECLARE
my_signkey_serial INT8;
my_expected_credit_serial INT8;
+ my_account_serial INT8;
my_affected_orders RECORD;
my_decose INT8;
my_order_id TEXT;
i INT8;
- curs CURSOR (arg_coin_pub BYTEA) FOR
+ curs CURSOR (arg_coin_pub BYTEA, arg_contract_term BYTEA) FOR
SELECT mcon.deposit_confirmation_serial,
mcon.order_serial
FROM merchant_deposits dep
JOIN merchant_deposit_confirmations mcon
USING (deposit_confirmation_serial)
- WHERE dep.coin_pub=arg_coin_pub;
+ JOIN merchant_contract_terms cterm
+ USING (order_serial)
+ WHERE dep.coin_pub=arg_coin_pub
+ AND cterm.h_contract_terms=arg_contract_term
+ AND mcon.exchange_url=in_exchange_url
+ AND mcon.account_serial=my_account_serial;
ini_coin_pub BYTEA;
ini_contract_term BYTEA;
ini_coin_value merchant.taler_amount_currency;
@@ -62,8 +68,8 @@ BEGIN
out_order_ids=ARRAY[]::TEXT[];
-- Determine account that was credited.
-SELECT expected_credit_serial
- INTO my_expected_credit_serial
+SELECT expected_credit_serial, account_serial
+ INTO my_expected_credit_serial, my_account_serial
FROM merchant_expected_transfers
WHERE exchange_url=in_exchange_url
AND wtid=in_wtid
@@ -172,6 +178,8 @@ LOOP
USING (order_serial)
WHERE dep.coin_pub=ini_coin_pub
AND cterm.h_contract_terms=ini_contract_term
+ AND dcon.exchange_url=in_exchange_url
+ AND dcon.account_serial=my_account_serial
-- The exchange may list the same coin more than once in one
-- response, and a coin belongs to at most one wire transfer
-- (merchant_expected_transfer_to_coin is UNIQUE on
@@ -180,7 +188,8 @@ LOOP
ON CONFLICT (deposit_serial) DO NOTHING;
RAISE NOTICE 'iterating over affected orders';
- OPEN curs (arg_coin_pub:=ini_coin_pub);
+ OPEN curs (arg_coin_pub:=ini_coin_pub,
+ arg_contract_term:=ini_contract_term);
LOOP
FETCH NEXT FROM curs INTO my_affected_orders;
EXIT WHEN NOT FOUND;
@@ -193,17 +202,37 @@ LOOP
-- wired the money to an account we do not know, EC 2558) has
-- settlement_retry_needed=FALSE and a settlement_wtid, but is
-- NOT settled: it must not make the order count as wired.
+ -- The signed transfer list is also settlement evidence. Reconciliation
+ -- can learn about every deposit in an aggregate before depositcheck has
+ -- queried them individually. Keep the distinct per-deposit signature
+ -- fields untouched, and only accept transfers to this deposit's account
+ -- from its exchange. A permanent deposit error still prevents settlement.
PERFORM FROM merchant_deposits md
+ JOIN merchant_deposit_confirmations dcon
+ USING (deposit_confirmation_serial)
WHERE md.deposit_confirmation_serial=my_decose
- AND (settlement_retry_needed
- OR (settlement_wtid IS NULL)
- OR (COALESCE(settlement_last_ec,0) <> 0) );
+ AND ((NOT COALESCE(md.settlement_retry_needed, TRUE)
+ AND COALESCE(md.settlement_last_ec, 0) <> 0)
+ OR ((COALESCE(md.settlement_retry_needed, TRUE)
+ OR md.settlement_wtid IS NULL
+ OR COALESCE(md.settlement_last_ec, 0) <> 0)
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_expected_transfer_to_coin tc
+ JOIN merchant_expected_transfers et
+ USING (expected_credit_serial)
+ JOIN merchant_transfer_signatures ts
+ USING (expected_credit_serial)
+ WHERE tc.deposit_serial=md.deposit_serial
+ AND et.exchange_url=dcon.exchange_url
+ AND et.account_serial=dcon.account_serial)));
IF NOT FOUND
THEN
-- must be all done, clear flag
UPDATE merchant_deposit_confirmations
SET wire_pending=FALSE
- WHERE (deposit_confirmation_serial=my_decose);
+ WHERE deposit_confirmation_serial=my_decose
+ AND wire_pending;
IF FOUND
THEN
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -7181,13 +7181,16 @@ run_test_transfers (struct TestTransfers_Closure *cls)
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders_agg[i],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
- &cls->signkey,
- &cls->orders_agg[i],
- &cls->deposits_agg[i],
- &cls->transfers[3],
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
- false));
+ /* Reconciliation learns about the second deposit from the signed
+ aggregate before depositcheck has queried it individually. */
+ if (0 == i)
+ TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
+ &cls->signkey,
+ &cls->orders_agg[i],
+ &cls->deposits_agg[i],
+ &cls->transfers[3],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false));
}
{
static const char *webhook_id = "test-transfer-order-settled";