merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/backenddb/insert_transfer_details.sql | 8+++++++-
Msrc/backenddb/test_merchantdb.c | 47++++++++++++++++++++++++++++++++++++++++++++++-
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; }