Add a `QuiescentAction` to track why we're going quiescent
What changed, and why it matters
This commit adds internal bookkeeping so that when a Lightning channel enters a quiet 'pause' state (called quiescence), the code remembers what action triggered the pause. Right now the only action is a placeholder used in tests, so this is a development-only infrastructure change with no immediate security impact on users.
No security action required. This is a normal feature-infrastructure commit. Continue routine review and testing of the upcoming splicing/quiescence integration.
Security signals we found
No memory-safety issues introduced (pure Rust enum and Option field)
No new network messages or protocol changes
No new user-exposed API surface in production builds
Adds a debug_assert!-protected consistency check for missing quiescent actions
Serialization uses existing TLV enum macro
Evidence from the diff
The patch introduces a QuiescentAction enum (currently only a DoNothing test variant) and stores it in FundedChannel as quiescent_action. It updates propose_quiescence to require an action and checks that action when quiescence is reached. The change is gated behind #[cfg(any(test, fuzzing))] in channelmanager.rs and is preparation for future splicing logic. No cryptographic, network, or consensus changes are present.
Changed components
lightning/src/ln/channel.rslightning/src/ln/channelmanager.rsInspect captured patch +43 / −7
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index a9a21c5..8075e2f 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -1863,6 +1863,7 @@ where
holder_commitment_point,
#[cfg(splicing)]
pending_splice: None,
+ quiescent_action: None,
};
let res = funded_channel.initial_commitment_signed_v2(msg, best_block, signer_provider, logger)
.map(|monitor| (Some(monitor), None))
@@ -2429,6 +2430,15 @@ impl PendingSplice {
}
}
+pub(crate) enum QuiescentAction {
+ // TODO: Make this test-only once we have another variant (as some code requires *a* variant).
+ DoNothing,
+}
+
+impl_writeable_tlv_based_enum_upgradable!(QuiescentAction,
+ (99, DoNothing) => {},
+);
+
/// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
struct ConfirmedTransaction<'a> {
tx: &'a Transaction,
@@ -6050,6 +6060,12 @@ where
/// Info about an in-progress, pending splice (if any), on the pre-splice channel
#[cfg(splicing)]
pending_splice: Option<PendingSplice>,
+
+ /// Once we become quiescent, if we're the initiator, there's some action we'll want to take.
+ /// This keeps track of that action. Note that if we become quiescent and we're not the
+ /// initiator we may be able to merge this action into what the counterparty wanted to do (e.g.
+ /// in the case of splicing).
+ quiescent_action: Option<QuiescentAction>,
}
#[cfg(splicing)]
@@ -11529,7 +11545,7 @@ where
#[cfg(any(test, fuzzing))]
#[rustfmt::skip]
pub fn propose_quiescence<L: Deref>(
- &mut self, logger: &L,
+ &mut self, logger: &L, action: QuiescentAction,
) -> Result<Option<msgs::Stfu>, ChannelError>
where
L::Target: Logger,
@@ -11541,11 +11557,13 @@ where
"Channel is not in a live state to propose quiescence".to_owned()
));
}
- if self.context.channel_state.is_quiescent() {
- return Err(ChannelError::Ignore("Channel is already quiescent".to_owned()));
+ if self.quiescent_action.is_some() {
+ return Err(ChannelError::Ignore("Channel is already quiescing".to_owned()));
}
- if self.context.channel_state.is_awaiting_quiescence()
+ self.quiescent_action = Some(action);
+ if self.context.channel_state.is_quiescent()
+ || self.context.channel_state.is_awaiting_quiescence()
|| self.context.channel_state.is_local_stfu_sent()
{
return Ok(None);
@@ -11664,6 +11682,21 @@ where
if !is_holder_quiescence_initiator { " not" } else { "" }
);
+ if is_holder_quiescence_initiator {
+ match self.quiescent_action.take() {
+ None => {
+ debug_assert!(false);
+ return Err(ChannelError::WarnAndDisconnect(
+ "Internal Error: Didn't have anything to do after reaching quiescence".to_owned()
+ ));
+ },
+ Some(QuiescentAction::DoNothing) => {
+ // In quiescence test we want to just hang out here, letting the test manually
+ // leave quiescence.
+ },
+ }
+ }
+
Ok(None)
}
@@ -12029,6 +12062,7 @@ where
holder_commitment_point,
#[cfg(splicing)]
pending_splice: None,
+ quiescent_action: None,
};
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
@@ -12315,6 +12349,7 @@ where
holder_commitment_point,
#[cfg(splicing)]
pending_splice: None,
+ quiescent_action: None,
};
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;
@@ -13974,6 +14009,7 @@ where
holder_commitment_point,
#[cfg(splicing)]
pending_splice: None,
+ quiescent_action: None,
})
}
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index af82f86..da51b43 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -58,8 +58,8 @@ use crate::events::{
};
use crate::events::{FundingInfo, PaidBolt12Invoice};
use crate::ln::chan_utils::selected_commitment_sat_per_1000_weight;
-// Since this struct is returned in `list_channels` methods, expose it here in case users want to
-// construct one themselves.
+#[cfg(any(test, fuzzing))]
+use crate::ln::channel::QuiescentAction;
use crate::ln::channel::{
self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, FundedChannel,
InboundV1Channel, OutboundV1Channel, PendingV2Channel, ReconnectionMsg, ShutdownResult,
@@ -11712,7 +11712,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
&self.logger, Some(*counterparty_node_id), Some(*channel_id), None
);
- match chan.propose_quiescence(&&logger) {
+ match chan.propose_quiescence(&&logger, QuiescentAction::DoNothing) {
Ok(None) => {},
Ok(Some(stfu)) => {
peer_state.pending_msg_events.push(MessageSendEvent::SendStfu {
Why this scored 18/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.