Fix initial commitment_signed for splicing#4014
Fix initial commitment_signed for splicing#4014TheBlueMatt merged 7 commits intolightningdevkit:mainfrom
commitment_signed for splicing#4014Conversation
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.
|
👋 Thanks for assigning @TheBlueMatt as a reviewer! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4014 +/- ##
==========================================
- Coverage 88.91% 88.84% -0.07%
==========================================
Files 174 175 +1
Lines 125066 127775 +2709
Branches 125066 127775 +2709
==========================================
+ Hits 111197 113523 +2326
- Misses 11358 11684 +326
- Partials 2511 2568 +57
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:
|
cb521b4 to
e2ed042
Compare
lightning/src/ln/channel.rs
Outdated
| is_holder_quiescence_initiator: None, | ||
| }, | ||
| interactive_tx_signing_session, | ||
| previous_holder_commitment_point, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Its not really clear which point is needed already? We need two different points depending on if we're validating a
commitment_signedfor a splice or during normal operation. I don't really see how passing au64and aPublicKeymakes those places any less readable.
I suppose it's more about mistakenly passing a point and number that don't correspond to each other.
Seems like one more rename of the method would do it?
nextandcurrenton aholder_commitment_pointwould be correct, and as you note we don't even access the after-next, so it would read fine?
What's the field of third point named? next_next_point?
There was a problem hiding this comment.
I suppose it's more about mistakenly passing a point and number that don't correspond to each other.
Hmm, then maybe its most readable to pass a HolderCommitmentPoint with a enum { Splicing, Normal } as a separate argument?
What's the field of third point named? next_next_point?
Sure? Given its not used very many places that seems fine.
There was a problem hiding this comment.
I suppose it's more about mistakenly passing a point and number that don't correspond to each other.
Hmm, then maybe its most readable to pass a
HolderCommitmentPointwith aenum { Splicing, Normal }as a separate argument?
Looks like it's only one place, which immediately takes each part to pass to another method. So probably ok just to pass the parts.
Sure? Given its not used very many places that seems fine.
Went with pending_next_point instead.
One other thing about using current_point and next_point naming is transaction_number is not for current (which is an Option anyway), so I'll need to rename it to next_transaction_number and have both current_transaction_number and next_transaction_number methods, presumably.
So we're pushing Option into the type. I think I'm fine with this as we keep the advance functionality encapsulated in the type. However, now UnfundedChannelContext::initial_holder_commitment_point is used as initial_holder_commitment_point.next_point(), which is a little confusing. Maybe we drop initial_ from that?
There was a problem hiding this comment.
However, now UnfundedChannelContext::initial_holder_commitment_point is used as initial_holder_commitment_point.next_point(), which is a little confusing. Maybe we drop initial_ from that?
sure, makes sense to me.
There was a problem hiding this comment.
I suppose it's more about mistakenly passing a point and number that don't correspond to each other.
Hmm, then maybe its most readable to pass a
HolderCommitmentPointwith aenum { Splicing, Normal }as a separate argument?Looks like it's only one place, which immediately takes each part to pass to another method. So probably ok just to pass the parts.
Was actually a few places, but I think it's fine as is. We need the current one only for the initial commitment signed for a splice, so checking the FundingScope wouldn't work either.
lightning/src/ln/channel.rs
Outdated
| next_holder_commitment_point.transaction_number() + 1; | ||
| let previous_point = holder_signer | ||
| .get_per_commitment_point(previous_holder_commitment_transaction_number, &secp_ctx) | ||
| .expect( |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
e2ed042 to
96a7870
Compare
TheBlueMatt
left a comment
There was a problem hiding this comment.
I'm a bit uncertain about the design for the access to the previous point - #4014 (comment)
|
🔔 1st Reminder Hey @wpaulino! This PR has been waiting for your review. |
HolderCommitmentPoint::next_point represents the point to use after advancing the point. However, HolderCommitmentPoint::point actually represents the next point we'd expect to receive. This will be renamed in the next commit, so to avoid clashing the existing next_point field needs to be renamed.
HolderCommitmentPoint::point represents the next point we expect to receive. Rename it to next_point to reflect that and similarly rename transaction_number. The next commit will add the current point, which is needed in splicing.
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 HolderCommitmentPoint whenever it is advanced.
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.
96a7870 to
13c81a1
Compare
| if self.holder_commitment_point.current_point().is_none() { | ||
| return Err(APIError::APIMisuseError { | ||
| err: format!( | ||
| "Channel {} cannot be spliced, commitment point needs to be advanced once", |
There was a problem hiding this comment.
Maybe phrase this so it's helpful to the user? Something like "a payment needs to be sent/received first"
| } | ||
|
|
||
| pub fn current_transaction_number(&self) -> u64 { | ||
| self.next_transaction_number + 1 |
There was a problem hiding this comment.
It's a bit weird this could give you an invalid commitment number if the HolderCommitmentPoint hasn't been advanced past INITIAL_COMMITMENT_NUMBER but it seems pre-existing anyway
There was a problem hiding this comment.
Yeah, it's preexisting and I think the only time we see that when get_cur_holder_commitment_transaction_number is called from ChannelManager::read.
TheBlueMatt
left a comment
There was a problem hiding this comment.
There's a few things to fix, but I'm gonna go ahead and land this to get the bulk of the code landed so there isn't rebase and cause it already has an ACK.
| // TODO(splicing): Add check that we are the quiescence acceptor | ||
|
|
||
| if self.holder_commitment_point.current_point().is_none() { | ||
| return Err(ChannelError::Warn(format!( |
There was a problem hiding this comment.
Doesn't this need to WarnAndDisconnect to actually fail the splice?
| match (holder_commitment_point_next_opt, holder_commitment_point_pending_next_opt) { | ||
| (Some(next_point), pending_next_point) => HolderCommitmentPoint { | ||
| next_transaction_number: holder_commitment_next_transaction_number, | ||
| current_point: None, |
There was a problem hiding this comment.
Shouldn't this use the value read from disk?
There was a problem hiding this comment.
Ah, yeah.. I think this was missed when resolving a merge conflict after rebasing to run rustfmt. :(
| ); | ||
| HolderCommitmentPoint { | ||
| next_transaction_number: holder_commitment_next_transaction_number, | ||
| current_point: holder_commitment_point_current_opt, |
There was a problem hiding this comment.
We should try to ask the signer for this too. If the signer is sync, we should support splicing from the first load, only in the async case should we hit the "sorry, need to advance once first" error.
| let commitment_point = self | ||
| .holder_commitment_point | ||
| .current_point() | ||
| .expect("current should be set after receiving the initial commitment_signed"); |
There was a problem hiding this comment.
This is wrong for splicing cause we could have restarted, no? I mean we do check on the init so its probably safe to unwrap, but might as well just return an error rather than panicing, and even if we dont the message is wrong.
There was a problem hiding this comment.
Not sure I understand the point about restarts. The message was more phrased for new channels where once the initial commitment_signed is received, we will have advanced the commitment point and thus current_point will be Some. Otherwise, right, we check when a splice is initiated that current_point is set.
There was a problem hiding this comment.
Right, my point is that on restart with an async signer this won't be filled in and the panic message would be wrong. Its okay cause its checked during splice_init handling, but the "comment" here explaining why the "unwrap" is safe is wrong.
|
Also, I don't think it breaks non-splicing, just doesn't quite fix splicing :). |
Follow-ups in #4023. |
…-follow-ups Follow-ups for #4014
When sending or handling the initial
commitment_signedmessage 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, refactorHolderCommitmentPointas the use of "current" is confusing when prefixingholder_commitment_pointfields withnext_andprevious_.Found when reviewing #3886 (comment).