Skip to content

Commit 3b758e7

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 c0e0129 commit 3b758e7

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
@@ -7093,6 +7093,10 @@ impl<SP: Deref> FundedChannel<SP> where
70937093
} else if
70947094
// Cleared upon receiving `channel_reestablish`.
70957095
self.context.channel_state.is_peer_disconnected()
7096+
// Cleared upon receiving `stfu`.
7097+
|| self.context.channel_state.is_local_stfu_sent()
7098+
// Cleared upon receiving a message that triggers the end of quiescence.
7099+
|| self.context.channel_state.is_quiescent()
70967100
// Cleared upon receiving `revoke_and_ack`.
70977101
|| self.context.has_pending_channel_update()
70987102
{
@@ -8871,6 +8875,9 @@ impl<SP: Deref> FundedChannel<SP> where
88718875
let is_holder_quiescence_initiator = !msg.initiator || self.context.is_outbound();
88728876
self.context.is_holder_quiescence_initiator = Some(is_holder_quiescence_initiator);
88738877

8878+
// We were expecting to receive `stfu` because we already sent ours.
8879+
self.mark_response_received();
8880+
88748881
if self.context.has_pending_channel_update() {
88758882
// Since we've already sent `stfu`, it should not be possible for one of our updates to
88768883
// be pending, so anything pending currently must be from a counterparty update.
@@ -8928,6 +8935,7 @@ impl<SP: Deref> FundedChannel<SP> where
89288935
debug_assert!(!self.context.channel_state.is_remote_stfu_sent());
89298936

89308937
if self.context.channel_state.is_quiescent() {
8938+
self.mark_response_received();
89318939
self.context.channel_state.clear_quiescent();
89328940
self.context.is_holder_quiescence_initiator.take().expect("Must always be set while quiescent")
89338941
} 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;
@@ -413,3 +415,87 @@ fn quiescence_updates_go_to_holding_cell(fail_htlc: bool) {
413415
expect_payment_sent(&nodes[1], payment_preimage1, None, true, true);
414416
}
415417
}
418+
419+
#[test]
420+
fn test_quiescence_timeout() {
421+
// Test that we'll disconnect if we remain quiescent for `DISCONNECT_PEER_AWAITING_RESPONSE_TICKS`.
422+
let chanmon_cfgs = create_chanmon_cfgs(2);
423+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
424+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
425+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
426+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
427+
428+
let node_id_0 = nodes[0].node.get_our_node_id();
429+
let node_id_1 = nodes[1].node.get_our_node_id();
430+
431+
nodes[0].node.maybe_propose_quiescence(&nodes[1].node.get_our_node_id(), &chan_id).unwrap();
432+
433+
let stfu_initiator = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
434+
nodes[1].node.handle_stfu(node_id_0, &stfu_initiator);
435+
436+
let stfu_responder = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
437+
nodes[0].node.handle_stfu(node_id_1, &stfu_responder);
438+
439+
assert!(stfu_initiator.initiator && !stfu_responder.initiator);
440+
441+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
442+
nodes[0].node.timer_tick_occurred();
443+
nodes[1].node.timer_tick_occurred();
444+
}
445+
446+
let f = |event| {
447+
if let MessageSendEvent::HandleError { action, .. } = event {
448+
if let msgs::ErrorAction::DisconnectPeerWithWarning { .. } = action {
449+
Some(())
450+
} else {
451+
None
452+
}
453+
} else {
454+
None
455+
}
456+
};
457+
assert!(nodes[0].node.get_and_clear_pending_msg_events().into_iter().find_map(f).is_some());
458+
assert!(nodes[1].node.get_and_clear_pending_msg_events().into_iter().find_map(f).is_some());
459+
}
460+
461+
#[test]
462+
fn test_quiescence_timeout_while_waiting_for_counterparty_stfu() {
463+
// Test that we'll disconnect if the counterparty does not send their stfu within a reasonable
464+
// time if we've already sent ours.
465+
let chanmon_cfgs = create_chanmon_cfgs(2);
466+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
467+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
468+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
469+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
470+
471+
let node_id_0 = nodes[0].node.get_our_node_id();
472+
473+
nodes[1].node.maybe_propose_quiescence(&node_id_0, &chan_id).unwrap();
474+
let _ = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
475+
476+
// Route a payment in between to ensure expecting to receive `revoke_and_ack` doesn't override
477+
// the expectation of receiving `stfu` as well.
478+
let _ = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
479+
480+
for _ in 0..DISCONNECT_PEER_AWAITING_RESPONSE_TICKS {
481+
nodes[0].node.timer_tick_occurred();
482+
nodes[1].node.timer_tick_occurred();
483+
}
484+
485+
// nodes[0] hasn't received stfu from nodes[1], so it's not enforcing any timeouts.
486+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
487+
488+
// nodes[1] didn't receive nodes[0]'s stfu within the timeout so it'll disconnect.
489+
let f = |&ref event| {
490+
if let MessageSendEvent::HandleError { action, .. } = event {
491+
if let msgs::ErrorAction::DisconnectPeerWithWarning { .. } = action {
492+
Some(())
493+
} else {
494+
None
495+
}
496+
} else {
497+
None
498+
}
499+
};
500+
assert!(nodes[1].node.get_and_clear_pending_msg_events().iter().find_map(f).is_some());
501+
}

0 commit comments

Comments
 (0)