Attempt queued splice after existing pending splice becomes locked
What changed, and why it matters
This patch fixes a logic gap in the Lightning Dev Kit where two sides tried to splice the same channel at the same time. If the counterparty won the tie-breaker, the local node's queued splice could be forgotten and never attempted. The fix re-queues the local splice once the counterparty's splice becomes locked, so the channel eventually reaches the intended upgraded state.
Treat as a functional/liveness fix rather than an urgent security patch. Review related splice quiescence tests to ensure dual-initiated splice scenarios are covered, and consider adding a regression test that exercises the tie-breaker path.
Security signals we found
State-machine liveness issue in dual-sided splice negotiation
Possible channel stall or stuck splice intent
No direct memory-safety or cryptographic bug visible in diff
Evidence from the diff
In rust-lightning’s channel state machine, only one non-RBF splice can be pending at a time. When both peers simultaneously attempt a splice, quiescence tie-breaking lets one win. Previously, if the local node lost the tie, its queued QuiescentAction::Splice could remain unprocessed because the code did not re-enter the awaiting-quiescence state after the winning splice locked. The change adds a check in the splice-funding promotion path: if a splice QuiescentAction is still queued, set channel_state to awaiting_quiescence so the local splice negotiation is attempted next.
Changed components
lightning/src/ln/channel.rsSplice funding promotion pathQuiescentAction state handlingInspect captured patch +6 / −0
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 8a8b2b0..8f74dc2 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -11180,6 +11180,12 @@ where
let announcement_sigs =
self.get_announcement_sigs(node_signer, chain_hash, user_config, block_height, logger);
+ if let Some(quiescent_action) = self.quiescent_action.as_ref() {
+ if matches!(quiescent_action, QuiescentAction::Splice(_)) {
+ self.context.channel_state.set_awaiting_quiescence();
+ }
+ }
+
Some(SpliceFundingPromotion {
funding_txo,
monitor_update,
Why this scored 35/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.