Skip to content

Commit 19db134

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 dd2e394 commit 19db134

File tree

3 files changed

+213
-60
lines changed

3 files changed

+213
-60
lines changed

lightning/src/ln/channel.rs

Lines changed: 109 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,35 +1680,37 @@ where
16801680

16811681
fn fail_interactive_tx_negotiation<L: Deref>(
16821682
&mut self, reason: AbortReason, 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);
16881688
log_info!(logger, "Failed interactive transaction negotiation: {reason}");
16891689

1690-
match &mut self.phase {
1690+
let splice_funding_failed = match &mut self.phase {
16911691
ChannelPhase::Undefined => unreachable!(),
1692-
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => {},
1692+
ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => None,
16931693
ChannelPhase::UnfundedV2(pending_v2_channel) => {
16941694
pending_v2_channel.interactive_tx_constructor.take();
1695+
None
16951696
},
16961697
ChannelPhase::Funded(funded_channel) => {
16971698
if funded_channel.should_reset_pending_splice_funding_negotiation().unwrap_or(true)
16981699
{
1699-
funded_channel.reset_pending_splice_state();
1700+
funded_channel.reset_pending_splice_state()
17001701
} else {
17011702
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
1703+
None
17021704
}
17031705
},
17041706
};
17051707

1706-
reason.into_tx_abort_msg(self.context().channel_id)
1708+
(reason.into_tx_abort_msg(self.context().channel_id), splice_funding_failed)
17071709
}
17081710

17091711
pub fn tx_add_input<L: Deref>(
17101712
&mut self, msg: &msgs::TxAddInput, logger: &L,
1711-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1713+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17121714
where
17131715
L::Target: Logger,
17141716
{
@@ -1723,7 +1725,7 @@ where
17231725

17241726
pub fn tx_add_output<L: Deref>(
17251727
&mut self, msg: &msgs::TxAddOutput, logger: &L,
1726-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1728+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17271729
where
17281730
L::Target: Logger,
17291731
{
@@ -1740,7 +1742,7 @@ where
17401742

17411743
pub fn tx_remove_input<L: Deref>(
17421744
&mut self, msg: &msgs::TxRemoveInput, logger: &L,
1743-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1745+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17441746
where
17451747
L::Target: Logger,
17461748
{
@@ -1757,7 +1759,7 @@ where
17571759

17581760
pub fn tx_remove_output<L: Deref>(
17591761
&mut self, msg: &msgs::TxRemoveOutput, logger: &L,
1760-
) -> Result<InteractiveTxMessageSend, msgs::TxAbort>
1762+
) -> Result<InteractiveTxMessageSend, (msgs::TxAbort, Option<SpliceFundingFailed>)>
17611763
where
17621764
L::Target: Logger,
17631765
{
@@ -1774,7 +1776,10 @@ where
17741776

17751777
pub fn tx_complete<L: Deref>(
17761778
&mut self, msg: &msgs::TxComplete, logger: &L,
1777-
) -> Result<(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>), msgs::TxAbort>
1779+
) -> Result<
1780+
(Option<InteractiveTxMessageSend>, Option<msgs::CommitmentSigned>),
1781+
(msgs::TxAbort, Option<SpliceFundingFailed>),
1782+
>
17781783
where
17791784
L::Target: Logger,
17801785
{
@@ -1835,7 +1840,13 @@ where
18351840
if should_reset {
18361841
// We may have still tracked the pending funding negotiation state, so we
18371842
// should ack with our own `tx_abort`.
1838-
funded_channel.reset_pending_splice_state()
1843+
let has_funding_negotiation = funded_channel
1844+
.pending_splice
1845+
.as_ref()
1846+
.map(|pending_splice| pending_splice.funding_negotiation.is_some())
1847+
.unwrap_or(false);
1848+
funded_channel.reset_pending_splice_state();
1849+
has_funding_negotiation
18391850
} else {
18401851
return Err(ChannelError::close(
18411852
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
@@ -1935,7 +1946,8 @@ where
19351946
interactive_tx_constructor,
19361947
} = funding_negotiation
19371948
{
1938-
Some((funding, interactive_tx_constructor))
1949+
let is_initiator = interactive_tx_constructor.is_initiator();
1950+
Some((is_initiator, funding, interactive_tx_constructor))
19391951
} else {
19401952
// Replace the taken state for later error handling
19411953
pending_splice.funding_negotiation = Some(funding_negotiation);
@@ -1947,7 +1959,7 @@ where
19471959
"Got a tx_complete message in an invalid state",
19481960
)
19491961
})
1950-
.and_then(|(mut funding, interactive_tx_constructor)| {
1962+
.and_then(|(is_initiator, mut funding, interactive_tx_constructor)| {
19511963
match chan.context.funding_tx_constructed(
19521964
&mut funding,
19531965
funding_outpoint,
@@ -1958,7 +1970,10 @@ where
19581970
Ok(commitment_signed) => {
19591971
// Advance the state
19601972
pending_splice.funding_negotiation =
1961-
Some(FundingNegotiation::AwaitingSignatures { funding });
1973+
Some(FundingNegotiation::AwaitingSignatures {
1974+
is_initiator,
1975+
funding,
1976+
});
19621977
Ok((interactive_tx_constructor, commitment_signed))
19631978
},
19641979
Err(e) => {
@@ -2562,12 +2577,14 @@ enum FundingNegotiation {
25622577
},
25632578
AwaitingSignatures {
25642579
funding: FundingScope,
2580+
is_initiator: bool,
25652581
},
25662582
}
25672583

25682584
impl_writeable_tlv_based_enum_upgradable!(FundingNegotiation,
25692585
(0, AwaitingSignatures) => {
25702586
(1, funding, required),
2587+
(3, is_initiator, required),
25712588
},
25722589
unread_variants: AwaitingAck, ConstructingTransaction
25732590
);
@@ -2577,7 +2594,17 @@ impl FundingNegotiation {
25772594
match self {
25782595
FundingNegotiation::AwaitingAck { .. } => None,
25792596
FundingNegotiation::ConstructingTransaction { funding, .. } => Some(funding),
2580-
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,
25812608
}
25822609
}
25832610
}
@@ -6614,12 +6641,15 @@ impl FundingNegotiationContext {
66146641
}
66156642

66166643
fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
6644+
let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
6645+
NegotiationError { reason, contributed_inputs, contributed_outputs }
6646+
}
6647+
6648+
fn into_contributed_inputs_and_outputs(self) -> (Vec<bitcoin::OutPoint>, Vec<TxOut>) {
66176649
let contributed_inputs =
66186650
self.our_funding_inputs.into_iter().map(|input| input.utxo.outpoint).collect();
6619-
66206651
let contributed_outputs = self.our_funding_outputs;
6621-
6622-
NegotiationError { reason, contributed_inputs, contributed_outputs }
6652+
(contributed_inputs, contributed_outputs)
66236653
}
66246654
}
66256655

@@ -6727,6 +6757,21 @@ pub struct SpliceFundingNegotiated {
67276757
pub channel_type: ChannelTypeFeatures,
67286758
}
67296759

6760+
/// Information about a splice funding negotiation that has failed.
6761+
pub struct SpliceFundingFailed {
6762+
/// The outpoint of the channel's splice funding transaction, if one was created.
6763+
pub funding_txo: Option<bitcoin::OutPoint>,
6764+
6765+
/// The features that this channel will operate with, if available.
6766+
pub channel_type: Option<ChannelTypeFeatures>,
6767+
6768+
/// UTXOs spent as inputs contributed to the splice transaction.
6769+
pub contributed_inputs: Vec<bitcoin::OutPoint>,
6770+
6771+
/// Outputs contributed to the splice transaction.
6772+
pub contributed_outputs: Vec<bitcoin::TxOut>,
6773+
}
6774+
67306775
pub struct SpliceFundingPromotion {
67316776
pub funding_txo: OutPoint,
67326777
pub monitor_update: Option<ChannelMonitorUpdate>,
@@ -6806,19 +6851,58 @@ where
68066851
&& self.pending_funding().is_empty()
68076852
}
68086853

6809-
fn reset_pending_splice_state(&mut self) -> bool {
6854+
fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
68106855
debug_assert!(self.should_reset_pending_splice_funding_negotiation().unwrap_or(true));
68116856
self.context.channel_state.clear_quiescent();
6812-
self.context.interactive_tx_signing_session.take();
6813-
let has_funding_negotiation = self
6857+
6858+
let signing_session = self.context.interactive_tx_signing_session.take();
6859+
let splice_funding_failed = self
68146860
.pending_splice
68156861
.as_mut()
68166862
.and_then(|pending_splice| pending_splice.funding_negotiation.take())
6817-
.is_some();
6863+
.filter(|funding_negotiation| funding_negotiation.is_initiator())
6864+
.map(|funding_negotiation| {
6865+
let funding_txo = funding_negotiation
6866+
.as_funding()
6867+
.and_then(|funding| funding.get_funding_txo())
6868+
.map(|txo| txo.into_bitcoin_outpoint());
6869+
6870+
let channel_type = funding_negotiation
6871+
.as_funding()
6872+
.map(|funding| funding.get_channel_type().clone());
6873+
6874+
let (contributed_inputs, contributed_outputs) = match funding_negotiation {
6875+
FundingNegotiation::AwaitingAck { context } => {
6876+
context.into_contributed_inputs_and_outputs()
6877+
},
6878+
FundingNegotiation::ConstructingTransaction {
6879+
interactive_tx_constructor,
6880+
..
6881+
} => interactive_tx_constructor.into_contributed_inputs_and_outputs(),
6882+
FundingNegotiation::AwaitingSignatures { .. } => match signing_session {
6883+
Some(signing_session) => {
6884+
signing_session.into_contributed_inputs_and_outputs()
6885+
},
6886+
None => {
6887+
debug_assert!(false);
6888+
(Vec::new(), Vec::new())
6889+
},
6890+
},
6891+
};
6892+
6893+
SpliceFundingFailed {
6894+
funding_txo,
6895+
channel_type,
6896+
contributed_inputs,
6897+
contributed_outputs,
6898+
}
6899+
});
6900+
68186901
if self.should_reset_pending_splice_state() {
68196902
self.pending_splice.take();
68206903
}
6821-
has_funding_negotiation
6904+
6905+
splice_funding_failed
68226906
}
68236907

68246908
#[rustfmt::skip]
@@ -8658,7 +8742,7 @@ where
86588742

86598743
if let Some(pending_splice) = self.pending_splice.as_mut() {
86608744
self.context.channel_state.clear_quiescent();
8661-
if let Some(FundingNegotiation::AwaitingSignatures { mut funding }) =
8745+
if let Some(FundingNegotiation::AwaitingSignatures { mut funding, .. }) =
86628746
pending_splice.funding_negotiation.take()
86638747
{
86648748
funding.funding_transaction = Some(funding_tx);
@@ -9585,7 +9669,7 @@ where
95859669
.as_ref()
95869670
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
95879671
.and_then(|funding_negotiation| {
9588-
if let FundingNegotiation::AwaitingSignatures { funding } = &funding_negotiation {
9672+
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
95899673
Some(funding)
95909674
} else {
95919675
None

0 commit comments

Comments
 (0)