Skip to content

Commit 0b01894

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 a39025b commit 0b01894

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

lightning/src/ln/channel.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,7 +1849,7 @@ where
18491849

18501850
pub fn tx_abort<L: Deref>(
18511851
&mut self, msg: &msgs::TxAbort, logger: &L,
1852-
) -> Result<Option<msgs::TxAbort>, ChannelError>
1852+
) -> Result<Option<(msgs::TxAbort, SpliceFundingFailed)>, ChannelError>
18531853
where
18541854
L::Target: Logger,
18551855
{
@@ -1858,20 +1858,51 @@ where
18581858
// phase for this interactively constructed transaction and hence we have not exchanged
18591859
// `tx_signatures`. Either way, we never close the channel upon receiving a `tx_abort`:
18601860
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L574-L576
1861-
let should_ack = match &mut self.phase {
1861+
let (should_ack, splice_funding_failed) = match &mut self.phase {
18621862
ChannelPhase::Undefined => unreachable!(),
18631863
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {
18641864
let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment";
18651865
return Err(ChannelError::Warn(err.into()));
18661866
},
18671867
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1868-
pending_v2_channel.interactive_tx_constructor.take().is_some()
1868+
let had_constructor = pending_v2_channel.interactive_tx_constructor.take().is_some();
1869+
(had_constructor, None)
1870+
},
1871+
ChannelPhase::Funded(funded_channel) => {
1872+
let funding_negotiation_opt = funded_channel
1873+
.pending_splice
1874+
.as_mut()
1875+
.and_then(|pending_splice| pending_splice.funding_negotiation.take());
1876+
1877+
let should_ack = funding_negotiation_opt.is_some();
1878+
let splice_funding_failed = funding_negotiation_opt
1879+
.filter(|funding_negotiation| funding_negotiation.is_initiator())
1880+
.map(|funding_negotiation| {
1881+
// Create SpliceFundingFailed for the aborted splice
1882+
let (funding_txo, channel_type) = match &funding_negotiation {
1883+
FundingNegotiation::ConstructingTransaction { funding, .. } => {
1884+
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
1885+
},
1886+
FundingNegotiation::AwaitingSignatures { funding, .. } => {
1887+
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
1888+
},
1889+
FundingNegotiation::AwaitingAck { .. } => {
1890+
(None, None)
1891+
},
1892+
};
1893+
1894+
SpliceFundingFailed {
1895+
channel_id: funded_channel.context.channel_id,
1896+
counterparty_node_id: funded_channel.context.counterparty_node_id,
1897+
user_channel_id: funded_channel.context.user_id,
1898+
funding_txo,
1899+
channel_type,
1900+
contributed_inputs: Vec::new(),
1901+
contributed_outputs: Vec::new(),
1902+
}
1903+
});
1904+
(should_ack, splice_funding_failed)
18691905
},
1870-
ChannelPhase::Funded(funded_channel) => funded_channel
1871-
.pending_splice
1872-
.as_mut()
1873-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1874-
.is_some(),
18751906
};
18761907

18771908
// NOTE: Since at this point we have not sent a `tx_abort` message for this negotiation
@@ -1880,16 +1911,20 @@ where
18801911
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L560-L561
18811912
// For rationale why we echo back `tx_abort`:
18821913
// https://github.com/lightning/bolts/blob/247e83d/02-peer-protocol.md?plain=1#L578-L580
1883-
Ok(should_ack.then(|| {
1914+
let result = if should_ack {
18841915
let logger = WithChannelContext::from(logger, &self.context(), None);
18851916
let reason =
18861917
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
18871918
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
1888-
msgs::TxAbort {
1919+
let tx_abort_response = msgs::TxAbort {
18891920
channel_id: msg.channel_id,
18901921
data: "Acknowledged tx_abort".to_string().into_bytes(),
1891-
}
1892-
}))
1922+
};
1923+
splice_funding_failed.map(|failed| (tx_abort_response, failed))
1924+
} else {
1925+
None
1926+
};
1927+
Ok(result)
18931928
}
18941929

18951930
#[rustfmt::skip]

lightning/src/ln/channelmanager.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10428,10 +10428,24 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1042810428
match peer_state.channel_by_id.entry(msg.channel_id) {
1042910429
hash_map::Entry::Occupied(mut chan_entry) => {
1043010430
let res = chan_entry.get_mut().tx_abort(msg, &self.logger);
10431-
if let Some(msg) = try_channel_entry!(self, peer_state, res, chan_entry) {
10431+
let tx_abort_and_splice_failed = try_channel_entry!(self, peer_state, res, chan_entry);
10432+
10433+
// Emit SpliceFailed event and send TxAbort response if we had an active splice negotiation
10434+
if let Some((tx_abort_msg, splice_funding_failed)) = tx_abort_and_splice_failed {
10435+
let pending_events = &mut self.pending_events.lock().unwrap();
10436+
pending_events.push_back((events::Event::SpliceFailed {
10437+
channel_id: splice_funding_failed.channel_id,
10438+
counterparty_node_id: splice_funding_failed.counterparty_node_id,
10439+
user_channel_id: splice_funding_failed.user_channel_id,
10440+
funding_txo: splice_funding_failed.funding_txo,
10441+
channel_type: splice_funding_failed.channel_type,
10442+
contributed_inputs: splice_funding_failed.contributed_inputs,
10443+
contributed_outputs: splice_funding_failed.contributed_outputs,
10444+
}, None));
10445+
1043210446
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1043310447
node_id: *counterparty_node_id,
10434-
msg,
10448+
msg: tx_abort_msg,
1043510449
});
1043610450
}
1043710451
Ok(())

0 commit comments

Comments
 (0)