Skip to content

Commit b861c68

Browse files
jkczyzclaude
andcommitted
Clear disconnect timer when exiting quiescence
Several code paths exit quiescence by calling `clear_quiescent()` directly without also clearing the disconnect timer via `mark_response_received()`. This causes the timer to fire after the splice completes or is aborted, spuriously disconnecting the peer. Replace `clear_quiescent()` with `exit_quiescence()` in `on_tx_signatures_exchange`, `reset_pending_splice_state`, and `peer_connected_get_handshake`, which clears both the quiescent state and the disconnect timer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c1594a0 commit b861c68

File tree

2 files changed

+200
-4
lines changed

2 files changed

+200
-4
lines changed

lightning/src/ln/channel.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,7 +1719,10 @@ where
17191719
// We shouldn't be quiescent anymore upon reconnecting if:
17201720
// - We were in quiescence but a splice/RBF was never negotiated or
17211721
// - We were in quiescence but the splice negotiation failed due to disconnecting
1722-
chan.context.channel_state.clear_quiescent();
1722+
//
1723+
// NOTE: While `exit_quiescence` clears the disconnect timer, it should already
1724+
// have been cleared by `remove_uncommitted_htlcs_and_mark_paused`.
1725+
chan.exit_quiescence();
17231726
None
17241727
} else {
17251728
None
@@ -7285,7 +7288,7 @@ where
72857288
self.pending_splice.take();
72867289
}
72877290

7288-
self.context.channel_state.clear_quiescent();
7291+
self.exit_quiescence();
72897292
if current_is_awaiting_signatures {
72907293
self.context.interactive_tx_signing_session.take();
72917294
}
@@ -9305,7 +9308,6 @@ where
93059308
debug_assert!(!self.context.channel_state.is_awaiting_remote_revoke());
93069309

93079310
if let Some(pending_splice) = self.pending_splice.as_mut() {
9308-
self.context.channel_state.clear_quiescent();
93099311
if let Some(FundingNegotiation::AwaitingSignatures {
93109312
mut funding,
93119313
funding_feerate_sat_per_1000_weight,
@@ -9351,6 +9353,8 @@ where
93519353
funding_tx_signed.funding_tx = Some((funding_tx, tx_type));
93529354
funding_tx_signed.splice_negotiated = Some(splice_negotiated);
93539355
funding_tx_signed.splice_locked = splice_locked;
9356+
9357+
self.exit_quiescence();
93549358
} else {
93559359
debug_assert!(false);
93569360
}

lightning/src/ln/splicing_tests.rs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::chain::ChannelMonitorUpdateStatus;
1616
use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};
1717
use crate::ln::chan_utils;
1818
use crate::ln::channel::{
19-
CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE,
19+
CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY, DISCONNECT_PEER_AWAITING_RESPONSE_TICKS,
20+
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE,
2021
};
2122
use crate::ln::channelmanager::{provided_init_features, PaymentId, BREAKDOWN_TIMEOUT};
2223
use crate::ln::functional_test_utils::*;
@@ -6819,3 +6820,194 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
68196820
other => panic!("Expected SpliceFailed, got {:?}", other),
68206821
}
68216822
}
6823+
6824+
#[test]
6825+
fn test_no_disconnect_after_splice_completes() {
6826+
// Test that the disconnect timer is cleared when exiting quiescence after a successful splice
6827+
// negotiation. Previously, `on_tx_signatures_exchange` cleared the quiescent state but not the
6828+
// disconnect timer, causing a spurious disconnect after the splice completed.
6829+
let chanmon_cfgs = create_chanmon_cfgs(2);
6830+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
6831+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
6832+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
6833+
6834+
let initial_channel_value_sat = 100_000;
6835+
let (_, _, channel_id, _) =
6836+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
6837+
6838+
let added_value = Amount::from_sat(50_000);
6839+
provide_utxo_reserves(&nodes, 2, added_value * 2);
6840+
6841+
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
6842+
let new_funding_script = complete_splice_handshake(&nodes[0], &nodes[1]);
6843+
6844+
// Fire a tick while quiescent to arm the disconnect timer.
6845+
nodes[0].node.timer_tick_occurred();
6846+
nodes[1].node.timer_tick_occurred();
6847+
6848+
// Complete the splice negotiation, which should clear the timer when exiting quiescence.
6849+
complete_interactive_funding_negotiation(
6850+
&nodes[0],
6851+
&nodes[1],
6852+
channel_id,
6853+
funding_contribution,
6854+
new_funding_script,
6855+
);
6856+
let (_, splice_locked) = sign_interactive_funding_tx(&nodes[0], &nodes[1], false);
6857+
assert!(splice_locked.is_none());
6858+
6859+
let node_id_0 = nodes[0].node.get_our_node_id();
6860+
let node_id_1 = nodes[1].node.get_our_node_id();
6861+
expect_splice_pending_event(&nodes[0], &node_id_1);
6862+
expect_splice_pending_event(&nodes[1], &node_id_0);
6863+
6864+
// Fire enough ticks to trigger a disconnect if the timer wasn't properly cleared.
6865+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
6866+
nodes[0].node.timer_tick_occurred();
6867+
nodes[1].node.timer_tick_occurred();
6868+
}
6869+
6870+
let has_disconnect = |events: &[MessageSendEvent]| {
6871+
events.iter().any(|event| {
6872+
matches!(
6873+
event,
6874+
MessageSendEvent::HandleError {
6875+
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
6876+
..
6877+
}
6878+
)
6879+
})
6880+
};
6881+
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
6882+
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
6883+
}
6884+
6885+
#[test]
6886+
fn test_no_disconnect_after_splice_aborted() {
6887+
// Test that the disconnect timer is cleared when exiting quiescence after a splice negotiation
6888+
// is aborted via tx_abort. Previously, `reset_pending_splice_state` cleared the quiescent
6889+
// state but not the disconnect timer, causing a spurious disconnect after the abort.
6890+
let chanmon_cfgs = create_chanmon_cfgs(2);
6891+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
6892+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
6893+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
6894+
6895+
let node_id_0 = nodes[0].node.get_our_node_id();
6896+
let node_id_1 = nodes[1].node.get_our_node_id();
6897+
6898+
let initial_channel_value_sat = 100_000;
6899+
let (_, _, channel_id, _) =
6900+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
6901+
6902+
let added_value = Amount::from_sat(50_000);
6903+
provide_utxo_reserves(&nodes, 2, added_value * 2);
6904+
6905+
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
6906+
complete_splice_handshake(&nodes[0], &nodes[1]);
6907+
6908+
// Fire a tick while quiescent to arm the disconnect timer.
6909+
nodes[0].node.timer_tick_occurred();
6910+
nodes[1].node.timer_tick_occurred();
6911+
6912+
// Abort the splice, which should clear the timer when exiting quiescence.
6913+
nodes[0].node.abandon_splice(&channel_id, &node_id_1).unwrap();
6914+
6915+
expect_splice_failed_events(&nodes[0], &channel_id, funding_contribution);
6916+
6917+
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
6918+
let tx_abort = msg_events
6919+
.iter()
6920+
.find_map(|event| {
6921+
if let MessageSendEvent::SendTxAbort { msg, .. } = event {
6922+
Some(msg.clone())
6923+
} else {
6924+
None
6925+
}
6926+
})
6927+
.expect("Expected SendTxAbort");
6928+
6929+
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort);
6930+
let tx_abort_echo = get_event_msg!(nodes[1], MessageSendEvent::SendTxAbort, node_id_0);
6931+
nodes[1].node.get_and_clear_pending_events();
6932+
6933+
nodes[0].node.handle_tx_abort(node_id_1, &tx_abort_echo);
6934+
6935+
// Fire enough ticks to trigger a disconnect if the timer wasn't properly cleared.
6936+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
6937+
nodes[0].node.timer_tick_occurred();
6938+
nodes[1].node.timer_tick_occurred();
6939+
}
6940+
6941+
let has_disconnect = |events: &[MessageSendEvent]| {
6942+
events.iter().any(|event| {
6943+
matches!(
6944+
event,
6945+
MessageSendEvent::HandleError {
6946+
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
6947+
..
6948+
}
6949+
)
6950+
})
6951+
};
6952+
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
6953+
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
6954+
}
6955+
6956+
#[test]
6957+
fn test_no_disconnect_after_quiescence_on_reconnect() {
6958+
// Test that there is no spurious disconnect after reconnecting from a quiescent state. The
6959+
// disconnect timer is cleared by `remove_uncommitted_htlcs_and_mark_paused` during
6960+
// disconnection and by `exit_quiescence` during reconnection.
6961+
let chanmon_cfgs = create_chanmon_cfgs(2);
6962+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
6963+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
6964+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
6965+
6966+
let node_id_0 = nodes[0].node.get_our_node_id();
6967+
let node_id_1 = nodes[1].node.get_our_node_id();
6968+
6969+
let initial_channel_value_sat = 100_000;
6970+
let (_, _, channel_id, _) =
6971+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
6972+
6973+
let added_value = Amount::from_sat(50_000);
6974+
provide_utxo_reserves(&nodes, 2, added_value * 2);
6975+
6976+
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
6977+
complete_splice_handshake(&nodes[0], &nodes[1]);
6978+
6979+
// Fire a tick while quiescent to arm the disconnect timer.
6980+
nodes[0].node.timer_tick_occurred();
6981+
nodes[1].node.timer_tick_occurred();
6982+
6983+
// Disconnect and reconnect.
6984+
nodes[0].node.peer_disconnected(node_id_1);
6985+
nodes[1].node.peer_disconnected(node_id_0);
6986+
6987+
expect_splice_failed_events(&nodes[0], &channel_id, funding_contribution);
6988+
6989+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
6990+
reconnect_args.send_channel_ready = (true, true);
6991+
reconnect_args.send_announcement_sigs = (true, true);
6992+
reconnect_nodes(reconnect_args);
6993+
6994+
// Fire enough ticks to trigger a disconnect if the timer wasn't properly cleared.
6995+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
6996+
nodes[0].node.timer_tick_occurred();
6997+
nodes[1].node.timer_tick_occurred();
6998+
}
6999+
7000+
let has_disconnect = |events: &[MessageSendEvent]| {
7001+
events.iter().any(|event| {
7002+
matches!(
7003+
event,
7004+
MessageSendEvent::HandleError {
7005+
action: msgs::ErrorAction::DisconnectPeerWithWarning { .. },
7006+
..
7007+
}
7008+
)
7009+
})
7010+
};
7011+
assert!(!has_disconnect(&nodes[0].node.get_and_clear_pending_msg_events()));
7012+
assert!(!has_disconnect(&nodes[1].node.get_and_clear_pending_msg_events()));
7013+
}

0 commit comments

Comments
 (0)