Correct channel closed detection in payment state replay
What changed, and why it matters
This commit fixes a logic bug in the Lightning Dev Kit's channel manager. During startup, when the software replays the state of pending payments, it now assumes a channel is closed unless it finds live peer state proving otherwise. Previously it assumed the channel was open unless proven closed. The change prevents a situation where payments tied to a channel with no peer state would be treated as still active and never re-failed or re-claimed, potentially leaving funds stuck.
Review whether the missing-peer-state scenario can actually occur in production (e.g., after crashes, partial persistence, or data corruption) and add regression tests. Monitor for any reports of stuck pending payments or channels not being replayed on startup. Consider this for inclusion in a bug-fix release.
Security signals we found
Logic inversion in payment replay path
Potential stuck pending payments if peer state is missing
Funds availability / liveness issue in channel state recovery
No explicit security framing by vendor
Evidence from the diff
In ChannelManager::finish_close_channel and related payment replay logic, the local variable is_channel_closed is initialized to true instead of false. The code iterates channel monitors and looks up the counterparty’s per-peer state. If peer state exists, it checks whether the channel is in the channel_by_id map and updates is_channel_closed accordingly. If no peer state exists, the previous default of false meant the channel was treated as live, so pending payment replay was skipped. The new default of true means a missing peer state causes the channel to be treated as closed, ensuring pending payments are replayed and resolved (failed/reclaimed). The commit message notes the no-peer-state case should be unreachable at this loading stage, but the default is now conservative.
Changed components
lightning/src/ln/channelmanager.rsChannelManager payment replay / channel close detectionPer-peer state lookup during node startupInspect captured patch +2 / −2
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index ec26f7a..0e1260e 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -16455,7 +16455,7 @@ where
// First we rebuild all pending payments, then separately re-claim and re-fail pending
// payments. This avoids edge-cases around MPP payments resulting in redundant actions.
for (channel_id, monitor) in args.channel_monitors.iter() {
- let mut is_channel_closed = false;
+ let mut is_channel_closed = true;
let counterparty_node_id = monitor.get_counterparty_node_id();
if let Some(peer_state_mtx) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mtx.lock().unwrap();
@@ -16495,7 +16495,7 @@ where
}
}
for (channel_id, monitor) in args.channel_monitors.iter() {
- let mut is_channel_closed = false;
+ let mut is_channel_closed = true;
let counterparty_node_id = monitor.get_counterparty_node_id();
if let Some(peer_state_mtx) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mtx.lock().unwrap();
Why this scored 55/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.