Skip to content

Commit 6d2b110

Browse files
committed
Wipe splice state upon failed interactive funding construction
An interactive funding construction can be considered failed upon a disconnect or a `tx_abort` message. So far, we've consumed the `InteractiveTxConstructor` in the latter case, but not the former. Additionally, we may have splice-specific state that needs to be consumed as well to allow us to negotiate another splice later on. This commit ensures that we properly consume all splice and interactive funding state whenever possible upon a disconnect or `tx_abort`. The interactive funding state is safe to consume as long as we have either yet to reach `AwaitingSignatures`, or we have but `tx_signatures` has not been sent/received. In all of these cases, we also make sure to clear the quiescent state flag such that we're able to resume processing updates on the channel. The splice state is safe to consume as long as we don't have a pending `FundingNegotiation::AwaitingSignatures` with a `tx_signatures` sent/received and we don't have any negotiated candidates. Note that until splice RBF is supported, it is not currently possible to have any negotiated candidates with a pending interactive funding transaction.
1 parent 3e21ba3 commit 6d2b110

File tree

3 files changed

+372
-38
lines changed

3 files changed

+372
-38
lines changed

lightning/src/ln/channel.rs

Lines changed: 122 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,27 +1686,20 @@ where
16861686
let logger = WithChannelContext::from(logger, &self.context(), None);
16871687
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
16881688

1689-
let _interactive_tx_constructor = match &mut self.phase {
1689+
match &mut self.phase {
16901690
ChannelPhase::Undefined => unreachable!(),
1691-
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
1691+
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {},
16921692
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1693-
pending_v2_channel.interactive_tx_constructor.take()
1693+
pending_v2_channel.interactive_tx_constructor.take();
1694+
},
1695+
ChannelPhase::Funded(funded_channel) => {
1696+
if funded_channel.should_reset_pending_splice_funding_negotiation().unwrap_or(true)
1697+
{
1698+
funded_channel.reset_pending_splice_state();
1699+
} else {
1700+
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
1701+
}
16941702
},
1695-
ChannelPhase::Funded(funded_channel) => funded_channel
1696-
.pending_splice
1697-
.as_mut()
1698-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1699-
.and_then(|funding_negotiation| {
1700-
if let FundingNegotiation::ConstructingTransaction {
1701-
interactive_tx_constructor,
1702-
..
1703-
} = funding_negotiation
1704-
{
1705-
Some(interactive_tx_constructor)
1706-
} else {
1707-
None
1708-
}
1709-
}),
17101703
};
17111704

17121705
reason.into_tx_abort_msg(self.context().channel_id)
@@ -1818,11 +1811,11 @@ where
18181811
where
18191812
L::Target: Logger,
18201813
{
1821-
// This checks for and resets the interactive negotiation state by `take()`ing it from the channel.
1822-
// The existence of the `tx_constructor` indicates that we have not moved into the signing
1823-
// phase for this interactively constructed transaction and hence we have not exchanged
1824-
// `tx_signatures`. Either way, we never close the channel upon receiving a `tx_abort`:
1825-
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L574-L576
1814+
// If we have not sent a `tx_abort` message for this negotiation previously, we need to echo
1815+
// back a tx_abort message according to the spec:
1816+
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
1817+
// For rationale why we echo back `tx_abort`:
1818+
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
18261819
let should_ack = match &mut self.phase {
18271820
ChannelPhase::Undefined => unreachable!(),
18281821
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
@@ -1832,19 +1825,27 @@ where
18321825
ChannelPhase::UnfundedV2(pending_v2_channel) => {
18331826
pending_v2_channel.interactive_tx_constructor.take().is_some()
18341827
},
1835-
ChannelPhase::Funded(funded_channel) => funded_channel
1836-
.pending_splice
1837-
.as_mut()
1838-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1839-
.is_some(),
1828+
ChannelPhase::Funded(funded_channel) => {
1829+
if let Some(should_reset) =
1830+
funded_channel.should_reset_pending_splice_funding_negotiation()
1831+
{
1832+
if should_reset {
1833+
// We may have still tracked the pending funding negotiation state, so we
1834+
// should ack with our own `tx_abort`.
1835+
funded_channel.reset_pending_splice_state()
1836+
} else {
1837+
return Err(ChannelError::close(
1838+
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
1839+
));
1840+
}
1841+
} else {
1842+
// We were not tracking the pending funding negotiation state anymore, likely
1843+
// due to a disconnection or already having sent our own `tx_abort`.
1844+
false
1845+
}
1846+
},
18401847
};
18411848

1842-
// NOTE: Since at this point we have not sent a `tx_abort` message for this negotiation
1843-
// previously (tx_constructor was `Some`), we need to echo back a tx_abort message according
1844-
// to the spec:
1845-
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
1846-
// For rationale why we echo back `tx_abort`:
1847-
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
18481849
Ok(should_ack.then(|| {
18491850
let logger = WithChannelContext::from(logger, &self.context(), None);
18501851
let reason =
@@ -2554,6 +2555,15 @@ impl FundingNegotiation {
25542555
}
25552556

25562557
impl PendingFunding {
2558+
fn can_abandon_funding_negotiation(&self) -> bool {
2559+
self.funding_negotiation
2560+
.as_ref()
2561+
.map(|funding_negotiation| {
2562+
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
2563+
})
2564+
.unwrap_or(true)
2565+
}
2566+
25572567
fn check_get_splice_locked<SP: Deref>(
25582568
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
25592569
) -> Option<msgs::SpliceLocked>
@@ -6739,6 +6749,45 @@ where
67396749
)
67406750
}
67416751

6752+
/// Returns `None` if there is no [`FundedChannel::pending_splice`], otherwise a boolean
6753+
/// indicating whether we should reset the splice's [`PendingFunding::funding_negotiation`].
6754+
fn should_reset_pending_splice_funding_negotiation(&self) -> Option<bool> {
6755+
self.pending_splice.as_ref().map(|pending_splice| {
6756+
if pending_splice.can_abandon_funding_negotiation() {
6757+
true
6758+
} else {
6759+
self.context
6760+
.interactive_tx_signing_session
6761+
.as_ref()
6762+
.map(|signing_session| !signing_session.has_received_commitment_signed())
6763+
.unwrap_or_else(|| {
6764+
debug_assert!(false);
6765+
false
6766+
})
6767+
}
6768+
})
6769+
}
6770+
6771+
fn should_reset_pending_splice_state(&self) -> bool {
6772+
self.should_reset_pending_splice_funding_negotiation().unwrap_or(true)
6773+
&& self.pending_funding().is_empty()
6774+
}
6775+
6776+
fn reset_pending_splice_state(&mut self) -> bool {
6777+
debug_assert!(self.should_reset_pending_splice_funding_negotiation().unwrap_or(true));
6778+
self.context.channel_state.clear_quiescent();
6779+
self.context.interactive_tx_signing_session.take();
6780+
let has_funding_negotiation = self
6781+
.pending_splice
6782+
.as_mut()
6783+
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
6784+
.is_some();
6785+
if self.should_reset_pending_splice_state() {
6786+
self.pending_splice.take();
6787+
}
6788+
has_funding_negotiation
6789+
}
6790+
67426791
#[rustfmt::skip]
67436792
fn check_remote_fee<F: Deref, L: Deref>(
67446793
channel_type: &ChannelTypeFeatures, fee_estimator: &LowerBoundedFeeEstimator<F>,
@@ -8864,7 +8913,14 @@ where
88648913
}
88658914
self.context.channel_state.clear_local_stfu_sent();
88668915
self.context.channel_state.clear_remote_stfu_sent();
8867-
self.context.channel_state.clear_quiescent();
8916+
if self.should_reset_pending_splice_funding_negotiation().unwrap_or(true) {
8917+
// If we were in quiescence but a splice was never negotiated, or the negotiation
8918+
// failed due to disconnecting, we shouldn't be quiescent anymore upon reconnecting.
8919+
// If there was a pending splice negotiation that has failed due to disconnecting,
8920+
// we also take the opportunity to clean up our state.
8921+
self.reset_pending_splice_state();
8922+
debug_assert!(!self.context.channel_state.is_quiescent());
8923+
}
88688924
}
88698925

88708926
self.context.channel_state.set_peer_disconnected();
@@ -13875,7 +13931,12 @@ where
1387513931
}
1387613932
channel_state.clear_local_stfu_sent();
1387713933
channel_state.clear_remote_stfu_sent();
13878-
channel_state.clear_quiescent();
13934+
if self.should_reset_pending_splice_funding_negotiation().unwrap_or(true) {
13935+
// If we were in quiescence but a splice was never negotiated, or the
13936+
// negotiation failed due to disconnecting, we shouldn't be quiescent
13937+
// anymore upon reconnecting.
13938+
channel_state.clear_quiescent();
13939+
}
1387913940
},
1388013941
ChannelState::FundingNegotiated(_)
1388113942
if self.context.interactive_tx_signing_session.is_some() => {},
@@ -14238,6 +14299,20 @@ where
1423814299
let holder_commitment_point_next = self.holder_commitment_point.next_point();
1423914300
let holder_commitment_point_pending_next = self.holder_commitment_point.pending_next_point;
1424014301

14302+
let interactive_tx_signing_session =
14303+
if self.should_reset_pending_splice_funding_negotiation().unwrap_or(false) {
14304+
None
14305+
} else {
14306+
self.context.interactive_tx_signing_session.as_ref()
14307+
};
14308+
let pending_splice = if self.should_reset_pending_splice_state() {
14309+
None
14310+
} else {
14311+
// We don't have to worry about resetting the pending `FundingNegotiation` because we
14312+
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
14313+
self.pending_splice.as_ref()
14314+
};
14315+
1424114316
write_tlv_fields!(writer, {
1424214317
(0, self.context.announcement_sigs, option),
1424314318
// minimum_depth and counterparty_selected_channel_reserve_satoshis used to have a
@@ -14281,12 +14356,12 @@ where
1428114356
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124
1428214357
(55, removed_htlc_attribution_data, optional_vec), // Added in 0.2
1428314358
(57, holding_cell_attribution_data, optional_vec), // Added in 0.2
14284-
(58, self.context.interactive_tx_signing_session, option), // Added in 0.2
14359+
(58, interactive_tx_signing_session, option), // Added in 0.2
1428514360
(59, self.funding.minimum_depth_override, option), // Added in 0.2
1428614361
(60, self.context.historical_scids, optional_vec), // Added in 0.2
1428714362
(61, fulfill_attribution_data, optional_vec), // Added in 0.2
1428814363
(63, holder_commitment_point_current, option), // Added in 0.2
14289-
(64, self.pending_splice, option), // Added in 0.2
14364+
(64, pending_splice, option), // Added in 0.2
1429014365
(65, self.quiescent_action, option), // Added in 0.2
1429114366
(67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2
1429214367
(69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2
@@ -14950,6 +15025,15 @@ where
1495015025
}
1495115026
};
1495215027

15028+
if let Some(funding_negotiation) = pending_splice
15029+
.as_ref()
15030+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
15031+
{
15032+
if !matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. }) {
15033+
return Err(DecodeError::InvalidValue);
15034+
}
15035+
}
15036+
1495315037
Ok(FundedChannel {
1495415038
funding: FundingScope {
1495515039
value_to_self_msat,

lightning/src/ln/quiescence_tests.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::ln::functional_test_utils::*;
77
use crate::ln::msgs;
88
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent};
99
use crate::util::errors::APIError;
10+
use crate::util::ser::Writeable;
1011
use crate::util::test_channel_signer::SignerOp;
1112

1213
#[test]
@@ -699,3 +700,62 @@ fn test_quiescence_during_disconnection() {
699700
do_test_quiescence_during_disconnection(false, true);
700701
do_test_quiescence_during_disconnection(true, true);
701702
}
703+
704+
#[test]
705+
fn test_quiescence_termination_on_disconnect() {
706+
do_test_quiescence_termination_on_disconnect(false);
707+
do_test_quiescence_termination_on_disconnect(true);
708+
}
709+
710+
fn do_test_quiescence_termination_on_disconnect(reload: bool) {
711+
// Tests that we terminate quiescence on disconnect if there's no quiescence protocol taking place.
712+
let chanmon_cfgs = create_chanmon_cfgs(2);
713+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
714+
let (persister_0a, persister_1a);
715+
let (chain_monitor_0a, chain_monitor_1a);
716+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
717+
let (node_0a, node_1a);
718+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
719+
720+
let node_id_0 = nodes[0].node.get_our_node_id();
721+
let node_id_1 = nodes[1].node.get_our_node_id();
722+
723+
let (_, _, channel_id, _) = create_announced_chan_between_nodes(&nodes, 0, 1);
724+
725+
nodes[0].node.maybe_propose_quiescence(&node_id_1, &channel_id).unwrap();
726+
727+
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
728+
nodes[1].node.handle_stfu(node_id_0, &stfu);
729+
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
730+
nodes[0].node.handle_stfu(node_id_1, &stfu);
731+
732+
if reload {
733+
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
734+
reload_node!(
735+
nodes[0],
736+
&nodes[0].node.encode(),
737+
&[&encoded_monitor_0],
738+
persister_0a,
739+
chain_monitor_0a,
740+
node_0a
741+
);
742+
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
743+
reload_node!(
744+
nodes[1],
745+
&nodes[1].node.encode(),
746+
&[&encoded_monitor_1],
747+
persister_1a,
748+
chain_monitor_1a,
749+
node_1a
750+
);
751+
} else {
752+
nodes[0].node.peer_disconnected(node_id_1);
753+
nodes[1].node.peer_disconnected(node_id_0);
754+
}
755+
756+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
757+
reconnect_args.send_channel_ready = (true, true);
758+
reconnect_nodes(reconnect_args);
759+
760+
send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
761+
}

0 commit comments

Comments
 (0)