Emit SpliceFailed upon disconnect while quiescent
What changed, and why it matters
This change fixes a bookkeeping bug in the Lightning Dev Kit's splicing feature. When a peer disconnects while a splice is being negotiated (but before signatures are exchanged), the software now emits a SpliceFailed event so the user's wallet knows the splice attempt is dead and can unlock any reserved funds. Previously, the internal splice state was reset on disconnect but no event was emitted, which could leave a wallet waiting indefinitely for a splice that would never complete.
Treat as a normal bug-fix patch. Reviewers should verify that SpliceFailed is emitted exactly once per abandoned splice and that no double-event occurs on both sides of a disconnect. No immediate security response is indicated.
Security signals we found
Missing event emission on disconnect could cause wallet-level fund-locking / UX failure
State cleanup without notification is a consistency bug in protocol state machine
Patch adds explicit SpliceFailed event for abandoned splice negotiation
No cryptographic, memory-safety, or remote-exploitable primitive is changed
Evidence from the diff
The commit refactors peer-disconnection handling so that Channel::peer_disconnected_is_resumable returns a DisconnectResult containing both the existing is_resumable flag and an optional SpliceFundingFailed. ChannelManager::peer_disconnected now pushes Event::SpliceFailed into pending_events when splice_funding_failed is present. The logic is limited to funded channels in ChannelReady state, and only when the splice has not yet reached FundingNegotiation::AwaitingSignatures. The change also extracts splice-failure cleanup into maybe_fail_splice_negotiation and moves the quiescence reset logic from remove_uncommitted_htlcs_and_mark_paused into peer_disconnected_is_resumable. Tests are updated to expect the new event.
Changed components
lightning/src/ln/channel.rslightning/src/ln/channelmanager.rslightning/src/ln/splicing_tests.rsInspect captured patch +238 / −175
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 6426d30..a0f64d4 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -1196,6 +1196,14 @@ pub(crate) struct ShutdownResult {
pub(crate) splice_funding_failed: Option<SpliceFundingFailed>,
}
+/// The result of a peer disconnection.
+pub(crate) struct DisconnectResult {
+ pub(crate) is_resumable: bool,
+ /// If a splice was in progress when the channel was shut down, this contains
+ /// the splice funding information for emitting a SpliceFailed event.
+ pub(crate) splice_funding_failed: Option<SpliceFundingFailed>,
+}
+
/// Tracks the transaction number, along with current and next commitment points.
/// This consolidates the logic to advance our commitment number and request new
/// commitment points from our signer.
@@ -1588,11 +1596,15 @@ where
/// Should be called when the peer is disconnected. Returns true if the channel can be resumed
/// when the peer reconnects (via [`Self::peer_connected_get_handshake`]). If not, the channel
/// must be immediately closed.
- #[rustfmt::skip]
- pub fn peer_disconnected_is_resumable<L: Deref>(&mut self, logger: &L) -> bool where L::Target: Logger {
- match &mut self.phase {
+ pub fn peer_disconnected_is_resumable<L: Deref>(&mut self, logger: &L) -> DisconnectResult
+ where
+ L::Target: Logger,
+ {
+ let is_resumable = match &mut self.phase {
ChannelPhase::Undefined => unreachable!(),
- ChannelPhase::Funded(chan) => chan.remove_uncommitted_htlcs_and_mark_paused(logger).is_ok(),
+ ChannelPhase::Funded(chan) => {
+ chan.remove_uncommitted_htlcs_and_mark_paused(logger).is_ok()
+ },
// If we get disconnected and haven't yet committed to a funding
// transaction, we can replay the `open_channel` on reconnection, so don't
// bother dropping the channel here. However, if we already committed to
@@ -1602,7 +1614,40 @@ where
ChannelPhase::UnfundedOutboundV1(chan) => chan.is_resumable(),
ChannelPhase::UnfundedInboundV1(_) => false,
ChannelPhase::UnfundedV2(_) => false,
- }
+ };
+
+ let splice_funding_failed = if let ChannelPhase::Funded(chan) = &mut self.phase {
+ // Reset any quiescence-related state as it is implicitly terminated once disconnected.
+ if matches!(chan.context.channel_state, ChannelState::ChannelReady(_)) {
+ if chan.quiescent_action.is_some() {
+ // If we were trying to get quiescent, try again after reconnection.
+ chan.context.channel_state.set_awaiting_quiescence();
+ }
+ chan.context.channel_state.clear_local_stfu_sent();
+ chan.context.channel_state.clear_remote_stfu_sent();
+ if chan.should_reset_pending_splice_state() {
+ // If there was a pending splice negotiation that failed due to disconnecting, we
+ // also take the opportunity to clean up our state.
+ let splice_funding_failed = chan.reset_pending_splice_state();
+ debug_assert!(!chan.context.channel_state.is_quiescent());
+ splice_funding_failed
+ } else if !chan.has_pending_splice_awaiting_signatures() {
+ // We shouldn't be quiescent anymore upon reconnecting if:
+ // - We were in quiescence but a splice/RBF was never negotiated or
+ // - We were in quiescence but the splice negotiation failed due to disconnecting
+ chan.context.channel_state.clear_quiescent();
+ None
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ DisconnectResult { is_resumable, splice_funding_failed }
}
/// Should be called when the peer re-connects, returning an initial message which we should
@@ -6837,40 +6882,42 @@ where
}
pub fn force_shutdown(&mut self, closure_reason: ClosureReason) -> ShutdownResult {
- let splice_funding_failed =
- if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
- if self.should_reset_pending_splice_state() {
- self.reset_pending_splice_state()
- } else {
- match self.quiescent_action.take() {
- Some(QuiescentAction::Splice(instructions)) => {
- self.context.channel_state.clear_awaiting_quiescence();
- let (inputs, outputs) =
- instructions.into_contributed_inputs_and_outputs();
- Some(SpliceFundingFailed {
- funding_txo: None,
- channel_type: None,
- contributed_inputs: inputs,
- contributed_outputs: outputs,
- })
- },
- #[cfg(any(test, fuzzing))]
- Some(quiescent_action) => {
- self.quiescent_action = Some(quiescent_action);
- None
- },
- None => None,
- }
- }
- } else {
- None
- };
+ let splice_funding_failed = self.maybe_fail_splice_negotiation();
let mut shutdown_result = self.context.force_shutdown(&self.funding, closure_reason);
shutdown_result.splice_funding_failed = splice_funding_failed;
shutdown_result
}
+ fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
+ if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
+ if self.should_reset_pending_splice_state() {
+ self.reset_pending_splice_state()
+ } else {
+ match self.quiescent_action.take() {
+ Some(QuiescentAction::Splice(instructions)) => {
+ self.context.channel_state.clear_awaiting_quiescence();
+ let (inputs, outputs) = instructions.into_contributed_inputs_and_outputs();
+ Some(SpliceFundingFailed {
+ funding_txo: None,
+ channel_type: None,
+ contributed_inputs: inputs,
+ contributed_outputs: outputs,
+ })
+ },
+ #[cfg(any(test, fuzzing))]
+ Some(quiescent_action) => {
+ self.quiescent_action = Some(quiescent_action);
+ None
+ },
+ None => None,
+ }
+ }
+ } else {
+ None
+ }
+ }
+
fn interactive_tx_constructor_mut(&mut self) -> Option<&mut InteractiveTxConstructor> {
self.pending_splice
.as_mut()
@@ -9130,27 +9177,6 @@ where
}
}
- // Reset any quiescence-related state as it is implicitly terminated once disconnected.
- if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
- if self.quiescent_action.is_some() {
- // If we were trying to get quiescent, try again after reconnection.
- self.context.channel_state.set_awaiting_quiescence();
- }
- self.context.channel_state.clear_local_stfu_sent();
- self.context.channel_state.clear_remote_stfu_sent();
- if self.should_reset_pending_splice_state() {
- // If there was a pending splice negotiation that failed due to disconnecting, we
- // also take the opportunity to clean up our state.
- self.reset_pending_splice_state();
- debug_assert!(!self.context.channel_state.is_quiescent());
- } else if !self.has_pending_splice_awaiting_signatures() {
- // We shouldn't be quiescent anymore upon reconnecting if:
- // - We were in quiescence but a splice/RBF was never negotiated or
- // - We were in quiescence but the splice negotiation failed due to disconnecting
- self.context.channel_state.clear_quiescent();
- }
- }
-
self.context.channel_state.set_peer_disconnected();
log_trace!(logger, "Peer disconnection resulted in {} remote-announced HTLC drops on channel {}", inbound_drop_count, &self.context.channel_id());
Ok(())
@@ -11823,9 +11849,9 @@ where
.map_err(|e| APIError::APIMisuseError { err: e.to_owned() })
}
- fn send_splice_init(
- &mut self, instructions: SpliceInstructions,
- ) -> Result<msgs::SpliceInit, String> {
+ fn send_splice_init(&mut self, instructions: SpliceInstructions) -> msgs::SpliceInit {
+ debug_assert!(self.pending_splice.is_none());
+
let SpliceInstructions {
adjusted_funding_contribution,
our_funding_inputs,
@@ -11835,15 +11861,6 @@ where
locktime,
} = instructions;
- // Check if a splice has been initiated already.
- // Note: only a single outstanding splice is supported (per spec)
- if self.pending_splice.is_some() {
- return Err(format!(
- "Channel {} cannot be spliced, as it has already a splice pending",
- self.context.channel_id(),
- ));
- }
-
let prev_funding_input = self.funding.to_splice_funding_input();
let context = FundingNegotiationContext {
is_initiator: true,
@@ -11867,14 +11884,14 @@ where
let prev_funding_txid = self.funding.get_funding_txid();
let funding_pubkey = self.context.holder_pubkeys(prev_funding_txid).funding_pubkey;
- Ok(msgs::SpliceInit {
+ msgs::SpliceInit {
channel_id: self.context.channel_id,
funding_contribution_satoshis: adjusted_funding_contribution.to_sat(),
funding_feerate_per_kw,
locktime,
funding_pubkey,
require_confirmed_inputs: None,
- })
+ }
}
#[cfg(test)]
@@ -13045,10 +13062,20 @@ where
"Internal Error: Didn't have anything to do after reaching quiescence".to_owned()
));
},
- Some(QuiescentAction::Splice(_instructions)) => {
- return self.send_splice_init(_instructions)
- .map(|splice_init| Some(StfuResponse::SpliceInit(splice_init)))
- .map_err(|e| ChannelError::WarnAndDisconnect(e.to_owned()));
+ Some(QuiescentAction::Splice(instructions)) => {
+ if self.pending_splice.is_some() {
+ self.quiescent_action = Some(QuiescentAction::Splice(instructions));
+
+ return Err(ChannelError::WarnAndDisconnect(
+ format!(
+ "Channel {} cannot be spliced as it already has a splice pending",
+ self.context.channel_id(),
+ ),
+ ));
+ }
+
+ let splice_init = self.send_splice_init(instructions);
+ return Ok(Some(StfuResponse::SpliceInit(splice_init)));
},
#[cfg(any(test, fuzzing))]
Some(QuiescentAction::DoNothing) => {
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 6a9f011..6a43468 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -59,9 +59,9 @@ use crate::ln::chan_utils::selected_commitment_sat_per_1000_weight;
#[cfg(any(test, fuzzing))]
use crate::ln::channel::QuiescentAction;
use crate::ln::channel::{
- self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, FundedChannel,
- FundingTxSigned, InboundV1Channel, OutboundV1Channel, PendingV2Channel, ReconnectionMsg,
- ShutdownResult, SpliceFundingFailed, StfuResponse, UpdateFulfillCommitFetch,
+ self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, DisconnectResult,
+ FundedChannel, FundingTxSigned, InboundV1Channel, OutboundV1Channel, PendingV2Channel,
+ ReconnectionMsg, ShutdownResult, SpliceFundingFailed, StfuResponse, UpdateFulfillCommitFetch,
WithChannelContext,
};
use crate::ln::channel_state::ChannelDetails;
@@ -13575,107 +13575,136 @@ where
#[rustfmt::skip]
fn peer_disconnected(&self, counterparty_node_id: PublicKey) {
- let _persistence_guard = PersistenceNotifierGuard::optionally_notify(
- self, || NotifyOption::SkipPersistHandleEvents);
- let mut failed_channels: Vec<(Result<Infallible, _>, _)> = Vec::new();
- let mut per_peer_state = self.per_peer_state.write().unwrap();
- let remove_peer = {
- log_debug!(
- WithContext::from(&self.logger, Some(counterparty_node_id), None, None),
- "Marking channels with {} disconnected and generating channel_updates.",
- log_pubkey!(counterparty_node_id)
- );
- if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
- let mut peer_state_lock = peer_state_mutex.lock().unwrap();
- let peer_state = &mut *peer_state_lock;
- let pending_msg_events = &mut peer_state.pending_msg_events;
- peer_state.channel_by_id.retain(|_, chan| {
- let logger = WithChannelContext::from(&self.logger, &chan.context(), None);
- if chan.peer_disconnected_is_resumable(&&logger) {
- return true;
- }
- // Clean up for removal.
- let reason = ClosureReason::DisconnectedPeer;
- let err = ChannelError::Close((reason.to_string(), reason));
- let (_, e) = convert_channel_err!(self, peer_state, err, chan);
- failed_channels.push((Err(e), counterparty_node_id));
- false
- });
- // Note that we don't bother generating any events for pre-accept channels -
- // they're not considered "channels" yet from the PoV of our events interface.
- peer_state.inbound_channel_request_by_id.clear();
- pending_msg_events.retain(|msg| {
- match msg {
- // V1 Channel Establishment
- &MessageSendEvent::SendAcceptChannel { .. } => false,
- &MessageSendEvent::SendOpenChannel { .. } => false,
- &MessageSendEvent::SendFundingCreated { .. } => false,
- &MessageSendEvent::SendFundingSigned { .. } => false,
- // V2 Channel Establishment
- &MessageSendEvent::SendAcceptChannelV2 { .. } => false,
- &MessageSendEvent::SendOpenChannelV2 { .. } => false,
- // Common Channel Establishment
- &MessageSendEvent::SendChannelReady { .. } => false,
- &MessageSendEvent::SendAnnouncementSignatures { .. } => false,
- // Quiescence
- &MessageSendEvent::SendStfu { .. } => false,
- // Splicing
- &MessageSendEvent::SendSpliceInit { .. } => false,
- &MessageSendEvent::SendSpliceAck { .. } => false,
- &MessageSendEvent::SendSpliceLocked { .. } => false,
- // Interactive Transaction Construction
- &MessageSendEvent::SendTxAddInput { .. } => false,
- &MessageSendEvent::SendTxAddOutput { .. } => false,
- &MessageSendEvent::SendTxRemoveInput { .. } => false,
- &MessageSendEvent::SendTxRemoveOutput { .. } => false,
- &MessageSendEvent::SendTxComplete { .. } => false,
- &MessageSendEvent::SendTxSignatures { .. } => false,
- &MessageSendEvent::SendTxInitRbf { .. } => false,
- &MessageSendEvent::SendTxAckRbf { .. } => false,
- &MessageSendEvent::SendTxAbort { .. } => false,
- // Channel Operations
- &MessageSendEvent::UpdateHTLCs { .. } => false,
- &MessageSendEvent::SendRevokeAndACK { .. } => false,
- &MessageSendEvent::SendClosingSigned { .. } => false,
- &MessageSendEvent::SendClosingComplete { .. } => false,
- &MessageSendEvent::SendClosingSig { .. } => false,
- &MessageSendEvent::SendShutdown { .. } => false,
- &MessageSendEvent::SendChannelReestablish { .. } => false,
- &MessageSendEvent::HandleError { .. } => false,
- // Gossip
- &MessageSendEvent::SendChannelAnnouncement { .. } => false,
- &MessageSendEvent::BroadcastChannelAnnouncement { .. } => true,
- // [`ChannelManager::pending_broadcast_events`] holds the [`BroadcastChannelUpdate`]
- // This check here is to ensure exhaustivity.
- &MessageSendEvent::BroadcastChannelUpdate { .. } => {
- debug_assert!(false, "This event shouldn't have been here");
- false
- },
- &MessageSendEvent::BroadcastNodeAnnouncement { .. } => true,
- &MessageSendEvent::SendChannelUpdate { .. } => false,
- &MessageSendEvent::SendChannelRangeQuery { .. } => false,
- &MessageSendEvent::SendShortIdsQuery { .. } => false,
- &MessageSendEvent::SendReplyChannelRange { .. } => false,
- &MessageSendEvent::SendGossipTimestampFilter { .. } => false,
-
- // Peer Storage
- &MessageSendEvent::SendPeerStorage { .. } => false,
- &MessageSendEvent::SendPeerStorageRetrieval { .. } => false,
- }
- });
- debug_assert!(peer_state.is_connected, "A disconnected peer cannot disconnect");
- peer_state.is_connected = false;
- peer_state.ok_to_remove(true)
- } else { debug_assert!(false, "Unconnected peer disconnected"); true }
- };
- if remove_peer {
- per_peer_state.remove(&counterparty_node_id);
- }
- mem::drop(per_peer_state);
+ let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
+ let mut splice_failed_events = Vec::new();
+ let mut failed_channels: Vec<(Result<Infallible, _>, _)> = Vec::new();
+ let mut per_peer_state = self.per_peer_state.write().unwrap();
+ let remove_peer = {
+ log_debug!(
+ WithContext::from(&self.logger, Some(counterparty_node_id), None, None),
+ "Marking channels with {} disconnected and generating channel_updates.",
+ log_pubkey!(counterparty_node_id)
+ );
+ if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
+ let mut peer_state_lock = peer_state_mutex.lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
+ let pending_msg_events = &mut peer_state.pending_msg_events;
+ peer_state.channel_by_id.retain(|_, chan| {
+ let logger = WithChannelContext::from(&self.logger, &chan.context(), None);
+ let DisconnectResult { is_resumable, splice_funding_failed } =
+ chan.peer_disconnected_is_resumable(&&logger);
- for (err, counterparty_node_id) in failed_channels.drain(..) {
- let _ = handle_error!(self, err, counterparty_node_id);
- }
+ if let Some(splice_funding_failed) = splice_funding_failed {
+ splice_failed_events.push(events::Event::SpliceFailed {
+ channel_id: chan.context().channel_id(),
+ counterparty_node_id,
+ user_channel_id: chan.context().get_user_id(),
+ abandoned_funding_txo: splice_funding_failed.funding_txo,
+ channel_type: splice_funding_failed.channel_type,
+ contributed_inputs: splice_funding_failed.contributed_inputs,
+ contributed_outputs: splice_funding_failed.contributed_outputs,
+ });
+ }
+
+ if is_resumable {
+ return true;
+ }
+
+ // Clean up for removal.
+ let reason = ClosureReason::DisconnectedPeer;
+ let err = ChannelError::Close((reason.to_string(), reason));
+ let (_, e) = convert_channel_err!(self, peer_state, err, chan);
+ failed_channels.push((Err(e), counterparty_node_id));
+ false
+ });
+ // Note that we don't bother generating any events for pre-accept channels -
+ // they're not considered "channels" yet from the PoV of our events interface.
+ peer_state.inbound_channel_request_by_id.clear();
+ pending_msg_events.retain(|msg| {
+ match msg {
+ // V1 Channel Establishment
+ &MessageSendEvent::SendAcceptChannel { .. } => false,
+ &MessageSendEvent::SendOpenChannel { .. } => false,
+ &MessageSendEvent::SendFundingCreated { .. } => false,
+ &MessageSendEvent::SendFundingSigned { .. } => false,
+ // V2 Channel Establishment
+ &MessageSendEvent::SendAcceptChannelV2 { .. } => false,
+ &MessageSendEvent::SendOpenChannelV2 { .. } => false,
+ // Common Channel Establishment
+ &MessageSendEvent::SendChannelReady { .. } => false,
+ &MessageSendEvent::SendAnnouncementSignatures { .. } => false,
+ // Quiescence
+ &MessageSendEvent::SendStfu { .. } => false,
+ // Splicing
+ &MessageSendEvent::SendSpliceInit { .. } => false,
+ &MessageSendEvent::SendSpliceAck { .. } => false,
+ &MessageSendEvent::SendSpliceLocked { .. } => false,
+ // Interactive Transaction Construction
+ &MessageSendEvent::SendTxAddInput { .. } => false,
+ &MessageSendEvent::SendTxAddOutput { .. } => false,
+ &MessageSendEvent::SendTxRemoveInput { .. } => false,
+ &MessageSendEvent::SendTxRemoveOutput { .. } => false,
+ &MessageSendEvent::SendTxComplete { .. } => false,
+ &MessageSendEvent::SendTxSignatures { .. } => false,
+ &MessageSendEvent::SendTxInitRbf { .. } => false,
+ &MessageSendEvent::SendTxAckRbf { .. } => false,
+ &MessageSendEvent::SendTxAbort { .. } => false,
+ // Channel Operations
+ &MessageSendEvent::UpdateHTLCs { .. } => false,
+ &MessageSendEvent::SendRevokeAndACK { .. } => false,
+ &MessageSendEvent::SendClosingSigned { .. } => false,
+ &MessageSendEvent::SendClosingComplete { .. } => false,
+ &MessageSendEvent::SendClosingSig { .. } => false,
+ &MessageSendEvent::SendShutdown { .. } => false,
+ &MessageSendEvent::SendChannelReestablish { .. } => false,
+ &MessageSendEvent::HandleError { .. } => false,
+ // Gossip
+ &MessageSendEvent::SendChannelAnnouncement { .. } => false,
+ &MessageSendEvent::BroadcastChannelAnnouncement { .. } => true,
+ // [`ChannelManager::pending_broadcast_events`] holds the [`BroadcastChannelUpdate`]
+ // This check here is to ensure exhaustivity.
+ &MessageSendEvent::BroadcastChannelUpdate { .. } => {
+ debug_assert!(false, "This event shouldn't have been here");
+ false
+ },
+ &MessageSendEvent::BroadcastNodeAnnouncement { .. } => true,
+ &MessageSendEvent::SendChannelUpdate { .. } => false,
+ &MessageSendEvent::SendChannelRangeQuery { .. } => false,
+ &MessageSendEvent::SendShortIdsQuery { .. } => false,
+ &MessageSendEvent::SendReplyChannelRange { .. } => false,
+ &MessageSendEvent::SendGossipTimestampFilter { .. } => false,
+
+ // Peer Storage
+ &MessageSendEvent::SendPeerStorage { .. } => false,
+ &MessageSendEvent::SendPeerStorageRetrieval { .. } => false,
+ }
+ });
+ debug_assert!(peer_state.is_connected, "A disconnected peer cannot disconnect");
+ peer_state.is_connected = false;
+ peer_state.ok_to_remove(true)
+ } else { debug_assert!(false, "Unconnected peer disconnected"); true }
+ };
+ if remove_peer {
+ per_peer_state.remove(&counterparty_node_id);
+ }
+ mem::drop(per_peer_state);
+
+ let persist = if splice_failed_events.is_empty() {
+ NotifyOption::SkipPersistHandleEvents
+ } else {
+ let mut pending_events = self.pending_events.lock().unwrap();
+ for event in splice_failed_events {
+ pending_events.push_back((event, None));
+ }
+ NotifyOption::DoPersist
+ };
+
+ for (err, counterparty_node_id) in failed_channels.drain(..) {
+ let _ = handle_error!(self, err, counterparty_node_id);
+ }
+
+ persist
+ });
}
#[rustfmt::skip]
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index 14f3192..2211695 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -431,6 +431,8 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
+
+ let _event = get_event!(nodes[0], Event::SpliceFailed);
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
@@ -488,6 +490,8 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
} else {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
+
+ let _event = get_event!(nodes[0], Event::SpliceFailed);
}
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
@@ -584,6 +588,9 @@ fn test_config_reject_inbound_splices() {
nodes[0].node.peer_disconnected(node_id_1);
nodes[1].node.peer_disconnected(node_id_0);
+
+ let _event = get_event!(nodes[0], Event::SpliceFailed);
+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
reconnect_args.send_channel_ready = (true, true);
reconnect_args.send_announcement_sigs = (true, true);
Why this scored 32/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.