Skip to content

Commit 5d8b10d

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 3ea1394 commit 5d8b10d

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
@@ -7060,6 +7060,10 @@ impl<SP: Deref> FundedChannel<SP> where
70607060
} else if
70617061
// Cleared upon receiving `channel_reestablish`.
70627062
self.context.channel_state.is_peer_disconnected()
7063+
// Cleared upon receiving `stfu`.
7064+
|| self.context.channel_state.is_local_stfu_sent()
7065+
// Cleared upon receiving a message that triggers the end of quiescence.
7066+
|| self.context.channel_state.is_quiescent()
70637067
// Cleared upon receiving `revoke_and_ack`.
70647068
|| self.context.has_pending_channel_update()
70657069
{
@@ -8825,6 +8829,9 @@ impl<SP: Deref> FundedChannel<SP> where
88258829
let is_holder_quiescence_initiator = !msg.initiator || self.context.is_outbound();
88268830
self.context.is_holder_quiescence_initiator = Some(is_holder_quiescence_initiator);
88278831

8832+
// We were expecting to receive `stfu` because we already sent ours.
8833+
self.mark_response_received();
8834+
88288835
if self.context.has_pending_channel_update() {
88298836
// Since we've already sent `stfu`, it should not be possible for one of our updates to
88308837
// be pending, so anything pending currently must be from a counterparty update.
@@ -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.is_holder_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)