merchant

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

commit c477fbaaeade3a546f2aa556c8716e5349b50670
parent b6cf0226759a12eb383fa9be0b62d6d2efbd787d
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  5 Aug 2026 22:38:40 +0200

handle case where exchange returns an empty list of deposits

Diffstat:
Msrc/backenddb/insert_transfer_details.c | 9+++++++++
Msrc/backenddb/insert_transfer_details.sql | 5++++-
Msrc/backenddb/test_merchantdb.c | 25++++++++++++++++++++++++-
3 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/src/backenddb/insert_transfer_details.c b/src/backenddb/insert_transfer_details.c @@ -52,6 +52,15 @@ TALER_MERCHANTDB_insert_transfer_details ( GNUNET_assert (NULL != pg->current_merchant_id); GNUNET_assert (0 == strcmp (instance_id, pg->current_merchant_id)); + if (0 == len) + { + /* Legal on the wire: the exchange may sign a transfer with an + empty list of deposits. Nothing to associate with the transfer, + but we still store the (signed) transfer details below. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange `%s' reported wire transfer without any deposits\n", + exchange_url); + } for (unsigned int i = 0; i<len; i++) { diff --git a/src/backenddb/insert_transfer_details.sql b/src/backenddb/insert_transfer_details.sql @@ -139,7 +139,10 @@ out_duplicate=FALSE; out_conflict=FALSE; -FOR i IN 1..array_length(ina_coin_pubs,1) +-- Note: the COALESCE is required, the exchange is allowed to +-- return an empty list of deposits for a wire transfer; without +-- it plpgsql raises 'upper bound of FOR loop cannot be null'. +FOR i IN 1..COALESCE(array_length(ina_coin_pubs,1),0) LOOP ini_coin_value=ina_coin_values[i]; ini_deposit_fee=ina_deposit_fees[i]; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -5368,7 +5368,7 @@ struct TestTransfers_Closure /** * The transfers. */ - struct TransferData transfers[1]; + struct TransferData transfers[2]; }; @@ -5417,6 +5417,12 @@ pre_test_transfers (struct TestTransfers_Closure *cls) &cls->deposit, &cls->transfers[0]); cls->transfers[0].confirmed = true; + /* A transfer the exchange reports without any deposits. */ + make_transfer (&cls->signkey, + 0, + &cls->deposit, + &cls->transfers[1]); + cls->transfers[1].confirmed = true; } @@ -5571,6 +5577,23 @@ run_test_transfers (struct TestTransfers_Closure *cls) 0, 1, &cls->transfers[0])); + /* The exchange is allowed to report a wire transfer with an empty + list of deposits; that must not fail the transaction (which used + to abort taler-merchant-reconciliation for all instances). + The deposit-to-transfer insert is what makes us expect the + transfer in the first place, so that we get past the account + lookup and actually reach the per-coin loop. */ + TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, + &cls->signkey, + &cls->order, + &cls->deposit, + &cls->transfers[1], + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, + false)); + TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance, + &cls->account, + &cls->transfers[1], + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); return 0; }