Skip to content

Commit f321396

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 41addd7 commit f321396

File tree

3 files changed

+114
-37
lines changed

3 files changed

+114
-37
lines changed

lightning/src/ln/channel.rs

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,43 +1714,56 @@ where
17141714

17151715
fn fail_interactive_tx_negotiation<L: Deref>(
17161716
&mut self, error: NegotiationError, logger: &L,
1717-
) -> msgs::TxAbort
1717+
) -> (msgs::TxAbort, Option<SpliceFundingFailed>)
17181718
where
17191719
L::Target: Logger,
17201720
{
17211721
let logger = WithChannelContext::from(logger, &self.context(), None);
1722-
let NegotiationError { reason, .. } = error;
1722+
let NegotiationError { reason, contributed_inputs, contributed_outputs } = error;
17231723
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
17241724

1725-
let _interactive_tx_constructor = match &mut self.phase {
1725+
let splice_funding_failed = match &mut self.phase {
17261726
ChannelPhase::Undefined => unreachable!(),
17271727
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
17281728
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1729-
pending_v2_channel.interactive_tx_constructor.take()
1729+
pending_v2_channel.interactive_tx_constructor.take();
1730+
None
1731+
},
1732+
ChannelPhase::Funded(funded_channel) => {
1733+
funded_channel
1734+
.pending_splice
1735+
.as_mut()
1736+
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1737+
.filter(|funding_negotiation| funding_negotiation.is_initiator())
1738+
.map(|funding_negotiation| {
1739+
let funding_txo = funding_negotiation
1740+
.as_funding()
1741+
.and_then(|funding| funding.get_funding_txo())
1742+
.map(|txo| txo.into_bitcoin_outpoint());
1743+
1744+
let channel_type = funding_negotiation
1745+
.as_funding()
1746+
.map(|funding| funding.get_channel_type().clone());
1747+
1748+
SpliceFundingFailed {
1749+
channel_id: funded_channel.context.channel_id,
1750+
counterparty_node_id: funded_channel.context.counterparty_node_id,
1751+
user_channel_id: funded_channel.context.user_id,
1752+
funding_txo,
1753+
channel_type,
1754+
contributed_inputs,
1755+
contributed_outputs,
1756+
}
1757+
})
17301758
},
1731-
ChannelPhase::Funded(funded_channel) => funded_channel
1732-
.pending_splice
1733-
.as_mut()
1734-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1735-
.and_then(|funding_negotiation| {
1736-
if let FundingNegotiation::ConstructingTransaction {
1737-
interactive_tx_constructor,
1738-
..
1739-
} = funding_negotiation
1740-
{
1741-
Some(interactive_tx_constructor)
1742-
} else {
1743-
None
1744-
}
1745-
}),
17461759
};
17471760

1748-
reason.into_tx_abort_msg(self.context().channel_id)
1761+
(reason.into_tx_abort_msg(self.context().channel_id), splice_funding_failed)
17491762
}
17501763

17511764
pub fn tx_add_input<L: Deref>(
17521765
&mut self, msg: &msgs::TxAddInput, logger: &L,
1753-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1766+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17541767
where
17551768
L::Target: Logger,
17561769
{
@@ -1769,7 +1782,7 @@ where
17691782

17701783
pub fn tx_add_output<L: Deref>(
17711784
&mut self, msg: &msgs::TxAddOutput, logger: &L,
1772-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1785+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17731786
where
17741787
L::Target: Logger,
17751788
{
@@ -1790,7 +1803,7 @@ where
17901803

17911804
pub fn tx_remove_input<L: Deref>(
17921805
&mut self, msg: &msgs::TxRemoveInput, logger: &L,
1793-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1806+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17941807
where
17951808
L::Target: Logger,
17961809
{
@@ -1811,7 +1824,7 @@ where
18111824

18121825
pub fn tx_remove_output<L: Deref>(
18131826
&mut self, msg: &msgs::TxRemoveOutput, logger: &L,
1814-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1827+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
18151828
where
18161829
L::Target: Logger,
18171830
{
@@ -1832,7 +1845,7 @@ where
18321845

18331846
pub fn tx_complete<L: Deref>(
18341847
&mut self, msg: &msgs::TxComplete, logger: &L,
1835-
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), msgs::TxAbort>
1848+
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), (msgs::TxAbort, Option<SpliceFundingFailed>)>
18361849
where
18371850
L::Target: Logger,
18381851
{
@@ -1985,6 +1998,7 @@ where
19851998
interactive_tx_constructor,
19861999
} = funding_negotiation
19872000
{
2001+
let is_initiator = interactive_tx_constructor.is_initiator();
19882002
let signing_session = interactive_tx_constructor.into_signing_session();
19892003
let commitment_signed = chan.context.funding_tx_constructed(
19902004
&mut funding,
@@ -1995,7 +2009,7 @@ where
19952009
)?;
19962010

19972011
pending_splice.funding_negotiation =
1998-
Some(FundingNegotiation::AwaitingSignatures { funding });
2012+
Some(FundingNegotiation::AwaitingSignatures { funding, is_initiator });
19992013

20002014
return Ok(commitment_signed);
20012015
} else {
@@ -2596,12 +2610,14 @@ enum FundingNegotiation {
25962610
},
25972611
AwaitingSignatures {
25982612
funding: FundingScope,
2613+
is_initiator: bool,
25992614
},
26002615
}
26012616

26022617
impl_writeable_tlv_based_enum_upgradable!(FundingNegotiation,
26032618
(0, AwaitingSignatures) => {
26042619
(1, funding, required),
2620+
(3, is_initiator, required),
26052621
},
26062622
unread_variants: AwaitingAck, ConstructingTransaction
26072623
);
@@ -2611,7 +2627,17 @@ impl FundingNegotiation {
26112627
match self {
26122628
FundingNegotiation::AwaitingAck { .. } => None,
26132629
FundingNegotiation::ConstructingTransaction { funding, .. } => Some(funding),
2614-
FundingNegotiation::AwaitingSignatures { funding } => Some(funding),
2630+
FundingNegotiation::AwaitingSignatures { funding, .. } => Some(funding),
2631+
}
2632+
}
2633+
2634+
fn is_initiator(&self) -> bool {
2635+
match self {
2636+
FundingNegotiation::AwaitingAck { context } => context.is_initiator,
2637+
FundingNegotiation::ConstructingTransaction { interactive_tx_constructor, .. } => {
2638+
interactive_tx_constructor.is_initiator()
2639+
},
2640+
FundingNegotiation::AwaitingSignatures { is_initiator, .. } => *is_initiator,
26152641
}
26162642
}
26172643
}
@@ -6802,6 +6828,25 @@ pub struct SpliceFundingNegotiated {
68026828
pub channel_type: ChannelTypeFeatures,
68036829
}
68046830

6831+
/// Information about a splice funding negotiation that has failed.
6832+
/// This is returned from channel operations and converted to an Event::SpliceFailed in ChannelManager.
6833+
pub struct SpliceFundingFailed {
6834+
/// The channel_id of the channel for which the splice failed.
6835+
pub channel_id: ChannelId,
6836+
/// The counterparty's node_id.
6837+
pub counterparty_node_id: PublicKey,
6838+
/// The user_channel_id value.
6839+
pub user_channel_id: u128,
6840+
/// The outpoint of the channel's splice funding transaction, if one was created.
6841+
pub funding_txo: Option<bitcoin::OutPoint>,
6842+
/// The features that this channel will operate with, if available.
6843+
pub channel_type: Option<ChannelTypeFeatures>,
6844+
/// Input outpoints contributed to the splice transaction.
6845+
pub contributed_inputs: Vec<bitcoin::OutPoint>,
6846+
/// Outputs contributed to the splice transaction.
6847+
pub contributed_outputs: Vec<bitcoin::TxOut>,
6848+
}
6849+
68056850
pub struct SpliceFundingPromotion {
68066851
pub funding_txo: OutPoint,
68076852
pub monitor_update: Option<ChannelMonitorUpdate>,
@@ -8649,7 +8694,7 @@ where
86498694

86508695
if let Some(pending_splice) = self.pending_splice.as_mut() {
86518696
self.context.channel_state.clear_quiescent();
8652-
if let Some(FundingNegotiation::AwaitingSignatures { mut funding }) =
8697+
if let Some(FundingNegotiation::AwaitingSignatures { mut funding, .. }) =
86538698
pending_splice.funding_negotiation.take()
86548699
{
86558700
funding.funding_transaction = Some(funding_tx);
@@ -9566,7 +9611,7 @@ where
95669611
.as_ref()
95679612
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
95689613
.and_then(|funding_negotiation| {
9569-
if let FundingNegotiation::AwaitingSignatures { funding } = &funding_negotiation {
9614+
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
95709615
Some(funding)
95719616
} else {
95729617
None

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-
StfuResponse, UpdateFulfillCommitFetch, WithChannelContext,
64+
SpliceFundingFailed, StfuResponse, UpdateFulfillCommitFetch, WithChannelContext,
6565
};
6666
use crate::ln::channel_state::ChannelDetails;
6767
use crate::ln::funding::SpliceContribution;
@@ -9997,7 +9997,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
99979997
}
99989998

99999999
fn internal_tx_msg<
10000-
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, msgs::TxAbort>,
10000+
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>,
1000110001
>(
1000210002
&self, counterparty_node_id: &PublicKey, channel_id: ChannelId,
1000310003
tx_msg_handler: HandleTxMsgFn,
@@ -10015,16 +10015,30 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1001510015
match peer_state.channel_by_id.entry(channel_id) {
1001610016
hash_map::Entry::Occupied(mut chan_entry) => {
1001710017
let channel = chan_entry.get_mut();
10018-
let msg_send_event = match tx_msg_handler(channel) {
10019-
Ok(msg_send) => msg_send.into_msg_send_event(*counterparty_node_id),
10020-
Err(tx_abort) => {
10021-
MessageSendEvent::SendTxAbort {
10018+
match tx_msg_handler(channel) {
10019+
Ok(msg_send) => {
10020+
let msg_send_event = msg_send.into_msg_send_event(*counterparty_node_id);
10021+
peer_state.pending_msg_events.push(msg_send_event);
10022+
},
10023+
Err((tx_abort, splice_funding_failed)) => {
10024+
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1002210025
node_id: *counterparty_node_id,
1002310026
msg: tx_abort,
10027+
});
10028+
if let Some(splice_funding_failed) = splice_funding_failed {
10029+
let pending_events = &mut self.pending_events.lock().unwrap();
10030+
pending_events.push_back((events::Event::SpliceFailed {
10031+
channel_id,
10032+
counterparty_node_id: *counterparty_node_id,
10033+
user_channel_id: channel.context().get_user_id(),
10034+
funding_txo: splice_funding_failed.funding_txo,
10035+
channel_type: splice_funding_failed.channel_type.clone(),
10036+
contributed_inputs: splice_funding_failed.contributed_inputs,
10037+
contributed_outputs: splice_funding_failed.contributed_outputs,
10038+
}, None));
1002410039
}
1002510040
},
1002610041
};
10027-
peer_state.pending_msg_events.push(msg_send_event);
1002810042
Ok(())
1002910043
},
1003010044
hash_map::Entry::Vacant(_) => {
@@ -10104,11 +10118,23 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1010410118
});
1010510119
}
1010610120
},
10107-
Err(tx_abort) => {
10121+
Err((tx_abort, splice_funding_failed)) => {
1010810122
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1010910123
node_id: counterparty_node_id,
1011010124
msg: tx_abort,
1011110125
});
10126+
if let Some(splice_funding_failed) = splice_funding_failed {
10127+
let pending_events = &mut self.pending_events.lock().unwrap();
10128+
pending_events.push_back((events::Event::SpliceFailed {
10129+
channel_id: msg.channel_id,
10130+
counterparty_node_id,
10131+
user_channel_id: chan.context().get_user_id(),
10132+
funding_txo: splice_funding_failed.funding_txo,
10133+
channel_type: splice_funding_failed.channel_type.clone(),
10134+
contributed_inputs: splice_funding_failed.contributed_inputs,
10135+
contributed_outputs: splice_funding_failed.contributed_outputs,
10136+
}, None));
10137+
}
1011210138
},
1011310139
}
1011410140
Ok(())

lightning/src/ln/interactivetxs.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,7 @@ impl InteractiveTxInput {
19161916

19171917
pub(super) struct InteractiveTxConstructor {
19181918
state_machine: StateMachine,
1919+
is_initiator: bool,
19191920
initiator_first_message: Option<InteractiveTxMessageSend>,
19201921
channel_id: ChannelId,
19211922
inputs_to_contribute: Vec<(SerialId, InputOwned)>,
@@ -2090,6 +2091,7 @@ impl InteractiveTxConstructor {
20902091

20912092
let mut constructor = Self {
20922093
state_machine,
2094+
is_initiator,
20932095
initiator_first_message: None,
20942096
channel_id,
20952097
inputs_to_contribute,
@@ -2102,6 +2104,10 @@ impl InteractiveTxConstructor {
21022104
Ok(constructor)
21032105
}
21042106

2107+
pub fn is_initiator(&self) -> bool {
2108+
self.is_initiator
2109+
}
2110+
21052111
pub fn take_initiator_first_message(&mut self) -> Option<InteractiveTxMessageSend> {
21062112
self.initiator_first_message.take()
21072113
}

0 commit comments

Comments
 (0)