-
Notifications
You must be signed in to change notification settings - Fork 419
Emit SplicePending
and SpliceFailed
events
#4077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🎉 This PR is now ready for review! |
Looking for high-level feedback on the approach and if all scenarios were covered. Specifically, is |
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0db24f3
to
41c1e60
Compare
lightning/src/ln/channel.rs
Outdated
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) | ||
})?; |
There was a problem hiding this comment.
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.
lightning/src/ln/channel.rs
Outdated
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise here.
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, | ||
} |
There was a problem hiding this comment.
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.
|
||
pub(super) struct InteractiveTxConstructor { | ||
state_machine: StateMachine, | ||
is_initiator: bool, |
There was a problem hiding this comment.
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.
lightning/src/ln/channel.rs
Outdated
contributed_inputs: Vec::new(), | ||
contributed_outputs: Vec::new(), |
There was a problem hiding this comment.
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.
lightning/src/ln/channel.rs
Outdated
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; |
There was a problem hiding this comment.
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.
🔔 1st Reminder Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review. |
1 similar comment
🔔 1st Reminder Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review. |
1 similar comment
🔔 2nd Reminder Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the status of this? IMO we should land it early so we can write tests and discover where we're missing coverage.
lightning/src/ln/channel.rs
Outdated
|
||
/// Information about a splice funding negotiation that has been completed. | ||
/// This is returned from channel operations and converted to an Event::SplicePending in ChannelManager. | ||
pub struct SpliceFundingNegotiated { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meh, just return the Event
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... our pattern has been to only create events in ChannelManager
. Though we can drop the first three fields and take them from the ChannelContext
in ChannelManager
as is done for other events.
lightning/src/ln/channel.rs
Outdated
/// Information about a splice funding negotiation that has been completed. | ||
/// This is returned from channel operations and converted to an Event::SplicePending in ChannelManager. | ||
pub struct SpliceFundingNegotiated { | ||
/// The channel_id of the channel being spliced. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol, these comments are great, they're exactly what I expect from an intern, they say absolutely nothing but meet the not-even-a-requirement for things having docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... I'll take a pass and clean up Claude's work. Didn't really give it much prompting to be honest.
lightning/src/events/mod.rs
Outdated
counterparty_node_id: PublicKey, | ||
/// The outpoint of the channel's splice funding transaction. | ||
funding_txo: OutPoint, | ||
/// The features that this channel will operate with. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should clarify that while it won't currently change this may change with future splices.
/// The outpoint of the channel's splice funding transaction, if one was created. | ||
funding_txo: Option<OutPoint>, | ||
/// The features that this channel will operate with, if available. | ||
channel_type: Option<ChannelTypeFeatures>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine to drop it here is you prefer. Keeping it in SplicePending
since it can change from the original funding.
/// Input outpoints contributed to the splice transaction. | ||
contributed_inputs: Vec<OutPoint>, | ||
/// Outputs contributed to the splice transaction. | ||
contributed_outputs: Vec<TxOut>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I believe we discussed on the call last week, but we should include information about other splices still pending.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the idea was to only include inputs that can be unlocked. I need to update this still.
This depends on #4097 now. |
🔔 3rd Reminder Hey @wpaulino! This PR has been waiting for your review. |
41c1e60
to
a01b467
Compare
let splice_funding_failed = funding_negotiation_opt | ||
.filter(|funding_negotiation| funding_negotiation.is_initiator()) | ||
.map(|funding_negotiation| { | ||
// Create SpliceFundingFailed for the aborted splice | ||
let (funding_txo, channel_type) = match &funding_negotiation { | ||
FundingNegotiation::ConstructingTransaction { funding, .. } => { | ||
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone())) | ||
}, | ||
FundingNegotiation::AwaitingSignatures { funding, .. } => { | ||
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone())) | ||
}, | ||
FundingNegotiation::AwaitingAck { .. } => { | ||
(None, None) | ||
}, | ||
}; | ||
|
||
SpliceFundingFailed { | ||
channel_id: funded_channel.context.channel_id, | ||
counterparty_node_id: funded_channel.context.counterparty_node_id, | ||
user_channel_id: funded_channel.context.user_id, | ||
funding_txo, | ||
channel_type, | ||
contributed_inputs: Vec::new(), | ||
contributed_outputs: Vec::new(), | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: In tx_abort handling for funded channels, when a splice negotiation is aborted during the ConstructingTransaction state, the contributed inputs and outputs are not extracted from the interactive_tx_constructor before creating the SpliceFundingFailed event. The code sets contributed_inputs and contributed_outputs to empty vectors, but if the local node had already contributed inputs/outputs to the transaction construction, this information should be preserved so users know which UTXOs they can reuse. The interactive_tx_constructor contains inputs_to_contribute and outputs_to_contribute fields that should be extracted before the constructor is dropped.
let splice_funding_failed = funding_negotiation_opt | |
.filter(|funding_negotiation| funding_negotiation.is_initiator()) | |
.map(|funding_negotiation| { | |
// Create SpliceFundingFailed for the aborted splice | |
let (funding_txo, channel_type) = match &funding_negotiation { | |
FundingNegotiation::ConstructingTransaction { funding, .. } => { | |
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone())) | |
}, | |
FundingNegotiation::AwaitingSignatures { funding, .. } => { | |
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone())) | |
}, | |
FundingNegotiation::AwaitingAck { .. } => { | |
(None, None) | |
}, | |
}; | |
SpliceFundingFailed { | |
channel_id: funded_channel.context.channel_id, | |
counterparty_node_id: funded_channel.context.counterparty_node_id, | |
user_channel_id: funded_channel.context.user_id, | |
funding_txo, | |
channel_type, | |
contributed_inputs: Vec::new(), | |
contributed_outputs: Vec::new(), | |
} | |
}); | |
let splice_funding_failed = funding_negotiation_opt | |
.filter(|funding_negotiation| funding_negotiation.is_initiator()) | |
.map(|funding_negotiation| { | |
// Create SpliceFundingFailed for the aborted splice | |
let (funding_txo, channel_type, contributed_inputs, contributed_outputs) = match &funding_negotiation { | |
FundingNegotiation::ConstructingTransaction { funding, interactive_tx_constructor, .. } => { | |
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), | |
Some(funding.get_channel_type().clone()), | |
interactive_tx_constructor.inputs_to_contribute.clone(), | |
interactive_tx_constructor.outputs_to_contribute.clone()) | |
}, | |
FundingNegotiation::AwaitingSignatures { funding, .. } => { | |
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), | |
Some(funding.get_channel_type().clone()), | |
Vec::new(), | |
Vec::new()) | |
}, | |
FundingNegotiation::AwaitingAck { .. } => { | |
(None, None, Vec::new(), Vec::new()) | |
}, | |
}; | |
SpliceFundingFailed { | |
channel_id: funded_channel.context.channel_id, | |
counterparty_node_id: funded_channel.context.counterparty_node_id, | |
user_channel_id: funded_channel.context.user_id, | |
funding_txo, | |
channel_type, | |
contributed_inputs, | |
contributed_outputs, | |
} | |
}); |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
I pushed four new commits immediately after the |
Specifically, see the |
a01b467
to
8ff58b9
Compare
An upcoming commit will include the contributed inputs and outputs in an error whenever ConstructedTransaction::new fails. In order to DRY up that logic, this commit updates the constructor to create the resulting object prior to performing any checks. This way a conversion method can be added that extracts the necessary input and output data.
Both NegotiationContext::validate_tx and ConstructedTransaction::new contain validity checks. Move the former into the latter in order to consolidate the checks in a single place. This will also allow for reusing error construction in an upcoming commit.
Instead of popping each input and output to contribute during an interactive tx session, clone the necessary parts and keep around the original inputs and outputs. This will let us reuse them later when constructing an error. The tradeoff is using additional memory to avoid more code complexity required to extract the sent input and outputs from NegotiationContext.
8ff58b9
to
3a08a0e
Compare
Opened #4123 based on some offline discussion, which this PR is now based on. It includes |
Currently, only the shared input index is stored in ConstructedTransaction. This will be used later to filter out the shared input when constructing an error during interactive tx negotiation. Store the shared output index as well so that the shared output can be filtered out as well.
When an interactive tx sessions is aborted, the user will need to be notified with an event indicating which inputs and outputs were contributed. This allows them to re-use inputs that are no longer in use. This commit introduces a NegotiationError type to achieve this, which includes the inputs and outputs along with an AbortReason.
3a08a0e
to
69a4748
Compare
Once a splice has been negotiated and its funding transaction has been broadcast, it is considered pending until both parties have seen enough confirmations to consider the funding locked. Add an event used to indicate this. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Once a splice has been negotiated and its funding transaction has been broadcast, emit a SplicePending event. Once this occurs, the inputs contributed to the splice cannot be reused except by an RBF attempt. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Once a splice has been successfully initiated, but prior to signing any negotiated funding transaction, it may fail. Add an event used to indicate this and which UTXOs can be reused. 🤖 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 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]>
Since quiescence is terminated upon disconnection, any outstanding splice negotiation should result in emitting a SpliceFailed event as long as we haven't reached FundingNegotiation::AwaitingSignatures. This may occur if we explicitly disconnect the peer (e.g., when failing to process splice_ack) or if the connection is lost..
69a4748
to
0f85dd8
Compare
🔔 4th Reminder Hey @wpaulino! This PR has been waiting for your review. |
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, anEvent::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:
Event::SpliceFailed
with inputs and outputs.