Don't use `is_pre_funded_state` to provide an FC monitor update
What changed, and why it matters
This change fixes a logic bug in how the Lightning node decides whether to send a 'channel is being force-closed' update to its on-disk channel monitor. Previously, the code used a broad channel-state check that could incorrectly skip sending the update during some interactive funding flows, or send it too early in other cases. The patch replaces that broad check with a precise counter that tracks when the original monitor was first created, ensuring the close update is only generated when a monitor already exists. The practical effect is more reliable recovery of funds during force-closes involving modern interactive channel-opening protocols.
Review and merge the patch. After deployment, operators using interactive/dual-funding channel opens should ensure they are running a version containing this fix, because force-closing during an interactive signing session could previously omit a required monitor update. No immediate external mitigation is described in the commit materials.
Security signals we found
Incorrect state predicate used to decide whether to persist a force-close monitor update
Interactive signing flow could suppress a required ChannelMonitorUpdate::ChannelForceClosed
Risk of inconsistent on-disk monitor state during force-close
Potential fund-recovery issue if force-close update is missing from persisted monitor
Evidence from the diff
In rust-lightning’s Channel::force_shutdown path, the code decides whether to emit a ChannelMonitorUpdate::ChannelForceClosed. It previously gated this on Channel::is_pre_funded_state, a predicate whose meaning drifted and which now returns false whenever an interactive signing session exists. That caused the monitor close update to be skipped in cases where a monitor had actually already been persisted (after the first commitment_signed). Conversely, in earlier states it could imply a monitor update before monitor registration. The patch replaces the state predicate with a direct check that counterparty_next_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER, which is true exactly after the initial ChannelMonitor has been generated. This aligns the update emission with monitor existence.
Changed components
lightning/src/ln/channel.rsChannel force-close logicChannelMonitorUpdate generationInteractive funding / dual-funding signing flowsInspect captured patch +7 / −8
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 701e269..da4175d 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -5496,15 +5496,14 @@ where
}
let monitor_update = if let Some(funding_txo) = funding.get_funding_txo() {
- // If we haven't yet exchanged funding signatures (ie channel_state < AwaitingChannelReady),
- // returning a channel monitor update here would imply a channel monitor update before
- // we even registered the channel monitor to begin with, which is invalid.
- // Thus, if we aren't actually at a point where we could conceivably broadcast the
- // funding transaction, don't return a funding txo (which prevents providing the
- // monitor update to the user, even if we return one).
- // See test_duplicate_chan_id and test_pre_lockin_no_chan_closed_update for more.
- if !self.channel_state.is_pre_funded_state() {
+ // We should only generate a closing `ChannelMonitorUpdate` if we already have a
+ // `ChannelMonitor` for the disk (i.e. `counterparty_next_commitment_transaction_number`
+ // has been decremented once, which hapens when we generate the initial
+ // `ChannelMonitor`). Otherwise, that would imply a channel monitor update before we
+ // even registered the channel monitor to begin with, which is invalid.
+ if self.counterparty_next_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
self.latest_monitor_update_id = self.get_latest_unblocked_monitor_update_id() + 1;
+
let update = ChannelMonitorUpdate {
update_id: self.latest_monitor_update_id,
updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed {
Why this scored 57/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.