Skip to content

Commit 17b99e0

Browse files
wpaulinoTheBlueMatt
authored andcommitted
Allow counterparty tx_abort before handling initial commitment signed
Upon processing the counterparty's initial `commitment_signed` for a splice, we queue a monitor update with the new commitment transactions spending the new funding transaction. Once handled, funding transaction signatures can be exchanged and we must start monitoring the chain for a possible commitment transaction broadcast spending the new funding transaction. Aborting the splice negotiation then would therefore be unsafe, hence why we currently force close in such cases. However, there is no reason to force close prior to receiving their initial `commitment_signed`, as this would imply the funding transaction signatures have yet to be exchanged, thus making a commitment broadcast spending said transaction impossible allowing us to abort the splice negotiation safely. Backport of d282a47
1 parent 1f156c9 commit 17b99e0

File tree

3 files changed

+207
-37
lines changed

3 files changed

+207
-37
lines changed

lightning/src/ln/channel.rs

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,7 @@ where
16451645
}
16461646
chan.context.channel_state.clear_local_stfu_sent();
16471647
chan.context.channel_state.clear_remote_stfu_sent();
1648-
if chan.should_reset_pending_splice_state() {
1648+
if chan.should_reset_pending_splice_state(false) {
16491649
// If there was a pending splice negotiation that failed due to disconnecting, we
16501650
// also take the opportunity to clean up our state.
16511651
let splice_funding_failed = chan.reset_pending_splice_state();
@@ -1766,7 +1766,7 @@ where
17661766
None
17671767
},
17681768
ChannelPhase::Funded(funded_channel) => {
1769-
if funded_channel.should_reset_pending_splice_state() {
1769+
if funded_channel.should_reset_pending_splice_state(false) {
17701770
funded_channel.reset_pending_splice_state()
17711771
} else {
17721772
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
@@ -1923,12 +1923,24 @@ where
19231923
(had_constructor, None)
19241924
},
19251925
ChannelPhase::Funded(funded_channel) => {
1926-
if funded_channel.has_pending_splice_awaiting_signatures() {
1926+
if funded_channel.has_pending_splice_awaiting_signatures()
1927+
&& funded_channel
1928+
.context()
1929+
.interactive_tx_signing_session
1930+
.as_ref()
1931+
.expect("We have a pending splice awaiting signatures")
1932+
.has_received_commitment_signed()
1933+
{
1934+
// We only force close once the counterparty tries to abort after committing to
1935+
// the splice via their initial `commitment_signed`. This is because our monitor
1936+
// state is updated with the post-splice commitment transaction upon receiving
1937+
// their `commitment_signed`, so we would need another monitor update to abandon
1938+
// it, which we don't currently support.
19271939
return Err(ChannelError::close(
19281940
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
19291941
));
19301942
}
1931-
if funded_channel.should_reset_pending_splice_state() {
1943+
if funded_channel.should_reset_pending_splice_state(true) {
19321944
let has_funding_negotiation = funded_channel
19331945
.pending_splice
19341946
.as_ref()
@@ -2695,19 +2707,6 @@ impl FundingNegotiation {
26952707
}
26962708

26972709
impl PendingFunding {
2698-
fn can_abandon_state(&self) -> bool {
2699-
self.funding_negotiation
2700-
.as_ref()
2701-
.map(|funding_negotiation| {
2702-
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
2703-
})
2704-
.unwrap_or_else(|| {
2705-
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
2706-
debug_assert!(has_negotiated_candidates);
2707-
!has_negotiated_candidates
2708-
})
2709-
}
2710-
27112710
fn check_get_splice_locked<SP: Deref>(
27122711
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
27132712
) -> Option<msgs::SpliceLocked>
@@ -6890,7 +6889,7 @@ pub struct SpliceFundingFailed {
68906889
}
68916890

68926891
macro_rules! maybe_create_splice_funding_failed {
6893-
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
6892+
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
68946893
$pending_splice
68956894
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
68966895
.filter(|funding_negotiation| funding_negotiation.is_initiator())
@@ -6912,10 +6911,12 @@ macro_rules! maybe_create_splice_funding_failed {
69126911
interactive_tx_constructor,
69136912
..
69146913
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
6915-
FundingNegotiation::AwaitingSignatures { .. } => {
6916-
debug_assert!(false);
6917-
(Vec::new(), Vec::new())
6918-
},
6914+
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
6915+
.context
6916+
.interactive_tx_signing_session
6917+
.$get()
6918+
.expect("We have a pending splice awaiting signatures")
6919+
.$contributed_inputs_and_outputs(),
69196920
};
69206921

69216922
SpliceFundingFailed {
@@ -6954,7 +6955,7 @@ where
69546955

69556956
fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
69566957
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
6957-
if self.should_reset_pending_splice_state() {
6958+
if self.should_reset_pending_splice_state(false) {
69586959
self.reset_pending_splice_state()
69596960
} else {
69606961
match self.quiescent_action.take() {
@@ -7028,19 +7029,54 @@ where
70287029

70297030
/// Returns a boolean indicating whether we should reset the splice's
70307031
/// [`PendingFunding::funding_negotiation`].
7031-
fn should_reset_pending_splice_state(&self) -> bool {
7032+
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
70327033
self.pending_splice
70337034
.as_ref()
7034-
.map(|pending_splice| pending_splice.can_abandon_state())
7035+
.map(|pending_splice| {
7036+
pending_splice
7037+
.funding_negotiation
7038+
.as_ref()
7039+
.map(|funding_negotiation| {
7040+
let is_awaiting_signatures = matches!(
7041+
funding_negotiation,
7042+
FundingNegotiation::AwaitingSignatures { .. }
7043+
);
7044+
if counterparty_aborted {
7045+
!is_awaiting_signatures
7046+
|| !self
7047+
.context()
7048+
.interactive_tx_signing_session
7049+
.as_ref()
7050+
.expect("We have a pending splice awaiting signatures")
7051+
.has_received_commitment_signed()
7052+
} else {
7053+
!is_awaiting_signatures
7054+
}
7055+
})
7056+
.unwrap_or_else(|| {
7057+
let has_negotiated_candidates =
7058+
!pending_splice.negotiated_candidates.is_empty();
7059+
debug_assert!(has_negotiated_candidates);
7060+
!has_negotiated_candidates
7061+
})
7062+
})
70357063
.unwrap_or(false)
70367064
}
70377065

70387066
fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
7039-
debug_assert!(self.should_reset_pending_splice_state());
7040-
debug_assert!(self.context.interactive_tx_signing_session.is_none());
7041-
self.context.channel_state.clear_quiescent();
7067+
debug_assert!(self.should_reset_pending_splice_state(true));
7068+
debug_assert!(
7069+
self.context.interactive_tx_signing_session.is_none()
7070+
|| !self
7071+
.context
7072+
.interactive_tx_signing_session
7073+
.as_ref()
7074+
.expect("We have a pending splice awaiting signatures")
7075+
.has_received_commitment_signed()
7076+
);
70427077

70437078
let splice_funding_failed = maybe_create_splice_funding_failed!(
7079+
self,
70447080
self.pending_splice.as_mut(),
70457081
take,
70467082
into_contributed_inputs_and_outputs
@@ -7050,15 +7086,19 @@ where
70507086
self.pending_splice.take();
70517087
}
70527088

7089+
self.context.channel_state.clear_quiescent();
7090+
self.context.interactive_tx_signing_session.take();
7091+
70537092
splice_funding_failed
70547093
}
70557094

70567095
pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
7057-
if !self.should_reset_pending_splice_state() {
7096+
if !self.should_reset_pending_splice_state(false) {
70587097
return None;
70597098
}
70607099

70617100
maybe_create_splice_funding_failed!(
7101+
self,
70627102
self.pending_splice.as_ref(),
70637103
as_ref,
70647104
to_contributed_inputs_and_outputs
@@ -12013,7 +12053,7 @@ where
1201312053
pub fn abandon_splice(
1201412054
&mut self,
1201512055
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
12016-
if self.should_reset_pending_splice_state() {
12056+
if self.should_reset_pending_splice_state(false) {
1201712057
let tx_abort =
1201812058
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
1201912059
let splice_funding_failed = self.reset_pending_splice_state();
@@ -14382,7 +14422,7 @@ where
1438214422
}
1438314423
channel_state.clear_local_stfu_sent();
1438414424
channel_state.clear_remote_stfu_sent();
14385-
if self.should_reset_pending_splice_state()
14425+
if self.should_reset_pending_splice_state(false)
1438614426
|| !self.has_pending_splice_awaiting_signatures()
1438714427
{
1438814428
// We shouldn't be quiescent anymore upon reconnecting if:
@@ -14756,7 +14796,7 @@ where
1475614796
// We don't have to worry about resetting the pending `FundingNegotiation` because we
1475714797
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
1475814798
let pending_splice =
14759-
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
14799+
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));
1476014800

1476114801
write_tlv_fields!(writer, {
1476214802
(0, self.context.announcement_sigs, option),

lightning/src/ln/interactivetxs.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,36 @@ impl ConstructedTransaction {
362362
NegotiationError { reason, contributed_inputs, contributed_outputs }
363363
}
364364

365+
fn to_contributed_inputs_and_outputs(&self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
366+
let contributed_inputs = self
367+
.tx
368+
.input
369+
.iter()
370+
.zip(self.input_metadata.iter())
371+
.enumerate()
372+
.filter(|(_, (_, input))| input.is_local(self.holder_is_initiator))
373+
.filter(|(index, _)| {
374+
self.shared_input_index
375+
.map(|shared_index| *index != shared_index as usize)
376+
.unwrap_or(true)
377+
})
378+
.map(|(_, (txin, _))| txin.previous_output)
379+
.collect();
380+
381+
let contributed_outputs = self
382+
.tx
383+
.output
384+
.iter()
385+
.zip(self.output_metadata.iter())
386+
.enumerate()
387+
.filter(|(_, (_, output))| output.is_local(self.holder_is_initiator))
388+
.filter(|(index, _)| *index != self.shared_output_index as usize)
389+
.map(|(_, (txout, _))| txout.clone())
390+
.collect();
391+
392+
(contributed_inputs, contributed_outputs)
393+
}
394+
365395
fn into_contributed_inputs_and_outputs(self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
366396
let contributed_inputs = self
367397
.tx
@@ -871,6 +901,10 @@ impl InteractiveTxSigningSession {
871901
self.unsigned_tx.into_negotiation_error(reason)
872902
}
873903

904+
pub(super) fn to_contributed_inputs_and_outputs(&self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
905+
self.unsigned_tx.to_contributed_inputs_and_outputs()
906+
}
907+
874908
pub(super) fn into_contributed_inputs_and_outputs(self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
875909
self.unsigned_tx.into_contributed_inputs_and_outputs()
876910
}

lightning/src/ln/splicing_tests.rs

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,17 +395,28 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
395395
// retry later.
396396
let chanmon_cfgs = create_chanmon_cfgs(2);
397397
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
398-
let (persister_0a, persister_0b, persister_0c, persister_1a, persister_1b, persister_1c);
398+
let (
399+
persister_0a,
400+
persister_0b,
401+
persister_0c,
402+
persister_0d,
403+
persister_1a,
404+
persister_1b,
405+
persister_1c,
406+
persister_1d,
407+
);
399408
let (
400409
chain_monitor_0a,
401410
chain_monitor_0b,
402411
chain_monitor_0c,
412+
chain_monitor_0d,
403413
chain_monitor_1a,
404414
chain_monitor_1b,
405415
chain_monitor_1c,
416+
chain_monitor_1d,
406417
);
407418
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
408-
let (node_0a, node_0b, node_0c, node_1a, node_1b, node_1c);
419+
let (node_0a, node_0b, node_0c, node_0d, node_1a, node_1b, node_1c, node_1d);
409420
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
410421

411422
let node_id_0 = nodes[0].node.get_our_node_id();
@@ -533,9 +544,56 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
533544
reconnect_args.send_announcement_sigs = (true, true);
534545
reconnect_nodes(reconnect_args);
535546

536-
// Attempt a splice negotiation that completes, (i.e. `tx_signatures` are exchanged). Reconnecting
537-
// should not abort the negotiation or reset the splice state.
538-
let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
547+
nodes[0]
548+
.node
549+
.splice_channel(
550+
&channel_id,
551+
&node_id_1,
552+
contribution.clone(),
553+
FEERATE_FLOOR_SATS_PER_KW,
554+
None,
555+
)
556+
.unwrap();
557+
558+
// Attempt a splice negotiation that ends before the initial `commitment_signed` messages are
559+
// exchanged. The node missing the other's `commitment_signed` upon reconnecting should
560+
// implicitly abort the negotiation and reset the splice state such that we're able to retry
561+
// another splice later.
562+
let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1);
563+
nodes[1].node.handle_stfu(node_id_0, &stfu);
564+
let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0);
565+
nodes[0].node.handle_stfu(node_id_1, &stfu);
566+
567+
let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1);
568+
nodes[1].node.handle_splice_init(node_id_0, &splice_init);
569+
let splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0);
570+
nodes[0].node.handle_splice_ack(node_id_1, &splice_ack);
571+
572+
let tx_add_input = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddInput, node_id_1);
573+
nodes[1].node.handle_tx_add_input(node_id_0, &tx_add_input);
574+
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
575+
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
576+
577+
let tx_add_output = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddOutput, node_id_1);
578+
nodes[1].node.handle_tx_add_output(node_id_0, &tx_add_output);
579+
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
580+
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
581+
582+
let tx_add_output = get_event_msg!(nodes[0], MessageSendEvent::SendTxAddOutput, node_id_1);
583+
nodes[1].node.handle_tx_add_output(node_id_0, &tx_add_output);
584+
let tx_complete = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, node_id_0);
585+
nodes[0].node.handle_tx_complete(node_id_1, &tx_complete);
586+
587+
let msg_events = nodes[0].node.get_and_clear_pending_msg_events();
588+
assert_eq!(msg_events.len(), 2);
589+
if let MessageSendEvent::SendTxComplete { .. } = &msg_events[0] {
590+
} else {
591+
panic!("Unexpected event");
592+
}
593+
if let MessageSendEvent::UpdateHTLCs { .. } = &msg_events[1] {
594+
} else {
595+
panic!("Unexpected event");
596+
}
539597

540598
if reload {
541599
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
@@ -561,6 +619,44 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
561619
nodes[1].node.peer_disconnected(node_id_0);
562620
}
563621

622+
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
623+
reconnect_args.send_channel_ready = (true, false);
624+
reconnect_args.send_announcement_sigs = (true, true);
625+
reconnect_args.send_tx_abort = (true, false);
626+
reconnect_nodes(reconnect_args);
627+
628+
let tx_abort = get_event_msg!(nodes[0], MessageSendEvent::SendTxAbort, node_id_1);
629+
nodes[1].node.handle_tx_abort(node_id_0, &tx_abort);
630+
let _event = get_event!(nodes[0], Event::SpliceFailed);
631+
632+
// Attempt a splice negotiation that completes, (i.e. `tx_signatures` are exchanged). Reconnecting
633+
// should not abort the negotiation or reset the splice state.
634+
let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, contribution);
635+
636+
if reload {
637+
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
638+
reload_node!(
639+
nodes[0],
640+
&nodes[0].node.encode(),
641+
&[&encoded_monitor_0],
642+
persister_0d,
643+
chain_monitor_0d,
644+
node_0d
645+
);
646+
let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
647+
reload_node!(
648+
nodes[1],
649+
&nodes[1].node.encode(),
650+
&[&encoded_monitor_1],
651+
persister_1d,
652+
chain_monitor_1d,
653+
node_1d
654+
);
655+
} else {
656+
nodes[0].node.peer_disconnected(node_id_1);
657+
nodes[1].node.peer_disconnected(node_id_0);
658+
}
659+
564660
let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
565661
reconnect_args.send_announcement_sigs = (true, true);
566662
reconnect_nodes(reconnect_args);

0 commit comments

Comments
 (0)