commit 12b8220f543ca16c3827e2fd63fd6ae1b3b72222
parent 56fa76fc383b1808cd77c35ecc8c1df5bf175e88
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 13 Aug 2026 20:09:38 +0200
handle NULL in reserve_close operations
Diffstat:
2 files changed, 31 insertions(+), 9 deletions(-)
diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c
@@ -209,8 +209,8 @@ commit_or_warn (void)
* @param cls NULL
* @param reserve_pub public key of the reserve
* @param left amount left in the reserve
- * @param account_payto_uri information about the bank account that initially
- * caused the reserve to be created
+ * @param account_payto_uri the bank account the balance is to be wired to,
+ * NULL if no bank account is known for the reserve at all
* @param expiration_date when did the reserve expire
* @param close_request_row row of request asking for
* closure, 0 for expired reserves
@@ -242,6 +242,19 @@ expired_reserve_cb (void *cls,
GNUNET_TIME_timestamp2s (expiration_date));
now = GNUNET_TIME_timestamp_get ();
+ if (NULL == account_payto_uri.full_payto)
+ {
+ /* Neither the close request nor the reserve names a bank account, so
+ there is nowhere to wire the balance to. Skip the request -- it
+ remains marked as processed -- instead of failing the entire run,
+ which would block the closing of all other reserves indefinitely. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No bank account known for reserve %s, cannot close it;"
+ " manual intervention required\n",
+ TALER_B2S (reserve_pub));
+ return GNUNET_OK;
+ }
+
/* lookup account we should use */
wa = TALER_EXCHANGEDB_find_account_by_payto_uri (account_payto_uri);
if (NULL == wa)
diff --git a/src/exchangedb/iterate_unfinished_close_requests.c b/src/exchangedb/iterate_unfinished_close_requests.c
@@ -77,8 +77,10 @@ reserve_cb (void *cls,
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_timestamp ("expiration_date",
&exp_date),
- GNUNET_PQ_result_spec_string ("account_details",
- &account_details.full_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("account_details",
+ &account_details.full_payto),
+ NULL),
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
&reserve_pub),
TALER_PQ_RESULT_SPEC_AMOUNT ("close",
@@ -88,6 +90,9 @@ reserve_cb (void *cls,
GNUNET_PQ_result_spec_end
};
+ /* If neither the close request nor the reserve identifies a bank
+ account, the callback is given a NULL payto URI. */
+ account_details.full_payto = NULL;
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
@@ -138,11 +143,15 @@ TALER_EXCHANGEDB_iterate_unfinished_close_requests (
" ,close_request_serial_id"
" ,close_timestamp AS expiration_date"
" ,close"
- " ,(SELECT payto_uri"
- " FROM reserves_in ri"
- " JOIN wire_targets wt"
- " ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
- " WHERE ri.reserve_pub=rc.reserve_pub)"
+ /* The account designated by the client (and covered by the KYC
+ check done at the time of the request) takes precedence over
+ the account the reserve was funded from. */
+ " ,COALESCE(rc.payto_uri"
+ " ,(SELECT payto_uri"
+ " FROM reserves_in ri"
+ " JOIN wire_targets wt"
+ " ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
+ " WHERE ri.reserve_pub=rc.reserve_pub))"
" AS account_details;");
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"iterate_unfinished_close_requests",