Skip to content

Commit 8852e8e

Browse files
committed
Emit SpliceFailed event when tx_abort is received
When a tx_abort message is successfully processed for a funded channel with an active splice negotiation, emit Event::SpliceFailed to notify users that the splice operation was aborted by the counterparty.
1 parent 1631837 commit 8852e8e

File tree

3 files changed

+212
-13
lines changed

3 files changed

+212
-13
lines changed

lightning/src/ln/channel.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,7 +1834,7 @@ where
18341834

18351835
pub fn tx_abort<L: Deref>(
18361836
&mut self, msg: &msgs::TxAbort, logger: &L,
1837-
) -> Result<Option<msgs::TxAbort>, ChannelError>
1837+
) -> Result<(Option<msgs::TxAbort>, Option<SpliceFundingFailed>), ChannelError>
18381838
where
18391839
L::Target: Logger,
18401840
{
@@ -1843,14 +1843,16 @@ where
18431843
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
18441844
// For rationale why we echo back `tx_abort`:
18451845
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
1846-
let should_ack = match &mut self.phase {
1846+
let (should_ack, splice_funding_failed) = match &mut self.phase {
18471847
ChannelPhase::Undefined => unreachable!(),
18481848
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
18491849
let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment";
18501850
return Err(ChannelError::Warn(err.into()));
18511851
},
18521852
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1853-
pending_v2_channel.interactive_tx_constructor.take().is_some()
1853+
let had_constructor =
1854+
pending_v2_channel.interactive_tx_constructor.take().is_some();
1855+
(had_constructor, None)
18541856
},
18551857
ChannelPhase::Funded(funded_channel) => {
18561858
if funded_channel.has_pending_splice_awaiting_signatures() {
@@ -1865,17 +1867,17 @@ where
18651867
.map(|pending_splice| pending_splice.funding_negotiation.is_some())
18661868
.unwrap_or(false);
18671869
debug_assert!(has_funding_negotiation);
1868-
funded_channel.reset_pending_splice_state();
1869-
true
1870+
let splice_funding_failed = funded_channel.reset_pending_splice_state();
1871+
(true, splice_funding_failed)
18701872
} else {
18711873
// We were not tracking the pending funding negotiation state anymore, likely
18721874
// due to a disconnection or already having sent our own `tx_abort`.
1873-
false
1875+
(false, None)
18741876
}
18751877
},
18761878
};
18771879

1878-
Ok(should_ack.then(|| {
1880+
let tx_abort = should_ack.then(|| {
18791881
let logger = WithChannelContext::from(logger, &self.context(), None);
18801882
let reason =
18811883
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
@@ -1884,7 +1886,9 @@ where
18841886
channel_id: msg.channel_id,
18851887
data: "Acknowledged tx_abort".to_string().into_bytes(),
18861888
}
1887-
}))
1889+
});
1890+
1891+
Ok((tx_abort, splice_funding_failed))
18881892
}
18891893

18901894
#[rustfmt::skip]
@@ -11828,6 +11832,32 @@ where
1182811832
})
1182911833
}
1183011834

11835+
#[cfg(test)]
11836+
pub fn abandon_splice(
11837+
&mut self,
11838+
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
11839+
if self.should_reset_pending_splice_state() {
11840+
let tx_abort =
11841+
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
11842+
let splice_funding_failed = self.reset_pending_splice_state();
11843+
Ok((tx_abort, splice_funding_failed))
11844+
} else if self.has_pending_splice_awaiting_signatures() {
11845+
Err(APIError::APIMisuseError {
11846+
err: format!(
11847+
"Channel {} splice cannot be abandoned; already awaiting signatures",
11848+
self.context.channel_id(),
11849+
),
11850+
})
11851+
} else {
11852+
Err(APIError::APIMisuseError {
11853+
err: format!(
11854+
"Channel {} splice cannot be abandoned; no pending splice",
11855+
self.context.channel_id(),
11856+
),
11857+
})
11858+
}
11859+
}
11860+
1183111861
/// Checks during handling splice_init
1183211862
pub fn validate_splice_init(
1183311863
&self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount,

lightning/src/ln/channelmanager.rs

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4744,6 +4744,94 @@ where
47444744
}
47454745
}
47464746

4747+
#[cfg(test)]
4748+
pub(crate) fn abandon_splice(
4749+
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey,
4750+
) -> Result<(), APIError> {
4751+
let mut res = Ok(());
4752+
PersistenceNotifierGuard::optionally_notify(self, || {
4753+
let result = self.internal_abandon_splice(channel_id, counterparty_node_id);
4754+
res = result;
4755+
match res {
4756+
Ok(_) => NotifyOption::SkipPersistHandleEvents,
4757+
Err(_) => NotifyOption::SkipPersistNoEvents,
4758+
}
4759+
});
4760+
res
4761+
}
4762+
4763+
#[cfg(test)]
4764+
fn internal_abandon_splice(
4765+
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey,
4766+
) -> Result<(), APIError> {
4767+
let per_peer_state = self.per_peer_state.read().unwrap();
4768+
4769+
let peer_state_mutex = match per_peer_state.get(counterparty_node_id).ok_or_else(|| {
4770+
APIError::ChannelUnavailable {
4771+
err: format!("Can't find a peer matching the passed counterparty node_id {counterparty_node_id}"),
4772+
}
4773+
}) {
4774+
Ok(p) => p,
4775+
Err(e) => return Err(e),
4776+
};
4777+
4778+
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
4779+
let peer_state = &mut *peer_state_lock;
4780+
4781+
// Look for the channel
4782+
match peer_state.channel_by_id.entry(*channel_id) {
4783+
hash_map::Entry::Occupied(mut chan_phase_entry) => {
4784+
if !chan_phase_entry.get().context().is_connected() {
4785+
// TODO: We should probably support this, but right now `splice_channel` refuses when
4786+
// the peer is disconnected, so we just check it here.
4787+
return Err(APIError::ChannelUnavailable {
4788+
err: "Cannot abandon splice while peer is disconnected".to_owned(),
4789+
});
4790+
}
4791+
4792+
if let Some(chan) = chan_phase_entry.get_mut().as_funded_mut() {
4793+
let (tx_abort, splice_funding_failed) = chan.abandon_splice()?;
4794+
4795+
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
4796+
node_id: *counterparty_node_id,
4797+
msg: tx_abort,
4798+
});
4799+
4800+
if let Some(splice_funding_failed) = splice_funding_failed {
4801+
let pending_events = &mut self.pending_events.lock().unwrap();
4802+
pending_events.push_back((
4803+
events::Event::SpliceFailed {
4804+
channel_id: *channel_id,
4805+
counterparty_node_id: *counterparty_node_id,
4806+
user_channel_id: chan.context.get_user_id(),
4807+
abandoned_funding_txo: splice_funding_failed.funding_txo,
4808+
channel_type: splice_funding_failed.channel_type,
4809+
contributed_inputs: splice_funding_failed.contributed_inputs,
4810+
contributed_outputs: splice_funding_failed.contributed_outputs,
4811+
},
4812+
None,
4813+
));
4814+
}
4815+
4816+
Ok(())
4817+
} else {
4818+
Err(APIError::ChannelUnavailable {
4819+
err: format!(
4820+
"Channel with id {} is not funded, cannot abandon splice",
4821+
channel_id
4822+
),
4823+
})
4824+
}
4825+
},
4826+
hash_map::Entry::Vacant(_) => Err(APIError::ChannelUnavailable {
4827+
err: format!(
4828+
"Channel with id {} not found for the passed counterparty node_id {}",
4829+
channel_id, counterparty_node_id,
4830+
),
4831+
}),
4832+
}
4833+
}
4834+
47474835
#[rustfmt::skip]
47484836
fn can_forward_htlc_to_outgoing_channel(
47494837
&self, chan: &mut FundedChannel<SP>, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails
@@ -10501,7 +10589,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1050110589

1050210590
#[rustfmt::skip]
1050310591
fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort)
10504-
-> Result<(), MsgHandleErrInternal> {
10592+
-> Result<NotifyOption, MsgHandleErrInternal> {
1050510593
let per_peer_state = self.per_peer_state.read().unwrap();
1050610594
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
1050710595
.ok_or_else(|| {
@@ -10515,13 +10603,35 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1051510603
match peer_state.channel_by_id.entry(msg.channel_id) {
1051610604
hash_map::Entry::Occupied(mut chan_entry) => {
1051710605
let res = chan_entry.get_mut().tx_abort(msg, &self.logger);
10518-
if let Some(msg) = try_channel_entry!(self, peer_state, res, chan_entry) {
10606+
let (tx_abort, splice_failed) = try_channel_entry!(self, peer_state, res, chan_entry);
10607+
10608+
let persist = if tx_abort.is_some() || splice_failed.is_some() {
10609+
NotifyOption::DoPersist
10610+
} else {
10611+
NotifyOption::SkipPersistNoEvents
10612+
};
10613+
10614+
if let Some(tx_abort_msg) = tx_abort {
1051910615
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1052010616
node_id: *counterparty_node_id,
10521-
msg,
10617+
msg: tx_abort_msg,
1052210618
});
1052310619
}
10524-
Ok(())
10620+
10621+
if let Some(splice_funding_failed) = splice_failed {
10622+
let pending_events = &mut self.pending_events.lock().unwrap();
10623+
pending_events.push_back((events::Event::SpliceFailed {
10624+
channel_id: msg.channel_id,
10625+
counterparty_node_id: *counterparty_node_id,
10626+
user_channel_id: chan_entry.get().context().get_user_id(),
10627+
abandoned_funding_txo: splice_funding_failed.funding_txo,
10628+
channel_type: splice_funding_failed.channel_type,
10629+
contributed_inputs: splice_funding_failed.contributed_inputs,
10630+
contributed_outputs: splice_funding_failed.contributed_outputs,
10631+
}, None));
10632+
}
10633+
10634+
Ok(persist)
1052510635
},
1052610636
hash_map::Entry::Vacant(_) => {
1052710637
Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
@@ -14868,8 +14978,13 @@ where
1486814978
// be persisted before any signatures are exchanged.
1486914979
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
1487014980
let res = self.internal_tx_abort(&counterparty_node_id, msg);
14981+
let persist = match &res {
14982+
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
14983+
Err(_) => NotifyOption::SkipPersistHandleEvents,
14984+
Ok(persist) => *persist,
14985+
};
1487114986
let _ = handle_error!(self, res, counterparty_node_id);
14872-
NotifyOption::SkipPersistHandleEvents
14987+
persist
1487314988
});
1487414989
}
1487514990

lightning/src/ln/splicing_tests.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,3 +1227,57 @@ fn fail_splice_on_interactive_tx_error() {
12271227
let tx_abort = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
12281228
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
12291229
}
1230+
1231+
#[test]
1232+
fn fail_splice_on_tx_abort() {
1233+
let chanmon_cfgs = create_chanmon_cfgs(2);
1234+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1235+
let config = test_default_anchors_channel_config();
1236+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
1237+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1238+
1239+
let initiator = &nodes[0];
1240+
let acceptor = &nodes[1];
1241+
1242+
let node_id_initiator = initiator.node.get_our_node_id();
1243+
let node_id_acceptor = acceptor.node.get_our_node_id();
1244+
1245+
let initial_channel_capacity = 100_000;
1246+
let (_, _, channel_id, _) =
1247+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
1248+
1249+
let coinbase_tx = provide_anchor_reserves(&nodes);
1250+
let splice_in_amount = initial_channel_capacity / 2;
1251+
let contribution = SpliceContribution::SpliceIn {
1252+
value: Amount::from_sat(splice_in_amount),
1253+
inputs: vec![FundingTxInput::new_p2wpkh(coinbase_tx, 0).unwrap()],
1254+
change_script: Some(nodes[0].wallet_source.get_change_script().unwrap()),
1255+
};
1256+
1257+
// Fail during interactive-tx construction by having the acceptor send tx_abort instead of
1258+
// tx_complete.
1259+
let _ = complete_splice_handshake(initiator, acceptor, channel_id, contribution.clone());
1260+
1261+
let tx_add_input =
1262+
get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
1263+
acceptor.node.handle_tx_add_input(node_id_initiator, &tx_add_input);
1264+
1265+
let _tx_complete =
1266+
get_event_msg!(acceptor, MessageSendEvent::SendTxComplete, node_id_initiator);
1267+
1268+
acceptor.node.abandon_splice(&channel_id, &node_id_initiator).unwrap();
1269+
let tx_abort = get_event_msg!(acceptor, MessageSendEvent::SendTxAbort, node_id_initiator);
1270+
initiator.node.handle_tx_abort(node_id_acceptor, &tx_abort);
1271+
1272+
let event = get_event!(initiator, Event::SpliceFailed);
1273+
match event {
1274+
Event::SpliceFailed { contributed_inputs, .. } => {
1275+
assert_eq!(contributed_inputs.len(), 1);
1276+
assert_eq!(contributed_inputs[0], contribution.inputs()[0].outpoint());
1277+
},
1278+
_ => panic!("Expected Event::SpliceFailed"),
1279+
}
1280+
1281+
let tx_abort = get_event_msg!(initiator, MessageSendEvent::SendTxAbort, node_id_acceptor);
1282+
acceptor.node.handle_tx_abort(node_id_initiator, &tx_abort);
1283+
}

0 commit comments

Comments
 (0)