Skip to content

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Sep 17, 2025

When a splice has been negotiated, it remains pending until the funding transaction has been confirmed and locked by both sides. Emit an Event::SplicePending when it reaches this state. At this point, the inputs used for the splice cannot be reused except for an RBF attempt. Once the splice is locked, an Event::DiscardFunding will be emitted for any unsuccessful candidates.

Similarly, a splice may fail before a splice has finished negotiation for various reasons. Emit an Event::SpliceFailed in these cases so the user may reuse the inputs.

The commits were generated by Claude Code, so leaving the PR in draft for now.

TODO:

  • Populate Event::SpliceFailed with inputs and outputs.

Applications need to be notified when splice transactions are pending
confirmation to properly track channel state transitions and update
their UIs accordingly. This event allows applications to distinguish
between initial channel funding (ChannelPending) and splice operations.

The SplicePending event contains the essential channel information
needed by applications: channel ID, user channel ID, counterparty node
ID, funding transaction outpoint, and channel type features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 17, 2025

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 17, 2025

Looking for high-level feedback on the approach and if all scenarios were covered. Specifically, is SpliceFailed sufficient to let a user know if an input can be reused? Seems for an RBF attempt they could have used an input that was used by a previous attempt that has had a SplicePending emitted. So it could be reused for another RBF attempt, IIUC, but not for splicing another channel, for instance.

Copy link

codecov bot commented Sep 17, 2025

Codecov Report

❌ Patch coverage is 10.46512% with 308 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.68%. Comparing base (3564646) to head (0db24f3).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 12.50% 150 Missing and 4 partials ⚠️
lightning/src/ln/channelmanager.rs 11.02% 107 Missing and 6 partials ⚠️
lightning/src/events/mod.rs 0.00% 41 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4077      +/-   ##
==========================================
- Coverage   87.81%   87.68%   -0.13%     
==========================================
  Files         176      176              
  Lines      131770   132034     +264     
  Branches   131770   132034     +264     
==========================================
+ Hits       115719   115780      +61     
- Misses      13416    13615     +199     
- Partials     2635     2639       +4     
Flag Coverage Δ
fuzzing 21.52% <2.61%> (-0.08%) ⬇️
tests 87.52% <10.46%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jkczyz and others added 14 commits September 17, 2025 15:14
Add Event::SplicePending emission whenever a FundingScope is pushed
into PendingSplice::negotiated_candidates. This allows applications
to be notified when a splice transaction has been constructed and
signed but is not yet confirmed on-chain.

Creates SpliceFundingNegotiated struct to carry event data from
channel operations to ChannelManager, where it's converted to the
appropriate Event. Updates function signatures in the channel layer
to return this information and modifies all callers in ChannelManager
to handle the new return values and emit the events.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Introduce SpliceFailed event to notify applications when splice
transaction construction or negotiation fails. This allows applications
to handle failures gracefully and take corrective action or retry.

The event includes similar fields to SplicePending but with optional
funding_txo and channel_type fields since they may not be available
when failures occur early in the process. Additionally includes
contributed_inputs and contributed_outputs vectors to provide
information about what inputs and outputs were attempted.

Uses TLV ID 52 for serialization to maintain proper event ordering
and avoid conflicts with other event types.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
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]>
When splice_ack validation or interactive transaction construction fails,
emit Event::SpliceFailed to notify users of the failure.

This reuses the SpliceFundingFailed struct introduced for interactive
transaction failures, providing consistent failure reporting across all
splice operations. The implementation handles two failure scenarios:

- Early validation failure: Sets funding_txo and channel_type to None
  since no splice funding was established
- Late construction failure: Includes actual funding information since
  validation passed but transaction construction failed

The SpliceFundingFailed struct provides placeholders for contributed
inputs/outputs that can be populated when access to interactive
transaction constructor internals becomes available.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
When a tx_abort message is successfully processed for a funded channel
with an active splice negotiation, emit Event::SpliceFailed to notify
users that the splice operation was aborted by the counterparty.

This extends the SpliceFailed event coverage to handle abort scenarios,
providing comprehensive splice failure notifications across all stages:

- AwaitingAck: funding_txo and channel_type are None since funding
  parameters were not yet established
- ConstructingTransaction/AwaitingSignatures: Include actual funding
  information since negotiation had progressed to funding establishment

The implementation captures splice context before taking the funding
negotiation state, ensuring accurate failure information is available
for event emission while maintaining proper tx_abort acknowledgment
behavior per the Lightning specification.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
…lice negotiations

Adds SpliceFailed event emission immediately after ChannelClosed events when a
FundedChannel is shut down while having an active splice negotiation. This ensures
users are notified when splice operations are terminated due to channel closure.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
…cence

Users need to be notified when splice operations fail at any stage. Previously,
splice failures during the quiescence initialization phase were not reported,
leaving users unaware of failed splice attempts.

Now emits Event::SpliceFailed when send_splice_init fails during stfu processing
of QuiescentAction::Splice, ensuring consistent splice failure reporting across
all phases of the splice lifecycle.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Users need to be notified when splice operations are cancelled due to channel
closure, regardless of the splice phase. Previously, splice operations that
were scheduled but not yet in active negotiation were silently lost during
channel shutdown, leaving users unaware of the failed splice attempt.

Now emits Event::SpliceFailed when a channel shuts down with a pending
QuiescentAction::Splice, ensuring comprehensive splice failure reporting
across all phases of the splice lifecycle.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 0db24f3 to 41c1e60 Compare September 18, 2025 02:03
Comment on lines -11890 to +11901
let splice_funding = self.validate_splice_ack(msg)?;
let splice_funding = self.validate_splice_ack(msg).map_err(|err| {
let splice_failed = SpliceFundingFailed {
channel_id: self.context.channel_id,
counterparty_node_id: self.context.counterparty_node_id,
user_channel_id: self.context.user_id,
funding_txo: None,
channel_type: None,
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
};
(err, splice_failed)
})?;
Copy link
Contributor Author

@jkczyz jkczyz Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit will likely need to be re-worked to have validate_splice_ack return Option<SpliceFundingFailed> as part of its error. But it seems we never clear pending_splice.funding_negotiation here.

@wpaulino Should we? I'd imagine if we ever emit Event::SpliceFailed then pending_splice.funding_negotiation should no longer be set.

Comment on lines -11920 to +11944
ChannelError::WarnAndDisconnect(format!(
let splice_failed = SpliceFundingFailed {
channel_id: self.context.channel_id,
counterparty_node_id: self.context.counterparty_node_id,
user_channel_id: self.context.user_id,
funding_txo: splice_funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()),
channel_type: Some(splice_funding.get_channel_type().clone()),
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
};
let channel_error = ChannelError::WarnAndDisconnect(format!(
"Failed to start interactive transaction construction, {:?}",
err
))
));
(channel_error, splice_failed)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here.

Comment on lines +2632 to 2639
fn is_initiator(&self) -> bool {
match self {
FundingNegotiation::AwaitingAck { context } => context.is_initiator,
FundingNegotiation::ConstructingTransaction { interactive_tx_constructor, .. } => {
interactive_tx_constructor.is_initiator()
},
FundingNegotiation::AwaitingSignatures { is_initiator, .. } => *is_initiator,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there is a preferred way to go about this. It's nice being able to filter like is done in this commit and later fixups:

.filter(|funding_negotiation| funding_negotiation.is_initiator())

But it requires tracking is_initiator both in InteractiveTxConstructor and FundingNegotiation::AwaitingSignatures, at least as currently written.

@@ -1848,6 +1848,7 @@ impl InteractiveTxInput {

pub(super) struct InteractiveTxConstructor {
state_machine: StateMachine,
is_initiator: bool,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be able to get this from the StateMachine, but not if it transitioned to NegotiationAborted, it seems.

Comment on lines +1777 to +1778
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue with populating these is if we are in FundingNegotiation::AwaitingSignatures, then we'd need to get them from ChannelContext::interactive_tx_signing_session, which may not have been set yet in FundedChannel::funding_tx_constructed depending on the error.

Comment on lines 2004 to 2018
let signing_session = interactive_tx_constructor.into_signing_session();
let commitment_signed = chan.context.funding_tx_constructed(
let commitment_signed_result = chan.context.funding_tx_constructed(
&mut funding,
signing_session,
true,
chan.holder_commitment_point.next_transaction_number(),
&&logger,
)?;
);

// This must be set even if returning an Err. Otherwise,
// fail_interactive_tx_negotiation won't produce a SpliceFailed event.
pending_splice.funding_negotiation =
Some(FundingNegotiation::AwaitingSignatures { funding });

return Ok(commitment_signed);
return commitment_signed_result;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, specifically, we may not have set ChannelContext::interactive_tx_signing_session for some errors.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

3 participants