Skip to content

Commit 761b48a

Browse files
jkczyzclaude
andcommitted
Add FundingContribution to SpliceFailed event
Replace the abandoned_funding_txo and channel_type fields on Event::SpliceFailed with an Option<FundingContribution> from the failed round. Users can feed this back to funding_contributed to retry or use it to inform a fresh attempt via splice_channel. Also makes FundingContribution::feerate() public so users can inspect the feerate when deciding whether to retry or bump. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2f8125b commit 761b48a

6 files changed

Lines changed: 62 additions & 50 deletions

File tree

lightning/src/events/mod.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::blinded_path::payment::{
2525
use crate::chain::transaction;
2626
use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
2727
use crate::ln::channelmanager::{InterceptId, PaymentId};
28+
use crate::ln::funding::FundingContribution;
2829
use crate::ln::msgs;
2930
use crate::ln::onion_utils::LocalHTLCFailureReason;
3031
use crate::ln::outbound_payment::RecipientOnionFields;
@@ -1621,19 +1622,20 @@ pub enum Event {
16211622
/// The witness script that is used to lock the channel's funding output to commitment transactions.
16221623
new_funding_redeem_script: ScriptBuf,
16231624
},
1624-
/// Used to indicate that a splice for the given `channel_id` has failed.
1625+
/// Used to indicate that a splice negotiation round for the given `channel_id` has failed.
16251626
///
1626-
/// This event may be emitted if a splice fails after it has been initiated but prior to signing
1627-
/// any negotiated funding transaction.
1627+
/// Each splice attempt (initial or RBF) resolves to either [`Event::SplicePending`] on
1628+
/// success or this event on failure. Prior successfully negotiated splice transactions are
1629+
/// unaffected.
16281630
///
1629-
/// Any UTXOs contributed to be spent by the funding transaction may be reused and will be
1630-
/// given in `contributed_inputs`.
1631+
/// Any UTXOs contributed to the failed round that are not committed to a prior negotiated
1632+
/// splice transaction will be returned via a preceding [`Event::DiscardFunding`].
16311633
///
16321634
/// # Failure Behavior and Persistence
16331635
/// This event will eventually be replayed after failures-to-handle (i.e., the event handler
16341636
/// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
16351637
SpliceFailed {
1636-
/// The `channel_id` of the channel for which the splice failed.
1638+
/// The `channel_id` of the channel for which the splice negotiation round failed.
16371639
channel_id: ChannelId,
16381640
/// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
16391641
/// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels.
@@ -1643,12 +1645,17 @@ pub enum Event {
16431645
user_channel_id: u128,
16441646
/// The `node_id` of the channel counterparty.
16451647
counterparty_node_id: PublicKey,
1646-
/// The outpoint of the channel's splice funding transaction, if one was created.
1647-
abandoned_funding_txo: Option<OutPoint>,
1648-
/// The features that this channel will operate with, if available.
1649-
channel_type: Option<ChannelTypeFeatures>,
16501648
/// The reason the splice negotiation failed.
16511649
reason: NegotiationFailureReason,
1650+
/// The funding contribution from the failed negotiation round, if available. This can be
1651+
/// fed back to [`ChannelManager::funding_contributed`] to retry with the same parameters.
1652+
/// Alternatively, call [`ChannelManager::splice_channel`] to obtain a fresh
1653+
/// [`FundingTemplate`] and build a new contribution.
1654+
///
1655+
/// [`ChannelManager::funding_contributed`]: crate::ln::channelmanager::ChannelManager::funding_contributed
1656+
/// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel
1657+
/// [`FundingTemplate`]: crate::ln::channelmanager::FundingTemplate
1658+
contribution: Option<FundingContribution>,
16521659
},
16531660
/// Used to indicate to the user that they can abandon the funding transaction and recycle the
16541661
/// inputs for another purpose.
@@ -2440,18 +2447,16 @@ impl Writeable for Event {
24402447
ref channel_id,
24412448
ref user_channel_id,
24422449
ref counterparty_node_id,
2443-
ref abandoned_funding_txo,
2444-
ref channel_type,
24452450
ref reason,
2451+
ref contribution,
24462452
} => {
24472453
52u8.write(writer)?;
24482454
write_tlv_fields!(writer, {
24492455
(1, channel_id, required),
2450-
(3, channel_type, option),
24512456
(5, user_channel_id, required),
24522457
(7, counterparty_node_id, required),
2453-
(9, abandoned_funding_txo, option),
24542458
(11, reason, required),
2459+
(13, contribution, option),
24552460
});
24562461
},
24572462
// Note that, going forward, all new events must only write data inside of
@@ -3092,20 +3097,18 @@ impl MaybeReadable for Event {
30923097
let mut f = || {
30933098
_init_and_read_len_prefixed_tlv_fields!(reader, {
30943099
(1, channel_id, required),
3095-
(3, channel_type, option),
30963100
(5, user_channel_id, required),
30973101
(7, counterparty_node_id, required),
3098-
(9, abandoned_funding_txo, option),
30993102
(11, reason, (default_value, NegotiationFailureReason::Unknown)),
3103+
(13, contribution, option),
31003104
});
31013105

31023106
Ok(Some(Event::SpliceFailed {
31033107
channel_id: channel_id.0.unwrap(),
31043108
user_channel_id: user_channel_id.0.unwrap(),
31053109
counterparty_node_id: counterparty_node_id.0.unwrap(),
3106-
abandoned_funding_txo,
3107-
channel_type,
31083110
reason: reason.0.unwrap(),
3111+
contribution,
31093112
}))
31103113
};
31113114
f()

lightning/src/ln/channel.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7040,6 +7040,9 @@ pub struct SpliceFundingFailed {
70407040

70417041
/// Outputs contributed to the splice transaction.
70427042
pub contributed_outputs: Vec<bitcoin::TxOut>,
7043+
7044+
/// The funding contribution from the failed round, if available.
7045+
pub contribution: Option<FundingContribution>,
70437046
}
70447047

70457048
macro_rules! maybe_create_splice_funding_failed {
@@ -7088,11 +7091,15 @@ macro_rules! maybe_create_splice_funding_failed {
70887091
return None;
70897092
}
70907093

7094+
let contribution =
7095+
$pending_splice_ref.and_then(|ps| ps.contributions.last().cloned());
7096+
70917097
Some(SpliceFundingFailed {
70927098
funding_txo,
70937099
channel_type,
70947100
contributed_inputs,
70957101
contributed_outputs,
7102+
contribution,
70967103
})
70977104
})
70987105
}};
@@ -7124,6 +7131,7 @@ where
71247131
/// Builds a [`SpliceFundingFailed`] from a contribution, filtering out inputs/outputs
71257132
/// that are still committed to a prior splice round.
71267133
fn splice_funding_failed_for(&self, contribution: FundingContribution) -> SpliceFundingFailed {
7134+
let cloned_contribution = contribution.clone();
71277135
let (mut inputs, mut outputs) = contribution.into_contributed_inputs_and_outputs();
71287136
if let Some(ref pending_splice) = self.pending_splice {
71297137
for input in pending_splice.contributed_inputs() {
@@ -7138,6 +7146,7 @@ where
71387146
channel_type: None,
71397147
contributed_inputs: inputs,
71407148
contributed_outputs: outputs,
7149+
contribution: Some(cloned_contribution),
71417150
}
71427151
}
71437152

lightning/src/ln/channelmanager.rs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4113,8 +4113,7 @@ impl<
41134113
channel_id: *chan_id,
41144114
counterparty_node_id: *counterparty_node_id,
41154115
user_channel_id: chan.context().get_user_id(),
4116-
abandoned_funding_txo: splice_funding_failed.funding_txo,
4117-
channel_type: splice_funding_failed.channel_type,
4116+
contribution: splice_funding_failed.contribution,
41184117
reason: events::NegotiationFailureReason::ChannelClosing,
41194118
},
41204119
None,
@@ -4420,8 +4419,7 @@ impl<
44204419
channel_id: shutdown_res.channel_id,
44214420
counterparty_node_id: shutdown_res.counterparty_node_id,
44224421
user_channel_id: shutdown_res.user_channel_id,
4423-
abandoned_funding_txo: splice_funding_failed.funding_txo,
4424-
channel_type: splice_funding_failed.channel_type,
4422+
contribution: splice_funding_failed.contribution,
44254423
reason: events::NegotiationFailureReason::ChannelClosing,
44264424
},
44274425
None,
@@ -4927,8 +4925,7 @@ impl<
49274925
channel_id: *channel_id,
49284926
counterparty_node_id: *counterparty_node_id,
49294927
user_channel_id: chan.context.get_user_id(),
4930-
abandoned_funding_txo: splice_funding_failed.funding_txo,
4931-
channel_type: splice_funding_failed.channel_type,
4928+
contribution: splice_funding_failed.contribution,
49324929
reason: events::NegotiationFailureReason::LocallyAbandoned,
49334930
},
49344931
None,
@@ -6614,10 +6611,7 @@ impl<
66146611
},
66156612
QuiescentError::FailSplice(
66166613
SpliceFundingFailed {
6617-
funding_txo,
6618-
channel_type,
6619-
contributed_inputs,
6620-
contributed_outputs,
6614+
contributed_inputs, contributed_outputs, contribution, ..
66216615
},
66226616
reason,
66236617
) => {
@@ -6627,9 +6621,8 @@ impl<
66276621
channel_id,
66286622
counterparty_node_id,
66296623
user_channel_id,
6630-
abandoned_funding_txo: funding_txo,
6631-
channel_type,
66326624
reason,
6625+
contribution,
66336626
},
66346627
None,
66356628
));
@@ -11863,8 +11856,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1186311856
channel_id,
1186411857
counterparty_node_id: *counterparty_node_id,
1186511858
user_channel_id: channel.context().get_user_id(),
11866-
abandoned_funding_txo: splice_funding_failed.funding_txo,
11867-
channel_type: splice_funding_failed.channel_type.clone(),
11859+
contribution: splice_funding_failed.contribution.clone(),
1186811860
reason: events::NegotiationFailureReason::NegotiationError,
1186911861
},
1187011862
None,
@@ -12023,8 +12015,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1202312015
channel_id: msg.channel_id,
1202412016
counterparty_node_id,
1202512017
user_channel_id: chan.context().get_user_id(),
12026-
abandoned_funding_txo: splice_funding_failed.funding_txo,
12027-
channel_type: splice_funding_failed.channel_type.clone(),
12018+
contribution: splice_funding_failed.contribution.clone(),
1202812019
reason: events::NegotiationFailureReason::NegotiationError,
1202912020
},
1203012021
None,
@@ -12194,8 +12185,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1219412185
channel_id: msg.channel_id,
1219512186
counterparty_node_id: *counterparty_node_id,
1219612187
user_channel_id: chan_entry.get().context().get_user_id(),
12197-
abandoned_funding_txo: splice_funding_failed.funding_txo,
12198-
channel_type: splice_funding_failed.channel_type,
12188+
contribution: splice_funding_failed.contribution,
1219912189
reason: events::NegotiationFailureReason::CounterpartyAborted,
1220012190
},
1220112191
None,
@@ -12343,8 +12333,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1234312333
channel_id: msg.channel_id,
1234412334
counterparty_node_id: *counterparty_node_id,
1234512335
user_channel_id: chan.context().get_user_id(),
12346-
abandoned_funding_txo: splice_funding_failed.funding_txo,
12347-
channel_type: splice_funding_failed.channel_type,
12336+
contribution: splice_funding_failed.contribution,
1234812337
reason: events::NegotiationFailureReason::ChannelClosing,
1234912338
},
1235012339
None,
@@ -15460,8 +15449,7 @@ impl<
1546015449
channel_id: chan.context().channel_id(),
1546115450
counterparty_node_id,
1546215451
user_channel_id: chan.context().get_user_id(),
15463-
abandoned_funding_txo: splice_funding_failed.funding_txo,
15464-
channel_type: splice_funding_failed.channel_type,
15452+
contribution: splice_funding_failed.contribution,
1546515453
reason: events::NegotiationFailureReason::PeerDisconnected,
1546615454
});
1546715455
splice_failed_events.push(events::Event::DiscardFunding {
@@ -18133,9 +18121,8 @@ impl<
1813318121
channel_id: chan.context.channel_id(),
1813418122
counterparty_node_id: chan.context.get_counterparty_node_id(),
1813518123
user_channel_id: chan.context.get_user_id(),
18136-
abandoned_funding_txo: splice_funding_failed.funding_txo,
18137-
channel_type: splice_funding_failed.channel_type,
1813818124
reason: events::NegotiationFailureReason::PeerDisconnected,
18125+
contribution: splice_funding_failed.contribution,
1813918126
},
1814018127
None,
1814118128
));

lightning/src/ln/functional_test_utils.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3248,9 +3248,10 @@ pub fn expect_splice_failed_events<'a, 'b, 'c, 'd>(
32483248
let events = node.node.get_and_clear_pending_events();
32493249
assert_eq!(events.len(), 2);
32503250
match &events[0] {
3251-
Event::SpliceFailed { channel_id, reason, .. } => {
3251+
Event::SpliceFailed { channel_id, reason, contribution, .. } => {
32523252
assert_eq!(*expected_channel_id, *channel_id);
32533253
assert_eq!(*reason, expected_reason);
3254+
assert_eq!(contribution.as_ref(), Some(&funding_contribution));
32543255
},
32553256
_ => panic!("Unexpected event"),
32563257
}

lightning/src/ln/funding.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,8 @@ impl_writeable_tlv_based!(FundingContribution, {
710710
});
711711

712712
impl FundingContribution {
713-
pub(super) fn feerate(&self) -> FeeRate {
713+
/// Returns the feerate of this contribution.
714+
pub fn feerate(&self) -> FeeRate {
714715
self.feerate
715716
}
716717

@@ -731,6 +732,11 @@ impl FundingContribution {
731732
self.value_added
732733
}
733734

735+
/// Returns the inputs included in this contribution.
736+
pub fn inputs(&self) -> &[FundingTxInput] {
737+
&self.inputs
738+
}
739+
734740
/// Returns the outputs (e.g., withdrawal destinations) included in this contribution.
735741
///
736742
/// This does not include the change output; see [`FundingContribution::change_output`].

lightning/src/ln/splicing_tests.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,9 +3059,10 @@ fn do_abandon_splice_quiescent_action_on_shutdown(local_shutdown: bool, pending_
30593059
let events = nodes[0].node.get_and_clear_pending_events();
30603060
assert_eq!(events.len(), 2, "{events:?}");
30613061
match &events[0] {
3062-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
3062+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
30633063
assert_eq!(*cid, channel_id);
30643064
assert_eq!(*reason, NegotiationFailureReason::ChannelClosing);
3065+
assert!(contribution.is_some());
30653066
},
30663067
other => panic!("Expected SpliceFailed, got {:?}", other),
30673068
}
@@ -4457,9 +4458,10 @@ fn test_splice_acceptor_disconnect_emits_events() {
44574458
let events = nodes[1].node.get_and_clear_pending_events();
44584459
assert_eq!(events.len(), 2, "{events:?}");
44594460
match &events[0] {
4460-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
4461+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
44614462
assert_eq!(*cid, channel_id);
44624463
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
4464+
assert!(contribution.is_some());
44634465
},
44644466
other => panic!("Expected SpliceFailed, got {:?}", other),
44654467
}
@@ -5912,9 +5914,10 @@ fn test_splice_rbf_acceptor_contributes_then_disconnects() {
59125914
let events = nodes[0].node.get_and_clear_pending_events();
59135915
assert_eq!(events.len(), 2, "{events:?}");
59145916
match &events[0] {
5915-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
5917+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
59165918
assert_eq!(*cid, channel_id);
59175919
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
5920+
assert!(contribution.is_some());
59185921
},
59195922
other => panic!("Expected SpliceFailed, got {:?}", other),
59205923
}
@@ -5994,9 +5997,10 @@ fn test_splice_rbf_disconnect_filters_prior_contributions() {
59945997
let events = nodes[0].node.get_and_clear_pending_events();
59955998
assert_eq!(events.len(), 2, "{events:?}");
59965999
match &events[0] {
5997-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
6000+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
59986001
assert_eq!(*cid, channel_id);
59996002
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
6003+
assert!(contribution.is_some());
60006004
},
60016005
other => panic!("Expected SpliceFailed, got {:?}", other),
60026006
}
@@ -6036,9 +6040,10 @@ fn test_splice_rbf_disconnect_filters_prior_contributions() {
60366040
let events = nodes[0].node.get_and_clear_pending_events();
60376041
assert_eq!(events.len(), 2, "{events:?}");
60386042
match &events[0] {
6039-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
6043+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
60406044
assert_eq!(*cid, channel_id);
60416045
assert_eq!(*reason, NegotiationFailureReason::PeerDisconnected);
6046+
assert!(contribution.is_some());
60426047
},
60436048
other => panic!("Expected SpliceFailed, got {:?}", other),
60446049
}
@@ -6726,9 +6731,10 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
67266731
let events = nodes[0].node.get_and_clear_pending_events();
67276732
assert_eq!(events.len(), 1, "{events:?}");
67286733
match &events[0] {
6729-
Event::SpliceFailed { channel_id: cid, reason, .. } => {
6734+
Event::SpliceFailed { channel_id: cid, reason, contribution, .. } => {
67306735
assert_eq!(*cid, channel_id);
67316736
assert_eq!(*reason, NegotiationFailureReason::FeeRateTooLow);
6737+
assert!(contribution.is_some());
67326738
},
67336739
other => panic!("Expected SpliceFailed, got {:?}", other),
67346740
}

0 commit comments

Comments
 (0)