Skip to content

Commit 0751efd

Browse files
jkczyzclaude
andcommitted
Emit SpliceFailed events for interactive transaction failures
When interactive transaction construction fails during splice funding negotiation, emit Event::SpliceFailed to notify users of the failure. This introduces a SpliceFundingFailed struct to carry failure information from channel functions to ChannelManager, where it is converted to the appropriate event. The struct includes channel metadata and placeholders for contributed inputs/outputs (currently empty pending access to private InteractiveTxConstructor fields). Updates all interactive transaction functions (tx_add_input, tx_add_output, tx_remove_input, tx_remove_output, tx_complete) to return failure information alongside abort messages, enabling proper event emission when splice negotiations fail. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 6830eff commit 0751efd

File tree

2 files changed

+98
-27
lines changed

2 files changed

+98
-27
lines changed

lightning/src/ln/channel.rs

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ pub struct SpliceFundingNegotiated {
126126
pub channel_type: ChannelTypeFeatures,
127127
}
128128

129+
/// Information about a splice funding negotiation that has failed.
130+
/// This is returned from channel operations and converted to an Event::SpliceFailed in ChannelManager.
131+
pub struct SpliceFundingFailed {
132+
/// The channel_id of the channel for which the splice failed.
133+
pub channel_id: ChannelId,
134+
/// The counterparty's node_id.
135+
pub counterparty_node_id: PublicKey,
136+
/// The user_channel_id value.
137+
pub user_channel_id: u128,
138+
/// The outpoint of the channel's splice funding transaction, if one was created.
139+
pub funding_txo: Option<bitcoin::OutPoint>,
140+
/// The features that this channel will operate with, if available.
141+
pub channel_type: Option<ChannelTypeFeatures>,
142+
/// Input outpoints contributed to the splice transaction.
143+
pub contributed_inputs: Vec<bitcoin::OutPoint>,
144+
/// Outputs contributed to the splice transaction.
145+
pub contributed_outputs: Vec<bitcoin::TxOut>,
146+
}
147+
129148
pub struct AvailableBalances {
130149
/// Total amount available for our counterparty to send to us.
131150
pub inbound_capacity_msat: u64,
@@ -1728,42 +1747,68 @@ where
17281747

17291748
fn fail_interactive_tx_negotiation<L: Deref>(
17301749
&mut self, reason: AbortReason, logger: &L,
1731-
) -> msgs::TxAbort
1750+
) -> (msgs::TxAbort, Option<SpliceFundingFailed>)
17321751
where
17331752
L::Target: Logger,
17341753
{
17351754
let logger = WithChannelContext::from(logger, &self.context(), None);
17361755
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
17371756

1757+
let mut splice_failed = None;
1758+
17381759
let _interactive_tx_constructor = match &mut self.phase {
17391760
ChannelPhase::Undefined => unreachable!(),
17401761
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
17411762
ChannelPhase::UnfundedV2(pending_v2_channel) => {
17421763
pending_v2_channel.interactive_tx_constructor.take()
17431764
},
1744-
ChannelPhase::Funded(funded_channel) => funded_channel
1745-
.pending_splice
1746-
.as_mut()
1747-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1748-
.and_then(|funding_negotiation| {
1749-
if let FundingNegotiation::ConstructingTransaction {
1750-
interactive_tx_constructor,
1751-
..
1752-
} = funding_negotiation
1753-
{
1754-
Some(interactive_tx_constructor)
1765+
ChannelPhase::Funded(funded_channel) => {
1766+
if let Some(pending_splice) = funded_channel.pending_splice.as_mut() {
1767+
if pending_splice.funding_negotiation.is_some() {
1768+
let funding_negotiation = pending_splice.funding_negotiation.take();
1769+
1770+
let (funding_txo, channel_type, contributed_inputs, contributed_outputs) =
1771+
if let Some(FundingNegotiation::ConstructingTransaction { funding, interactive_tx_constructor: _ }) = &funding_negotiation {
1772+
(
1773+
funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()),
1774+
Some(funding.get_channel_type().clone()),
1775+
Vec::new(), // TODO: Extract contributed inputs from interactive_tx_constructor
1776+
Vec::new(), // TODO: Extract contributed outputs from interactive_tx_constructor
1777+
)
1778+
} else {
1779+
(None, None, Vec::new(), Vec::new())
1780+
};
1781+
1782+
splice_failed = Some(SpliceFundingFailed {
1783+
channel_id: funded_channel.context.channel_id,
1784+
counterparty_node_id: funded_channel.context.counterparty_node_id,
1785+
user_channel_id: funded_channel.context.user_id,
1786+
funding_txo,
1787+
channel_type,
1788+
contributed_inputs,
1789+
contributed_outputs,
1790+
});
1791+
1792+
if let Some(FundingNegotiation::ConstructingTransaction { interactive_tx_constructor, .. }) = funding_negotiation {
1793+
Some(interactive_tx_constructor)
1794+
} else {
1795+
None
1796+
}
17551797
} else {
17561798
None
17571799
}
1758-
}),
1800+
} else {
1801+
None
1802+
}
1803+
},
17591804
};
17601805

1761-
reason.into_tx_abort_msg(self.context().channel_id)
1806+
(reason.into_tx_abort_msg(self.context().channel_id), splice_failed)
17621807
}
17631808

17641809
pub fn tx_add_input<L: Deref>(
17651810
&mut self, msg: &msgs::TxAddInput, logger: &L,
1766-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1811+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17671812
where
17681813
L::Target: Logger,
17691814
{
@@ -1778,7 +1823,7 @@ where
17781823

17791824
pub fn tx_add_output<L: Deref>(
17801825
&mut self, msg: &msgs::TxAddOutput, logger: &L,
1781-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1826+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17821827
where
17831828
L::Target: Logger,
17841829
{
@@ -1795,7 +1840,7 @@ where
17951840

17961841
pub fn tx_remove_input<L: Deref>(
17971842
&mut self, msg: &msgs::TxRemoveInput, logger: &L,
1798-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1843+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17991844
where
18001845
L::Target: Logger,
18011846
{
@@ -1812,7 +1857,7 @@ where
18121857

18131858
pub fn tx_remove_output<L: Deref>(
18141859
&mut self, msg: &msgs::TxRemoveOutput, logger: &L,
1815-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1860+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
18161861
where
18171862
L::Target: Logger,
18181863
{
@@ -1829,7 +1874,7 @@ where
18291874

18301875
pub fn tx_complete<L: Deref>(
18311876
&mut self, msg: &msgs::TxComplete, logger: &L,
1832-
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), msgs::TxAbort>
1877+
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), (msgs::TxAbort, Option<SpliceFundingFailed>)>
18331878
where
18341879
L::Target: Logger,
18351880
{

lightning/src/ln/channelmanager.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ use crate::ln::channel::QuiescentAction;
6161
use crate::ln::channel::{
6262
self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, FundedChannel,
6363
InboundV1Channel, OutboundV1Channel, PendingV2Channel, ReconnectionMsg, ShutdownResult,
64-
SpliceFundingNegotiated, StfuResponse, UpdateFulfillCommitFetch, WithChannelContext,
64+
SpliceFundingFailed, StfuResponse, UpdateFulfillCommitFetch, WithChannelContext,
6565
};
6666
use crate::ln::channel_state::ChannelDetails;
6767
use crate::ln::funding::SpliceContribution;
@@ -9994,7 +9994,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
99949994
}
99959995

99969996
fn internal_tx_msg<
9997-
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, msgs::TxAbort>,
9997+
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>,
99989998
>(
99999999
&self, counterparty_node_id: &PublicKey, channel_id: ChannelId,
1000010000
tx_msg_handler: HandleTxMsgFn,
@@ -10012,16 +10012,30 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1001210012
match peer_state.channel_by_id.entry(channel_id) {
1001310013
hash_map::Entry::Occupied(mut chan_entry) => {
1001410014
let channel = chan_entry.get_mut();
10015-
let msg_send_event = match tx_msg_handler(channel) {
10016-
Ok(msg_send) => msg_send.into_msg_send_event(*counterparty_node_id),
10017-
Err(tx_abort) => {
10018-
MessageSendEvent::SendTxAbort {
10015+
match tx_msg_handler(channel) {
10016+
Ok(msg_send) => {
10017+
let msg_send_event = msg_send.into_msg_send_event(*counterparty_node_id);
10018+
peer_state.pending_msg_events.push(msg_send_event);
10019+
},
10020+
Err((tx_abort, splice_funding_failed)) => {
10021+
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1001910022
node_id: *counterparty_node_id,
1002010023
msg: tx_abort,
10024+
});
10025+
if let Some(splice_funding_failed) = splice_funding_failed {
10026+
let pending_events = &mut self.pending_events.lock().unwrap();
10027+
pending_events.push_back((events::Event::SpliceFailed {
10028+
channel_id,
10029+
counterparty_node_id: *counterparty_node_id,
10030+
user_channel_id: channel.context().get_user_id(),
10031+
funding_txo: splice_funding_failed.funding_txo,
10032+
channel_type: splice_funding_failed.channel_type.clone(),
10033+
contributed_inputs: splice_funding_failed.contributed_inputs,
10034+
contributed_outputs: splice_funding_failed.contributed_outputs,
10035+
}, None));
1002110036
}
1002210037
},
1002310038
};
10024-
peer_state.pending_msg_events.push(msg_send_event);
1002510039
Ok(())
1002610040
},
1002710041
hash_map::Entry::Vacant(_) => {
@@ -10101,11 +10115,23 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1010110115
});
1010210116
}
1010310117
},
10104-
Err(tx_abort) => {
10118+
Err((tx_abort, splice_funding_failed)) => {
1010510119
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1010610120
node_id: counterparty_node_id,
1010710121
msg: tx_abort,
1010810122
});
10123+
if let Some(splice_funding_failed) = splice_funding_failed {
10124+
let pending_events = &mut self.pending_events.lock().unwrap();
10125+
pending_events.push_back((events::Event::SpliceFailed {
10126+
channel_id: msg.channel_id,
10127+
counterparty_node_id,
10128+
user_channel_id: chan.context().get_user_id(),
10129+
funding_txo: splice_funding_failed.funding_txo,
10130+
channel_type: splice_funding_failed.channel_type.clone(),
10131+
contributed_inputs: splice_funding_failed.contributed_inputs,
10132+
contributed_outputs: splice_funding_failed.contributed_outputs,
10133+
}, None));
10134+
}
1010910135
},
1011010136
}
1011110137
Ok(())

0 commit comments

Comments
 (0)