commit b63506baec3d85ca5db57b00cbedd3f522c80caa
parent c477fbaaeade3a546f2aa556c8716e5349b50670
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 5 Aug 2026 22:39:36 +0200
handle case where exchange sends the same coin twice in the same response (ignore duplicate)
Diffstat:
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/src/backenddb/insert_transfer_details.sql b/src/backenddb/insert_transfer_details.sql
@@ -167,7 +167,13 @@ LOOP
JOIN merchant_contract_terms cterm
USING (order_serial)
WHERE dep.coin_pub=ini_coin_pub
- AND cterm.h_contract_terms=ini_contract_term;
+ AND cterm.h_contract_terms=ini_contract_term
+ -- 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
+ -- deposit_serial): keep the first association we saw instead of
+ -- failing the entire transaction.
+ ON CONFLICT (deposit_serial) DO NOTHING;
RAISE NOTICE 'iterating over affected orders';
OPEN curs (arg_coin_pub:=ini_coin_pub);
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -5361,6 +5361,12 @@ struct TestTransfers_Closure
struct DepositData deposit;
/**
+ * A second deposit for the same order, used to test an
+ * exchange response that lists the same coin twice.
+ */
+ struct DepositData deposit2;
+
+ /**
* Wire fee data.
*/
struct WireFeeData wire_fee[2];
@@ -5368,7 +5374,7 @@ struct TestTransfers_Closure
/**
* The transfers.
*/
- struct TransferData transfers[2];
+ struct TransferData transfers[3];
};
@@ -5423,6 +5429,23 @@ pre_test_transfers (struct TestTransfers_Closure *cls)
&cls->deposit,
&cls->transfers[1]);
cls->transfers[1].confirmed = true;
+ /* A transfer where the exchange lists the same coin twice. */
+ make_deposit (&cls->instance,
+ &cls->account,
+ &cls->order,
+ &cls->signkey,
+ &cls->deposit2);
+ {
+ struct DepositData dup[2];
+
+ dup[0] = cls->deposit2;
+ dup[1] = cls->deposit2;
+ make_transfer (&cls->signkey,
+ 2,
+ dup,
+ &cls->transfers[2]);
+ }
+ cls->transfers[2].confirmed = true;
}
@@ -5437,6 +5460,9 @@ post_test_transfers (struct TestTransfers_Closure *cls)
GNUNET_array_grow (cls->transfers->data.details,
cls->transfers->data.details_length,
0);
+ GNUNET_array_grow (cls->transfers[2].data.details,
+ cls->transfers[2].data.details_length,
+ 0);
free_instance_data (&cls->instance);
free_order_data (&cls->order);
}
@@ -5594,6 +5620,25 @@ run_test_transfers (struct TestTransfers_Closure *cls)
&cls->account,
&cls->transfers[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* The exchange may list the same coin twice in one response;
+ merchant_expected_transfer_to_coin is UNIQUE on deposit_serial,
+ so this used to be a hard error killing the reconciliation
+ daemon for all instances. */
+ TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+ &cls->signkey,
+ &cls->deposit2,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
+ &cls->signkey,
+ &cls->order,
+ &cls->deposit2,
+ &cls->transfers[2],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false));
+ TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
+ &cls->account,
+ &cls->transfers[2],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
return 0;
}