Allow outgoing splice request while disconnected
What changed, and why it matters
This change lets a user queue a Bitcoin Lightning 'splice' request even when the other party is offline, so it can proceed once they reconnect. Previously the software rejected splice requests while disconnected. The patch also adds clearer logging when the protocol waits for reconnection. It is a feature change, not a fix for an active exploit.
Review as a normal feature change. Verify that queued splice requests cannot be abused to exhaust resources, double-initiate, or stall channel state, especially given the disclosed lack of timeout/cancel. No immediate security patch appears warranted from the diff alone.
Security signals we found
State-guard relaxation: splice initiation no longer requires peer connectivity
New TODO-like limitation: queued requests cannot be timed out or canceled
Logging-only additions for quiescence/reconnection paths
No input validation, cryptographic, or memory-safety changes visible
Evidence from the diff
The commit relaxes a state check for outgoing channel splices. In channel.rs, the guard changes from is_live() to is_usable(), which permits splicing on channels that are usable but not currently connected. In channelmanager.rs, the explicit disconnected-peer rejection is removed. A new log line is added for the quiescence wait path. The commit message frames this as needed for low-availability/mobile peers and notes there is no timeout/cancel mechanism yet.
Changed components
lightning/src/ln/channel.rslightning/src/ln/channelmanager.rsLightning channel splicing flowInspect captured patch +4 / −10
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 75ac056..8a8b2b0 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -11851,10 +11851,10 @@ where
});
}
- if !self.context.is_live() {
+ if !self.context.is_usable() {
return Err(APIError::APIMisuseError {
err: format!(
- "Channel {} cannot be spliced, as channel is not live",
+ "Channel {} cannot be spliced as it is either pending open/close",
self.context.channel_id()
),
});
@@ -13017,6 +13017,7 @@ where
|| self.context.channel_state.is_awaiting_quiescence()
|| self.context.channel_state.is_local_stfu_sent()
{
+ log_debug!(logger, "Channel is either pending quiescence or already quiescent");
return Ok(None);
}
@@ -13024,6 +13025,7 @@ where
if self.context.is_live() {
Ok(Some(self.send_stfu(logger)?))
} else {
+ log_debug!(logger, "Waiting for peer reconnection to send stfu");
Ok(None)
}
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index ff57e95..68c5e81 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -4728,14 +4728,6 @@ where
// Look for the channel
match peer_state.channel_by_id.entry(*channel_id) {
hash_map::Entry::Occupied(mut chan_phase_entry) => {
- if !chan_phase_entry.get().context().is_connected() {
- // TODO: We should probably support this, but right now `splice_channel` refuses when
- // the peer is disconnected, so we just check it here.
- return Err(APIError::ChannelUnavailable {
- err: "Cannot initiate splice while peer is disconnected".to_owned(),
- });
- }
-
let locktime = locktime.unwrap_or_else(|| self.current_best_block().height);
if let Some(chan) = chan_phase_entry.get_mut().as_funded_mut() {
let logger = WithChannelContext::from(&self.logger, &chan.context, None);
Why this scored 29/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.