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:
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;
}