Skip to content

Commit b03685f

Browse files
committed
Enforce disconnect timeout during quiescence
Since new updates are not allowed during quiescence (local updates enter the holding cell), we want to ensure quiescence eventually terminates if the handshake takes too long or our counterparty is uncooperative. Disconnecting implicitly terminates quiescence, so the holding cell can be freed upon re-establishing the channel (assuming quiescence is not requested again).
1 parent 8ceeb00 commit b03685f

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

lightning/src/ln/channel.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7059,6 +7059,10 @@ impl<SP: Deref> FundedChannel<SP> where
70597059
} else if
70607060
// Cleared upon receiving `channel_reestablish`.
70617061
self.context.channel_state.is_peer_disconnected()
7062+
// Cleared upon receiving `stfu`.
7063+
|| self.context.channel_state.is_local_stfu_sent()
7064+
// Cleared upon receiving a message that triggers the end of quiescence.
7065+
|| self.context.channel_state.is_quiescent()
70627066
// Cleared upon receiving `revoke_and_ack`.
70637067
|| self.context.has_pending_channel_update()
70647068
{
@@ -8824,6 +8828,9 @@ impl<SP: Deref> FundedChannel<SP> where
88248828
let quiescence_initiator = !msg.initiator || self.context.is_outbound();
88258829
self.context.quiescence_initiator = Some(quiescence_initiator);
88268830

8831+
// We were expecting to receive `stfu` because we already sent ours.
8832+
self.mark_response_received();
8833+
88278834
if self.context.has_pending_channel_update() {
88288835
return Err(ChannelError::Ignore(
88298836
"Received counterparty stfu, cannot become quiescent until state machine is no longer pending".to_owned()
@@ -8882,6 +8889,7 @@ impl<SP: Deref> FundedChannel<SP> where
88828889
debug_assert!(!self.context.channel_state.is_remote_stfu_sent());
88838890

88848891
if self.context.channel_state.is_quiescent() {
8892+
self.mark_response_received();
88858893
self.context.channel_state.clear_quiescent();
88868894
self.context.quiescence_initiator.take().expect("Must always be set while quiescent")
88878895
} else {

lightning/src/ln/quiescence_tests.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use crate::events::Event;
33
use crate::events::HTLCDestination;
44
use crate::events::MessageSendEvent;
55
use crate::events::MessageSendEventsProvider;
6+
use crate::ln::channel::DISCONNECT_PEER_AWAITING_RESPONSE_TICKS;
67
use crate::ln::channelmanager::PaymentId;
78
use crate::ln::channelmanager::RecipientOnionFields;
89
use crate::ln::functional_test_utils::*;
10+
use crate::ln::msgs;
911
use crate::ln::msgs::{ChannelMessageHandler, ErrorAction};
1012
use crate::util::errors::APIError;
1113
use crate::util::test_channel_signer::SignerOp;
@@ -410,3 +412,87 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) {
410412
expect_payment_sent(&nodes[1], payment_preimage1, None, true, true);
411413
}
412414
}
415+
416+
#[test]
417+
fn test_quiescence_timeout() {
418+
// Test that we'll disconnect if we remain quiescent for `DISCONNECT_PEER_AWAITING_RESPONSE_TICKS`.
419+
let chanmon_cfgs = create_chanmon_cfgs(2);
420+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
421+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
422+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
423+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
424+
425+
let node_id_0 = nodes[0].node.get_our_node_id();
426+
let node_id_1 = nodes[1].node.get_our_node_id();
427+
428+
nodes[0].node.maybe_propose_quiescence(&nodes[1].node.get_our_node_id(), &chan_id).unwrap();
429+
430+
let stfu_initiator = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
431+
nodes[1].node.handle_stfu(node_id_0, &stfu_initiator);
432+
433+
let stfu_responder = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
434+
nodes[0].node.handle_stfu(node_id_1, &stfu_responder);
435+
436+
assert!(stfu_initiator.initiator && !stfu_responder.initiator);
437+
438+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
439+
nodes[0].node.timer_tick_occurred();
440+
nodes[1].node.timer_tick_occurred();
441+
}
442+
443+
let f = |event| {
444+
if let MessageSendEvent::HandleError { action, .. } = event {
445+
if let msgs::ErrorAction::DisconnectPeerWithWarning { .. } = action {
446+
Some(())
447+
} else {
448+
None
449+
}
450+
} else {
451+
None
452+
}
453+
};
454+
assert!(nodes[0].node.get_and_clear_pending_msg_events().into_iter().find_map(f).is_some());
455+
assert!(nodes[1].node.get_and_clear_pending_msg_events().into_iter().find_map(f).is_some());
456+
}
457+
458+
#[test]
459+
fn test_quiescence_timeout_while_waiting_for_counterparty_stfu() {
460+
// Test that we'll disconnect if the counterparty does not send their stfu within a reasonable
461+
// time if we've already sent ours.
462+
let chanmon_cfgs = create_chanmon_cfgs(2);
463+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
464+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
465+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
466+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
467+
468+
let node_id_0 = nodes[0].node.get_our_node_id();
469+
470+
nodes[1].node.maybe_propose_quiescence(&node_id_0, &chan_id).unwrap();
471+
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
472+
473+
// Route a payment in between to ensure expecting to receive `revoke_and_ack` doesn't override
474+
// the expectation of receiving `stfu` as well.
475+
let _ = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
476+
477+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
478+
nodes[0].node.timer_tick_occurred();
479+
nodes[1].node.timer_tick_occurred();
480+
}
481+
482+
// nodes[0] hasn't received stfu from nodes[1], so it's not enforcing any timeouts.
483+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
484+
485+
// nodes[1] didn't receive nodes[0]'s stfu within the timeout so it'll disconnect.
486+
let f = |&ref event| {
487+
if let MessageSendEvent::HandleError { action, .. } = event {
488+
if let msgs::ErrorAction::DisconnectPeerWithWarning { .. } = action {
489+
Some(())
490+
} else {
491+
None
492+
}
493+
} else {
494+
None
495+
}
496+
};
497+
assert!(nodes[1].node.get_and_clear_pending_msg_events().iter().find_map(f).is_some());
498+
}

0 commit comments

Comments
 (0)