Skip to content

Commit d282a47

Browse files
committed
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.
1 parent 0cdcf54 commit d282a47

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
@@ -1654,7 +1654,7 @@ where
16541654
}
16551655
chan.context.channel_state.clear_local_stfu_sent();
16561656
chan.context.channel_state.clear_remote_stfu_sent();
1657-
if chan.should_reset_pending_splice_state() {
1657+
if chan.should_reset_pending_splice_state(false) {
16581658
// If there was a pending splice negotiation that failed due to disconnecting, we
16591659
// also take the opportunity to clean up our state.
16601660
let splice_funding_failed = chan.reset_pending_splice_state();
@@ -1775,7 +1775,7 @@ where
17751775
None
17761776
},
17771777
ChannelPhase::Funded(funded_channel) => {
1778-
if funded_channel.should_reset_pending_splice_state() {
1778+
if funded_channel.should_reset_pending_splice_state(false) {
17791779
funded_channel.reset_pending_splice_state()
17801780
} else {
17811781
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
@@ -1932,12 +1932,24 @@ where
19321932
(had_constructor, None)
19331933
},
19341934
ChannelPhase::Funded(funded_channel) => {
1935-
if funded_channel.has_pending_splice_awaiting_signatures() {
1935+
if funded_channel.has_pending_splice_awaiting_signatures()
1936+
&& funded_channel
1937+
.context()
1938+
.interactive_tx_signing_session
1939+
.as_ref()
1940+
.expect("We have a pending splice awaiting signatures")
1941+
.has_received_commitment_signed()
1942+
{
1943+
// We only force close once the counterparty tries to abort after committing to
1944+
// the splice via their initial `commitment_signed`. This is because our monitor
1945+
// state is updated with the post-splice commitment transaction upon receiving
1946+
// their `commitment_signed`, so we would need another monitor update to abandon
1947+
// it, which we don't currently support.
19361948
return Err(ChannelError::close(
19371949
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
19381950
));
19391951
}
1940-
if funded_channel.should_reset_pending_splice_state() {
1952+
if funded_channel.should_reset_pending_splice_state(true) {
19411953
let has_funding_negotiation = funded_channel
19421954
.pending_splice
19431955
.as_ref()
@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
26812693
}
26822694

26832695
impl PendingFunding {
2684-
fn can_abandon_state(&self) -> bool {
2685-
self.funding_negotiation
2686-
.as_ref()
2687-
.map(|funding_negotiation| {
2688-
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
2689-
})
2690-
.unwrap_or_else(|| {
2691-
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
2692-
debug_assert!(has_negotiated_candidates);
2693-
!has_negotiated_candidates
2694-
})
2695-
}
2696-
26972696
fn check_get_splice_locked<SP: Deref>(
26982697
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
26992698
) -> Option<msgs::SpliceLocked>
@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
68736872
}
68746873

68756874
macro_rules! maybe_create_splice_funding_failed {
6876-
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
6875+
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
68776876
$pending_splice
68786877
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
68796878
.filter(|funding_negotiation| funding_negotiation.is_initiator())
@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
68956894
interactive_tx_constructor,
68966895
..
68976896
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
6898-
FundingNegotiation::AwaitingSignatures { .. } => {
6899-
debug_assert!(false);
6900-
(Vec::new(), Vec::new())
6901-
},
6897+
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
6898+
.context
6899+
.interactive_tx_signing_session
6900+
.$get()
6901+
.expect("We have a pending splice awaiting signatures")
6902+
.$contributed_inputs_and_outputs(),
69026903
};
69036904

69046905
SpliceFundingFailed {
@@ -6937,7 +6938,7 @@ where
69376938

69386939
fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
69396940
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
6940-
if self.should_reset_pending_splice_state() {
6941+
if self.should_reset_pending_splice_state(false) {
69416942
self.reset_pending_splice_state()
69426943
} else {
69436944
match self.quiescent_action.take() {
@@ -7011,19 +7012,54 @@ where
70117012

70127013
/// Returns a boolean indicating whether we should reset the splice's
70137014
/// [`PendingFunding::funding_negotiation`].
7014-
fn should_reset_pending_splice_state(&self) -> bool {
7015+
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
70157016
self.pending_splice
70167017
.as_ref()
7017-
.map(|pending_splice| pending_splice.can_abandon_state())
7018+
.map(|pending_splice| {
7019+
pending_splice
7020+
.funding_negotiation
7021+
.as_ref()
7022+
.map(|funding_negotiation| {
7023+
let is_awaiting_signatures = matches!(
7024+
funding_negotiation,
7025+
FundingNegotiation::AwaitingSignatures { .. }
7026+
);
7027+
if counterparty_aborted {
7028+
!is_awaiting_signatures
7029+
|| !self
7030+
.context()
7031+
.interactive_tx_signing_session
7032+
.as_ref()
7033+
.expect("We have a pending splice awaiting signatures")
7034+
.has_received_commitment_signed()
7035+
} else {
7036+
!is_awaiting_signatures
7037+
}
7038+
})
7039+
.unwrap_or_else(|| {
7040+
let has_negotiated_candidates =
7041+
!pending_splice.negotiated_candidates.is_empty();
7042+
debug_assert!(has_negotiated_candidates);
7043+
!has_negotiated_candidates
7044+
})
7045+
})
70187046
.unwrap_or(false)
70197047
}
70207048

70217049
fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
7022-
debug_assert!(self.should_reset_pending_splice_state());
7023-
debug_assert!(self.context.interactive_tx_signing_session.is_none());
7024-
self.context.channel_state.clear_quiescent();
7050+
debug_assert!(self.should_reset_pending_splice_state(true));
7051+
debug_assert!(
7052+
self.context.interactive_tx_signing_session.is_none()
7053+
|| !self
7054+
.context
7055+
.interactive_tx_signing_session
7056+
.as_ref()
7057+
.expect("We have a pending splice awaiting signatures")
7058+
.has_received_commitment_signed()
7059+
);
70257060

70267061
let splice_funding_failed = maybe_create_splice_funding_failed!(
7062+
self,
70277063
self.pending_splice.as_mut(),
70287064
take,
70297065
into_contributed_inputs_and_outputs
@@ -7033,15 +7069,19 @@ where
70337069
self.pending_splice.take();
70347070
}
70357071

7072+
self.context.channel_state.clear_quiescent();
7073+
self.context.interactive_tx_signing_session.take();
7074+
70367075
splice_funding_failed
70377076
}
70387077

70397078
pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
7040-
if !self.should_reset_pending_splice_state() {
7079+
if !self.should_reset_pending_splice_state(false) {
70417080
return None;
70427081
}
70437082

70447083
maybe_create_splice_funding_failed!(
7084+
self,
70457085
self.pending_splice.as_ref(),
70467086
as_ref,
70477087
to_contributed_inputs_and_outputs
@@ -11996,7 +12036,7 @@ where
1199612036
pub fn abandon_splice(
1199712037
&mut self,
1199812038
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
11999-
if self.should_reset_pending_splice_state() {
12039+
if self.should_reset_pending_splice_state(false) {
1200012040
let tx_abort =
1200112041
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
1200212042
let splice_funding_failed = self.reset_pending_splice_state();
@@ -14361,7 +14401,7 @@ where
1436114401
}
1436214402
channel_state.clear_local_stfu_sent();
1436314403
channel_state.clear_remote_stfu_sent();
14364-
if self.should_reset_pending_splice_state()
14404+
if self.should_reset_pending_splice_state(false)
1436514405
|| !self.has_pending_splice_awaiting_signatures()
1436614406
{
1436714407
// We shouldn't be quiescent anymore upon reconnecting if:
@@ -14735,7 +14775,7 @@ where
1473514775
// We don't have to worry about resetting the pending `FundingNegotiation` because we
1473614776
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
1473714777
let pending_splice =
14738-
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
14778+
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));
1473914779

1474014780
write_tlv_fields!(writer, {
1474114781
(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
@@ -350,6 +350,36 @@ impl ConstructedTransaction {
350350
NegotiationError { reason, contributed_inputs, contributed_outputs }
351351
}
352352

353+
fn to_contributed_inputs_and_outputs(&self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
354+
let contributed_inputs = self
355+
.tx
356+
.input
357+
.iter()
358+
.zip(self.input_metadata.iter())
359+
.enumerate()
360+
.filter(|(_, (_, input))| input.is_local(self.holder_is_initiator))
361+
.filter(|(index, _)| {
362+
self.shared_input_index
363+
.map(|shared_index| *index != shared_index as usize)
364+
.unwrap_or(true)
365+
})
366+
.map(|(_, (txin, _))| txin.previous_output)
367+
.collect();
368+
369+
let contributed_outputs = self
370+
.tx
371+
.output
372+
.iter()
373+
.zip(self.output_metadata.iter())
374+
.enumerate()
375+
.filter(|(_, (_, output))| output.is_local(self.holder_is_initiator))
376+
.filter(|(index, _)| *index != self.shared_output_index as usize)
377+
.map(|(_, (txout, _))| txout.clone())
378+
.collect();
379+
380+
(contributed_inputs, contributed_outputs)
381+
}
382+
353383
fn into_contributed_inputs_and_outputs(self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
354384
let contributed_inputs = self
355385
.tx
@@ -859,6 +889,10 @@ impl InteractiveTxSigningSession {
859889
self.unsigned_tx.into_negotiation_error(reason)
860890
}
861891

892+
pub(super) fn to_contributed_inputs_and_outputs(&self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
893+
self.unsigned_tx.to_contributed_inputs_and_outputs()
894+
}
895+
862896
pub(super) fn into_contributed_inputs_and_outputs(self) -> (Vec<BitcoinOutPoint>, Vec<TxOut>) {
863897
self.unsigned_tx.into_contributed_inputs_and_outputs()
864898
}

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)