Skip to content

Exchange splice_locked messages #3741

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

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Apr 16, 2025

After a splice has been negotiated, each party must send a splice_locked message to the other party once the splice transaction has had an acceptable number of confirmations. Update the logic for processing newly confirmed transactions and updated best block to send splice_locked when appropriate.

TODO: Likewise, handle splice_locked and promote the channel's FundingScope once both splice_locked messages have been exchanged.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Apr 16, 2025

👋 Thanks for assigning @wpaulino 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 jkczyz requested a review from wpaulino April 16, 2025 17:11
@jkczyz
Copy link
Contributor Author

jkczyz commented Apr 16, 2025

The logic around determining if the splice_locked messages have been exchanged is still a work in progress. It may need to be re-considered to work with chained 0-conf splices. See lightning/bolts#1160 (comment).

jkczyz added 5 commits April 18, 2025 17:56
When processing confirmed transactions and updates to the best block,
ChannelManager may be instructed to send a channel_ready message when a
channel's funding transaction has confirmed and has met the required
number of confirmations. A similar action is needed for sending
splice_locked once splice transaction has confirmed with required number
of confirmations. Generalize do_chain_event signature to allow for
either scenario.
When processing confirmed transactions, if the funding transaction is
found then information about it in the ChannelContext is updated. In
preparation for splicing, move this data to FundingScope.
When processing confirmed transactions, if the funding transaction is
found then information about it in the ChannelContext is updated. In
preparation for splicing, move this data to FundingScope.
When processing confirmed transactions, if the funding transaction is
found then information about it in the ChannelContext is updated. In
preparation for splicing, move this data to FundingScope.
When processing confirmed transactions, if the funding transaction is
found then information about it in the ChannelContext is updated. In
preparation for splicing, move this data to FundingScope.
@jkczyz jkczyz force-pushed the 2025-04-splice-locked branch from dfbc04e to e4c0566 Compare April 18, 2025 23:15
@jkczyz
Copy link
Contributor Author

jkczyz commented Apr 18, 2025

@wpaulino Ok, this is in better shape for a high-level look. I don't believe it correctly handles unconfirmed splice transactions yet. Also, doesn't yet re-send splice_locked on channel reestablishment, thought that may come in a follow-up.

Copy link

codecov bot commented Apr 18, 2025

Codecov Report

Attention: Patch coverage is 88.63636% with 40 lines in your changes missing coverage. Please review.

Project coverage is 90.15%. Comparing base (7b45811) to head (e4c0566).
Report is 49 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 86.47% 30 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs 96.72% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3741      +/-   ##
==========================================
+ Coverage   89.10%   90.15%   +1.04%     
==========================================
  Files         156      156              
  Lines      123431   132183    +8752     
  Branches   123431   132183    +8752     
==========================================
+ Hits       109985   119164    +9179     
+ Misses      10760    10435     -325     
+ Partials     2686     2584     -102     

☔ 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.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @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.

Copy link
Contributor

@optout21 optout21 left a comment

Choose a reason for hiding this comment

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

LGTM. The preparational changes are very clear. The WIP part also makes sense so far.

@@ -11725,6 +11727,13 @@ where
log_trace!(logger, "Sending channel_ready WITHOUT channel_update for {}", funded_channel.context.channel_id());
}
},
#[cfg(splicing)]
Some(FundingConfirmedMessage::Splice(splice_locked)) => {
pending_msg_events.push(MessageSendEvent::SendSpliceLocked {
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO note: An event will have to be sent here as well, similar to ChannelReady -- ChannelReady with a flag indicating it is for splicing, or a new, splicing-specific event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I take it we want this only after both splice_locked messages have been exchanged? For naming, debating either SpliceLocked or SpliceReady. Something like SpliceFunded also a possibility though it seems odd if it is a splice-out.

Related: Do we want something similar to ChannelPending for when tx_signatures have been exchanged?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want something similar to ChannelPending for when tx_signatures have been exchanged?

I think so, that way we can let the user know their splice was negotiated successfully and is pending confirmation.

@@ -1864,6 +1864,8 @@ impl FundingScope {
#[cfg(splicing)]
struct PendingSplice {
pub our_funding_contribution: i64,
sent_funding_txid: Option<Txid>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this field set? Is it when signatures have been exchanged for the the negotiated funding transaction / it has been broadcast?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe these are specific to splice_locked, so when that is sent/received.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @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.

self.context.minimum_depth = Some(COINBASE_MATURITY);
self.funding.minimum_depth.unwrap_or(0) > 0 &&
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
self.funding.minimum_depth = Some(COINBASE_MATURITY);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we somehow enforce the coinbase maturity without updating minimum_depth? We always keep the funding transaction around now so we should be able to check if it's a coinbase transaction or not. That would prevent us from tracking minimum_depth per scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like for v1 channel establishment, the acceptor doesn't have the funding transaction until they see the funding txo on chain. So we need to set it then. We could only store it if it's the coinbase transaction, but I've set it unconditionally for now.

@@ -1864,6 +1864,8 @@ impl FundingScope {
#[cfg(splicing)]
struct PendingSplice {
pub our_funding_contribution: i64,
sent_funding_txid: Option<Txid>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe these are specific to splice_locked, so when that is sent/received.


let funding_tx_confirmations = height as i64 - funding.funding_tx_confirmation_height as i64 + 1;
if funding_tx_confirmations <= 0 {
funding.funding_tx_confirmation_height = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels weird to have this done here, as opposed to doing so in funding_transaction_unconfirmed or when we see the block at funding_tx_confirmation_height get disconnected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, though note this code was moved from check_get_channel_ready which is used in other places as well.

None
},
_ => {
Some(msgs::SpliceLocked {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't sent_funding_txid get set here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, somehow missed this when moving some commits around.


match pending_splice.sent_funding_txid {
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
debug_assert!(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

We may need to replay splice_locked if a disconnect happened before they were able to process it. Unclear if we'd want to use this same code path for it or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By this do you mean for channel_reestablish?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I assume there is a similar retransmission case we need to handle there, like with channel_ready.

};

let mut confirmed_funding_tx = None;
for &(index_in_block, tx) in txdata.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may want to iterate over the funding scopes for each transaction instead, since we may be processing full block data with thousands of transactions and having to compute the txid of each one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some fixups for this as discussed offline.

jkczyz added 13 commits April 23, 2025 09:32
When checking if channel_ready should be sent, the funding transaction
must reach minimum_depth confirmations. The same logic is needed for
splicing a channel, so refactor it into a helper method.
The minimum_depth of a channel is overridden to COINBASE_MATURITY if the
funding transaction is the coinbase transaction. However, if this is to
be reused for a splice's minimum_depth, it would be a problem since
sending splice_locked would be unnecessarily delayed. Now that
FundingScope contains the funding transaction, use this to check if it
is a coinbase transaction instead of overriding minimum_depth.
@jkczyz jkczyz force-pushed the 2025-04-splice-locked branch from e4c0566 to 49f8ef6 Compare April 29, 2025 19:55
Copy link
Contributor Author

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Addressed most comments other than adding an event and for re-sending splice_locked. See comment replies for open questions.

Also, added code to insert the new scid in short_to_chan_info. Should we remove the old one?


let funding_tx_confirmations = height as i64 - funding.funding_tx_confirmation_height as i64 + 1;
if funding_tx_confirmations <= 0 {
funding.funding_tx_confirmation_height = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, though note this code was moved from check_get_channel_ready which is used in other places as well.

None
},
_ => {
Some(msgs::SpliceLocked {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, somehow missed this when moving some commits around.

self.context.minimum_depth = Some(COINBASE_MATURITY);
self.funding.minimum_depth.unwrap_or(0) > 0 &&
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
self.funding.minimum_depth = Some(COINBASE_MATURITY);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like for v1 channel establishment, the acceptor doesn't have the funding transaction until they see the funding txo on chain. So we need to set it then. We could only store it if it's the coinbase transaction, but I've set it unconditionally for now.

};

let mut confirmed_funding_tx = None;
for &(index_in_block, tx) in txdata.iter() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some fixups for this as discussed offline.


match pending_splice.sent_funding_txid {
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
debug_assert!(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

By this do you mean for channel_reestablish?

@@ -11725,6 +11727,13 @@ where
log_trace!(logger, "Sending channel_ready WITHOUT channel_update for {}", funded_channel.context.channel_id());
}
},
#[cfg(splicing)]
Some(FundingConfirmedMessage::Splice(splice_locked)) => {
pending_msg_events.push(MessageSendEvent::SendSpliceLocked {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I take it we want this only after both splice_locked messages have been exchanged? For naming, debating either SpliceLocked or SpliceReady. Something like SpliceFunded also a possibility though it seems odd if it is a splice-out.

Related: Do we want something similar to ChannelPending for when tx_signatures have been exchanged?

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

Successfully merging this pull request may close these issues.

4 participants