From dd07aa2b8270c74bf73a648f8877596d7cd5a29c Mon Sep 17 00:00:00 2001 From: amackillop Date: Tue, 20 Jan 2026 07:35:37 -0800 Subject: [PATCH] Fix receive_payment exiting before claims complete The receive_payment function would exit based solely on time thresholds, even when PaymentClaimable events were pending. This caused payments to get stuck if the node shut down after receiving the HTLC but before claim_funds() completed. Track pending claims using a HashSet and wait for PaymentReceived or PaymentFailed events before allowing shutdown. Add a 60-second hard timeout with warning to prevent infinite blocking. --- src/lib.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d5b9874..630a113 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![deny(clippy::all)] use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, convert::TryFrom, fmt::{self, Write}, str::FromStr, @@ -39,6 +39,7 @@ use ldk_node::{ util::scid_utils, }, lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description}, + lightning_types::payment::PaymentHash, }; use tokio::runtime::Runtime; @@ -439,7 +440,11 @@ impl MdkNode { min_threshold_ms: i64, quiet_threshold_ms: i64, ) -> Vec { + // Hard timeout to prevent infinite wait if claims get stuck (60 seconds) + const HARD_TIMEOUT_MS: i64 = 60_000; + let mut received_payments = vec![]; + let mut pending_claims: HashSet = HashSet::new(); if let Err(err) = self.node.start() { eprintln!("[lightning-js] Failed to start node in receive_payment: {err}"); @@ -460,7 +465,26 @@ impl MdkNode { let total_time_elapsed = now.duration_since(start_sync_at).as_millis() as i64; let quiet_time_elapsed = now.duration_since(last_event_time).as_millis() as i64; - if total_time_elapsed >= min_threshold_ms && quiet_time_elapsed >= quiet_threshold_ms { + let can_exit = pending_claims.is_empty() + && total_time_elapsed >= min_threshold_ms + && quiet_time_elapsed >= quiet_threshold_ms; + + if can_exit { + break; + } + + if total_time_elapsed >= HARD_TIMEOUT_MS { + if !pending_claims.is_empty() { + eprintln!( + "[lightning-js] WARNING: Exiting receive_payment with {} pending claims after hard timeout ({}ms): {:?}", + pending_claims.len(), + HARD_TIMEOUT_MS, + pending_claims + .iter() + .map(|h| bytes_to_hex(&h.0)) + .collect::>() + ); + } break; } @@ -489,6 +513,9 @@ impl MdkNode { eprintln!( "[lightning-js] PaymentFailed payment_id={payment_id_hex} payment_hash={payment_hash_hex} reason={reason_str}", ); + if let Some(hash) = payment_hash { + pending_claims.remove(hash); + } } Event::PaymentClaimable { payment_hash, @@ -505,6 +532,7 @@ impl MdkNode { eprintln!( "[lightning-js] PaymentClaimable payment_hash={payment_hash_hex} claimable_amount_msat={claimable_amount_msat} claim_deadline={claim_deadline_str}", ); + pending_claims.insert(*payment_hash); } Event::PaymentReceived { payment_hash, @@ -515,6 +543,7 @@ impl MdkNode { eprintln!( "[lightning-js] PaymentReceived payment_hash={payment_hash_hex} amount_msat={amount_msat}", ); + pending_claims.remove(payment_hash); } _ => {} }