Skip to content

Fix initial commitment_signed for splicing #4014

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Aug 15, 2025

When sending or handling the initial commitment_signed message for splicing, the previous commitment point and number must be used. Track this and fix the current behavior which was using the next commitment point and number by mistake. Also, refactor HolderCommitmentPoint as the use of "current" is confusing when prefixing holder_commitment_point fields with next_ and previous_.

Found when reviewing #3886 (comment).

jkczyz added 3 commits August 14, 2025 19:34
The only difference between the two variants is the next point, which
can be stored using an Option for simplicity. The naming of the
Available variant is also confusing as it refers to the next commitment
point. But HolderCommitmentPoint is typically used to represent the next
point, which is actually stored in the current field. Drop the "current"
nomenclature to avoid confusion.
The previous commitment point will be tracked in an upcoming commit.
Rename existing HolderCommitmentPoint fields to avoid ambiguity.
The previous commitment point will be tracked in an upcoming commit.
Rename existing HolderCommitmentPoint fields to avoid ambiguity.
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Aug 15, 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 jkczyz self-assigned this Aug 15, 2025
@jkczyz jkczyz requested review from TheBlueMatt and wpaulino August 15, 2025 00:49
Copy link

codecov bot commented Aug 15, 2025

Codecov Report

❌ Patch coverage is 83.43949% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.85%. Comparing base (3b939c0) to head (96a7870).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 83.33% 17 Missing and 9 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4014      +/-   ##
==========================================
- Coverage   88.91%   88.85%   -0.06%     
==========================================
  Files         174      175       +1     
  Lines      125066   127727    +2661     
  Branches   125066   127727    +2661     
==========================================
+ Hits       111197   113488    +2291     
- Misses      11358    11675     +317     
- Partials     2511     2564      +53     
Flag Coverage Δ
fuzzing 21.85% <48.07%> (-0.24%) ⬇️
tests 88.68% <83.43%> (-0.06%) ⬇️

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.

@@ -6065,7 +6065,10 @@ where
///
/// This field is cleared once our counterparty sends a `channel_ready`.
pub interactive_tx_signing_session: Option<InteractiveTxSigningSession>,
holder_commitment_point: HolderCommitmentPoint,

/// The commitment point used for the next commitment transaction.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The commitment point used for the next commitment transaction.
/// The commitment point used for the next holder commitment transaction.

@@ -6066,6 +6068,9 @@ where
/// This field is cleared once our counterparty sends a `channel_ready`.
pub interactive_tx_signing_session: Option<InteractiveTxSigningSession>,

/// The commitment point used for the previous commitment transaction.
previous_holder_commitment_point: Option<HolderCommitmentPoint>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be current (aka the one most recently negotiated but not revoked) not previous?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yeah, that would be better. May have been just overzealous with ridding us of "current".

@jkczyz jkczyz force-pushed the 2025-08-splice-initial-commitment-signed branch from 57ece8a to cb521b4 Compare August 15, 2025 01:25
jkczyz added 3 commits August 15, 2025 10:19
When splicing a channel, sending and receiving the initial
commitment_signed message should use the current commitment point
rather then the next one. Store this in FundedChannel whenever the
commitment point is advanced and re-derive it using the signer upon
restart.
@jkczyz jkczyz force-pushed the 2025-08-splice-initial-commitment-signed branch from cb521b4 to e2ed042 Compare August 15, 2025 15:30
next_point,
},
(_, _) => {
let point = holder_signer.get_per_commitment_point(next_holder_commitment_transaction_number, &secp_ctx)
.expect("Must be able to derive the current commitment point upon channel restoration");
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 suppose this should be "next"...

&secp_ctx,
)
.expect(
"Must be able to derive the next commitment point upon channel restoration",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which means this is next-next... 😛

Why do we attempt to get this here when next_holder_commitment_point_opt is None but don't bother above when it is Some? In the latter case, next_holder_commitment_point_next_advance_opt may be either.

@@ -6066,6 +6068,9 @@ where
/// This field is cleared once our counterparty sends a `channel_ready`.
pub interactive_tx_signing_session: Option<InteractiveTxSigningSession>,

/// The commitment point used for the previous commitment transaction.
previous_holder_commitment_point: Option<HolderCommitmentPoint>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yeah, that would be better. May have been just overzealous with ridding us of "current".

@jkczyz jkczyz requested a review from wpaulino August 15, 2025 15:32
@@ -14005,6 +14025,7 @@ where
is_holder_quiescence_initiator: None,
},
interactive_tx_signing_session,
previous_holder_commitment_point,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems really weird to store a { transaction_number, point, next_point } in FundedChannel when two of the three fields are redundant with next_holder_commitment_point. Why can't we just store the one extra key in HolderCommitmentPoint?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are places in the code that take a HolderCommitmentPoint, so it wouldn't be clear which point is needed. Currently, next_point is simply used as a means to determine if we can advance. It's never actually accessed outside serialization.

Also, the code wouldn't be very readable as next_holder_commitment_point.previous_point() to get the current point. And if we keep the field named holder_commitment_point, it's "current" is the "next", and it's previous is the "current". The primary motivation if this approach is to avoid confusion.

That said, we can get away with only persisting the new point, even there is some in-memory duplication. We could probably clear the "next" point form current, too, since that shouldn't be needed after advancing the point.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There are places in the code that take a HolderCommitmentPoint, so it wouldn't be clear which point is needed.

Its not really clear which point is needed already? We need two different points depending on if we're validating a commitment_signed for a splice or during normal operation. I don't really see how passing a u64 and a PublicKey makes those places any less readable.

Also, the code wouldn't be very readable as next_holder_commitment_point.previous_point() to get the current point. And if we keep the field named holder_commitment_point, it's "current" is the "next", and it's previous is the "current". The primary motivation if this approach is to avoid confusion.

Seems like one more rename of the method would do it? next and current on a holder_commitment_point would be correct, and as you note we don't even access the after-next, so it would read fine?

next_holder_commitment_point.transaction_number() + 1;
let previous_point = holder_signer
.get_per_commitment_point(previous_holder_commitment_transaction_number, &secp_ctx)
.expect(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can't always rely on this on startup, we need to persist the new point. The logic we had relied on was "you have to do a sync-signer load once, then you can switch to async signing", but now we require you to have a sync signer always on startup, which isn't acceptable.

I'm also kinda not a fan of requiring a sync signer again once after we told people they can do async signing after loading once with a previous version. Given this is only used in splicing is it that much work to make the new key Optional and just fail splices until its filled in (which should happen any time we step the state machine forward)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, that should be simple enough to do. Though, we'd advertise to our peer that channel supports splicing but may fail on the first attempt.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that's fine. "If you're using async signing then splicing may fail until you do a channel update" seems like a rare enough thing that we can just document it and live with it. Not many of our users do async signing anyway.

jkczyz added 4 commits August 15, 2025 14:46
When splicing a channel, the initial commitment_signed received should
use the same commitment number and point previously received prior to
splicing the channel. Account for this by checking the commitment_signed
against that one instead, which is now stored separately in
FundedChannel.
When splicing a channel, the initial commitment_signed should use the
same commitment number and point previously sent. Account for this by
adjusting these to use the previous commitment number and point, since
the next expected one is stored.
@jkczyz jkczyz force-pushed the 2025-08-splice-initial-commitment-signed branch from e2ed042 to 96a7870 Compare August 15, 2025 19:56
@jkczyz jkczyz requested a review from TheBlueMatt August 15, 2025 21:49
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

I'm a bit uncertain about the design for the access to the previous point - #4014 (comment)

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