Skip to content

Commit 6bb677f

Browse files
jkczyzclaude
andcommitted
Emit SpliceFailed events when tx_abort is received for funded channels
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. This extends the SpliceFailed event coverage to handle abort scenarios, providing comprehensive splice failure notifications across all stages: - AwaitingAck: funding_txo and channel_type are None since funding parameters were not yet established - ConstructingTransaction/AwaitingSignatures: Include actual funding information since negotiation had progressed to funding establishment The implementation captures splice context before taking the funding negotiation state, ensuring accurate failure information is available for event emission while maintaining proper tx_abort acknowledgment behavior per the Lightning specification. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 19db134 commit 6bb677f

File tree

2 files changed

+42
-15
lines changed

2 files changed

+42
-15
lines changed

lightning/src/ln/channel.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,7 +1815,7 @@ where
18151815

18161816
pub fn tx_abort<L: Deref>(
18171817
&mut self, msg: &msgs::TxAbort, logger: &L,
1818-
) -> Result<Option<msgs::TxAbort>, ChannelError>
1818+
) -> Result<Option<(msgs::TxAbort, SpliceFundingFailed)>, ChannelError>
18191819
where
18201820
L::Target: Logger,
18211821
{
@@ -1824,14 +1824,16 @@ where
18241824
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
18251825
// For rationale why we echo back `tx_abort`:
18261826
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
1827-
let should_ack = match &mut self.phase {
1827+
let (should_ack, splice_funding_failed) = match &mut self.phase {
18281828
ChannelPhase::Undefined => unreachable!(),
18291829
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
18301830
let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment";
18311831
return Err(ChannelError::Warn(err.into()));
18321832
},
18331833
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1834-
pending_v2_channel.interactive_tx_constructor.take().is_some()
1834+
let had_constructor =
1835+
pending_v2_channel.interactive_tx_constructor.take().is_some();
1836+
(had_constructor, None)
18351837
},
18361838
ChannelPhase::Funded(funded_channel) => {
18371839
if let Some(should_reset) =
@@ -1845,8 +1847,8 @@ where
18451847
.as_ref()
18461848
.map(|pending_splice| pending_splice.funding_negotiation.is_some())
18471849
.unwrap_or(false);
1848-
funded_channel.reset_pending_splice_state();
1849-
has_funding_negotiation
1850+
let splice_funding_failed = funded_channel.reset_pending_splice_state();
1851+
(has_funding_negotiation, splice_funding_failed)
18501852
} else {
18511853
return Err(ChannelError::close(
18521854
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
@@ -1855,21 +1857,25 @@ where
18551857
} else {
18561858
// We were not tracking the pending funding negotiation state anymore, likely
18571859
// due to a disconnection or already having sent our own `tx_abort`.
1858-
false
1860+
(false, None)
18591861
}
18601862
},
18611863
};
18621864

1863-
Ok(should_ack.then(|| {
1865+
let result = if should_ack {
18641866
let logger = WithChannelContext::from(logger, &self.context(), None);
18651867
let reason =
18661868
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
18671869
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
1868-
msgs::TxAbort {
1870+
let tx_abort_response = msgs::TxAbort {
18691871
channel_id: msg.channel_id,
18701872
data: "Acknowledged tx_abort".to_string().into_bytes(),
1871-
}
1872-
}))
1873+
};
1874+
splice_funding_failed.map(|failed| (tx_abort_response, failed))
1875+
} else {
1876+
None
1877+
};
1878+
Ok(result)
18731879
}
18741880

18751881
#[rustfmt::skip]

lightning/src/ln/channelmanager.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10473,7 +10473,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1047310473

1047410474
#[rustfmt::skip]
1047510475
fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort)
10476-
-> Result<(), MsgHandleErrInternal> {
10476+
-> Result<NotifyOption, MsgHandleErrInternal> {
1047710477
let per_peer_state = self.per_peer_state.read().unwrap();
1047810478
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
1047910479
.ok_or_else(|| {
@@ -10487,13 +10487,29 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1048710487
match peer_state.channel_by_id.entry(msg.channel_id) {
1048810488
hash_map::Entry::Occupied(mut chan_entry) => {
1048910489
let res = chan_entry.get_mut().tx_abort(msg, &self.logger);
10490-
if let Some(msg) = try_channel_entry!(self, peer_state, res, chan_entry) {
10490+
let tx_abort_and_splice_failed = try_channel_entry!(self, peer_state, res, chan_entry);
10491+
10492+
// Emit SpliceFailed event and send TxAbort response if we had an active splice negotiation
10493+
if let Some((tx_abort_msg, splice_funding_failed)) = tx_abort_and_splice_failed {
10494+
let pending_events = &mut self.pending_events.lock().unwrap();
10495+
pending_events.push_back((events::Event::SpliceFailed {
10496+
channel_id: msg.channel_id,
10497+
counterparty_node_id: *counterparty_node_id,
10498+
user_channel_id: chan_entry.get().context().get_user_id(),
10499+
funding_txo: splice_funding_failed.funding_txo,
10500+
channel_type: splice_funding_failed.channel_type,
10501+
contributed_inputs: splice_funding_failed.contributed_inputs,
10502+
contributed_outputs: splice_funding_failed.contributed_outputs,
10503+
}, None));
10504+
1049110505
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1049210506
node_id: *counterparty_node_id,
10493-
msg,
10507+
msg: tx_abort_msg,
1049410508
});
10509+
Ok(NotifyOption::DoPersist)
10510+
} else {
10511+
Ok(NotifyOption::SkipPersistNoEvents)
1049510512
}
10496-
Ok(())
1049710513
},
1049810514
hash_map::Entry::Vacant(_) => {
1049910515
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))
@@ -14855,8 +14871,13 @@ where
1485514871
// be persisted before any signatures are exchanged.
1485614872
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
1485714873
let res = self.internal_tx_abort(&counterparty_node_id, msg);
14874+
let persist = match &res {
14875+
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
14876+
Err(_) => NotifyOption::SkipPersistHandleEvents,
14877+
Ok(persist) => *persist,
14878+
};
1485814879
let _ = handle_error!(self, res, counterparty_node_id);
14859-
NotifyOption::SkipPersistHandleEvents
14880+
persist
1486014881
});
1486114882
}
1486214883

0 commit comments

Comments
 (0)