Skip to content

Commit 2fafc66

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 f6ccbec commit 2fafc66

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
@@ -1680,43 +1680,56 @@ where
16801680

16811681
fn fail_interactive_tx_negotiation<L: Deref>(
16821682
&mut self, error: NegotiationError, logger: &L,
1683-
) -> msgs::TxAbort
1683+
) -> (msgs::TxAbort, Option<SpliceFundingFailed>)
16841684
where
16851685
L::Target: Logger,
16861686
{
16871687
let logger = WithChannelContext::from(logger, &self.context(), None);
1688-
let NegotiationError { reason, .. } = error;
1688+
let NegotiationError { reason, contributed_inputs, contributed_outputs } = error;
16891689
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
16901690

1691-
let _interactive_tx_constructor = match &mut self.phase {
1691+
let splice_funding_failed = match &mut self.phase {
16921692
ChannelPhase::Undefined => unreachable!(),
16931693
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
16941694
ChannelPhase::UnfundedV2(pending_v2_channel) => {
1695-
pending_v2_channel.interactive_tx_constructor.take()
1695+
pending_v2_channel.interactive_tx_constructor.take();
1696+
None
1697+
},
1698+
ChannelPhase::Funded(funded_channel) => {
1699+
funded_channel
1700+
.pending_splice
1701+
.as_mut()
1702+
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1703+
.filter(|funding_negotiation| funding_negotiation.is_initiator())
1704+
.map(|funding_negotiation| {
1705+
let funding_txo = funding_negotiation
1706+
.as_funding()
1707+
.and_then(|funding| funding.get_funding_txo())
1708+
.map(|txo| txo.into_bitcoin_outpoint());
1709+
1710+
let channel_type = funding_negotiation
1711+
.as_funding()
1712+
.map(|funding| funding.get_channel_type().clone());
1713+
1714+
SpliceFundingFailed {
1715+
channel_id: funded_channel.context.channel_id,
1716+
counterparty_node_id: funded_channel.context.counterparty_node_id,
1717+
user_channel_id: funded_channel.context.user_id,
1718+
funding_txo,
1719+
channel_type,
1720+
contributed_inputs,
1721+
contributed_outputs,
1722+
}
1723+
})
16961724
},
1697-
ChannelPhase::Funded(funded_channel) => funded_channel
1698-
.pending_splice
1699-
.as_mut()
1700-
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
1701-
.and_then(|funding_negotiation| {
1702-
if let FundingNegotiation::ConstructingTransaction {
1703-
interactive_tx_constructor,
1704-
..
1705-
} = funding_negotiation
1706-
{
1707-
Some(interactive_tx_constructor)
1708-
} else {
1709-
None
1710-
}
1711-
}),
17121725
};
17131726

1714-
reason.into_tx_abort_msg(self.context().channel_id)
1727+
(reason.into_tx_abort_msg(self.context().channel_id), splice_funding_failed)
17151728
}
17161729

17171730
pub fn tx_add_input<L: Deref>(
17181731
&mut self, msg: &msgs::TxAddInput, logger: &L,
1719-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1732+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17201733
where
17211734
L::Target: Logger,
17221735
{
@@ -1735,7 +1748,7 @@ where
17351748

17361749
pub fn tx_add_output<L: Deref>(
17371750
&mut self, msg: &msgs::TxAddOutput, logger: &L,
1738-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1751+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17391752
where
17401753
L::Target: Logger,
17411754
{
@@ -1756,7 +1769,7 @@ where
17561769

17571770
pub fn tx_remove_input<L: Deref>(
17581771
&mut self, msg: &msgs::TxRemoveInput, logger: &L,
1759-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1772+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17601773
where
17611774
L::Target: Logger,
17621775
{
@@ -1777,7 +1790,7 @@ where
17771790

17781791
pub fn tx_remove_output<L: Deref>(
17791792
&mut self, msg: &msgs::TxRemoveOutput, logger: &L,
1780-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1793+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17811794
where
17821795
L::Target: Logger,
17831796
{
@@ -1798,7 +1811,7 @@ where
17981811

17991812
pub fn tx_complete<L: Deref>(
18001813
&mut self, msg: &msgs::TxComplete, logger: &L,
1801-
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), msgs::TxAbort>
1814+
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), (msgs::TxAbort, Option<SpliceFundingFailed>)>
18021815
where
18031816
L::Target: Logger,
18041817
{
@@ -1951,6 +1964,7 @@ where
19511964
interactive_tx_constructor,
19521965
} = funding_negotiation
19531966
{
1967+
let is_initiator = interactive_tx_constructor.is_initiator();
19541968
let signing_session = interactive_tx_constructor.into_signing_session();
19551969
let commitment_signed = chan.context.funding_tx_constructed(
19561970
&mut funding,
@@ -1961,7 +1975,7 @@ where
19611975
)?;
19621976

19631977
pending_splice.funding_negotiation =
1964-
Some(FundingNegotiation::AwaitingSignatures { funding });
1978+
Some(FundingNegotiation::AwaitingSignatures { funding, is_initiator });
19651979

19661980
return Ok(commitment_signed);
19671981
} else {
@@ -2563,12 +2577,14 @@ enum FundingNegotiation {
25632577
},
25642578
AwaitingSignatures {
25652579
funding: FundingScope,
2580+
is_initiator: bool,
25662581
},
25672582
}
25682583

25692584
impl_writeable_tlv_based_enum_upgradable!(FundingNegotiation,
25702585
(0, AwaitingSignatures) => {
25712586
(1, funding, required),
2587+
(3, is_initiator, required),
25722588
},
25732589
unread_variants: AwaitingAck, ConstructingTransaction
25742590
);
@@ -2578,7 +2594,17 @@ impl FundingNegotiation {
25782594
match self {
25792595
FundingNegotiation::AwaitingAck { .. } => None,
25802596
FundingNegotiation::ConstructingTransaction { funding, .. } => Some(funding),
2581-
FundingNegotiation::AwaitingSignatures { funding } => Some(funding),
2597+
FundingNegotiation::AwaitingSignatures { funding, .. } => Some(funding),
2598+
}
2599+
}
2600+
2601+
fn is_initiator(&self) -> bool {
2602+
match self {
2603+
FundingNegotiation::AwaitingAck { context } => context.is_initiator,
2604+
FundingNegotiation::ConstructingTransaction { interactive_tx_constructor, .. } => {
2605+
interactive_tx_constructor.is_initiator()
2606+
},
2607+
FundingNegotiation::AwaitingSignatures { is_initiator, .. } => *is_initiator,
25822608
}
25832609
}
25842610
}
@@ -6749,6 +6775,25 @@ pub struct SpliceFundingNegotiated {
67496775
pub channel_type: ChannelTypeFeatures,
67506776
}
67516777

6778+
/// Information about a splice funding negotiation that has failed.
6779+
/// This is returned from channel operations and converted to an Event::SpliceFailed in ChannelManager.
6780+
pub struct SpliceFundingFailed {
6781+
/// The channel_id of the channel for which the splice failed.
6782+
pub channel_id: ChannelId,
6783+
/// The counterparty's node_id.
6784+
pub counterparty_node_id: PublicKey,
6785+
/// The user_channel_id value.
6786+
pub user_channel_id: u128,
6787+
/// The outpoint of the channel's splice funding transaction, if one was created.
6788+
pub funding_txo: Option<bitcoin::OutPoint>,
6789+
/// The features that this channel will operate with, if available.
6790+
pub channel_type: Option<ChannelTypeFeatures>,
6791+
/// Input outpoints contributed to the splice transaction.
6792+
pub contributed_inputs: Vec<bitcoin::OutPoint>,
6793+
/// Outputs contributed to the splice transaction.
6794+
pub contributed_outputs: Vec<bitcoin::TxOut>,
6795+
}
6796+
67526797
pub struct SpliceFundingPromotion {
67536798
pub funding_txo: OutPoint,
67546799
pub monitor_update: Option<ChannelMonitorUpdate>,
@@ -8641,7 +8686,7 @@ where
86418686

86428687
if let Some(pending_splice) = self.pending_splice.as_mut() {
86438688
self.context.channel_state.clear_quiescent();
8644-
if let Some(FundingNegotiation::AwaitingSignatures { mut funding }) =
8689+
if let Some(FundingNegotiation::AwaitingSignatures { mut funding, .. }) =
86458690
pending_splice.funding_negotiation.take()
86468691
{
86478692
funding.funding_transaction = Some(funding_tx);
@@ -9555,7 +9600,7 @@ where
95559600
.as_ref()
95569601
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
95579602
.and_then(|funding_negotiation| {
9558-
if let FundingNegotiation::AwaitingSignatures { funding } = &funding_negotiation {
9603+
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
95599604
Some(funding)
95609605
} else {
95619606
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;
@@ -10205,7 +10205,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1020510205
}
1020610206

1020710207
fn internal_tx_msg<
10208-
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, msgs::TxAbort>,
10208+
HandleTxMsgFn: Fn(&mut Channel<SP>) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>,
1020910209
>(
1021010210
&self, counterparty_node_id: &PublicKey, channel_id: ChannelId,
1021110211
tx_msg_handler: HandleTxMsgFn,
@@ -10223,16 +10223,30 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1022310223
match peer_state.channel_by_id.entry(channel_id) {
1022410224
hash_map::Entry::Occupied(mut chan_entry) => {
1022510225
let channel = chan_entry.get_mut();
10226-
let msg_send_event = match tx_msg_handler(channel) {
10227-
Ok(msg_send) => msg_send.into_msg_send_event(*counterparty_node_id),
10228-
Err(tx_abort) => {
10229-
MessageSendEvent::SendTxAbort {
10226+
match tx_msg_handler(channel) {
10227+
Ok(msg_send) => {
10228+
let msg_send_event = msg_send.into_msg_send_event(*counterparty_node_id);
10229+
peer_state.pending_msg_events.push(msg_send_event);
10230+
},
10231+
Err((tx_abort, splice_funding_failed)) => {
10232+
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1023010233
node_id: *counterparty_node_id,
1023110234
msg: tx_abort,
10235+
});
10236+
if let Some(splice_funding_failed) = splice_funding_failed {
10237+
let pending_events = &mut self.pending_events.lock().unwrap();
10238+
pending_events.push_back((events::Event::SpliceFailed {
10239+
channel_id,
10240+
counterparty_node_id: *counterparty_node_id,
10241+
user_channel_id: channel.context().get_user_id(),
10242+
funding_txo: splice_funding_failed.funding_txo,
10243+
channel_type: splice_funding_failed.channel_type.clone(),
10244+
contributed_inputs: splice_funding_failed.contributed_inputs,
10245+
contributed_outputs: splice_funding_failed.contributed_outputs,
10246+
}, None));
1023210247
}
1023310248
},
1023410249
};
10235-
peer_state.pending_msg_events.push(msg_send_event);
1023610250
Ok(())
1023710251
},
1023810252
hash_map::Entry::Vacant(_) => {
@@ -10312,11 +10326,23 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1031210326
});
1031310327
}
1031410328
},
10315-
Err(tx_abort) => {
10329+
Err((tx_abort, splice_funding_failed)) => {
1031610330
peer_state.pending_msg_events.push(MessageSendEvent::SendTxAbort {
1031710331
node_id: counterparty_node_id,
1031810332
msg: tx_abort,
1031910333
});
10334+
if let Some(splice_funding_failed) = splice_funding_failed {
10335+
let pending_events = &mut self.pending_events.lock().unwrap();
10336+
pending_events.push_back((events::Event::SpliceFailed {
10337+
channel_id: msg.channel_id,
10338+
counterparty_node_id,
10339+
user_channel_id: chan.context().get_user_id(),
10340+
funding_txo: splice_funding_failed.funding_txo,
10341+
channel_type: splice_funding_failed.channel_type.clone(),
10342+
contributed_inputs: splice_funding_failed.contributed_inputs,
10343+
contributed_outputs: splice_funding_failed.contributed_outputs,
10344+
}, None));
10345+
}
1032010346
},
1032110347
}
1032210348
Ok(())

lightning/src/ln/interactivetxs.rs

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

18771877
pub(super) struct InteractiveTxConstructor {
18781878
state_machine: StateMachine,
1879+
is_initiator: bool,
18791880
initiator_first_message: Option<InteractiveTxMessageSend>,
18801881
channel_id: ChannelId,
18811882
inputs_to_contribute: Vec<(SerialId, InputOwned)>,
@@ -2054,6 +2055,7 @@ impl InteractiveTxConstructor {
20542055

20552056
let mut constructor = Self {
20562057
state_machine,
2058+
is_initiator,
20572059
initiator_first_message: None,
20582060
channel_id,
20592061
inputs_to_contribute,
@@ -2068,6 +2070,10 @@ impl InteractiveTxConstructor {
20682070
Ok(constructor)
20692071
}
20702072

2073+
pub fn is_initiator(&self) -> bool {
2074+
self.is_initiator
2075+
}
2076+
20712077
pub fn take_initiator_first_message(&mut self) -> Option<InteractiveTxMessageSend> {
20722078
self.initiator_first_message.take()
20732079
}

0 commit comments

Comments
 (0)