Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Handle fallible per commitment point in channel establishment - #3109

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding
Dec 15, 2024
Merged

Handle fallible per commitment point in channel establishment#3109
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
alecchendev:2024-06-async-commit-point-funding

Conversation

@alecchendev

@alecchendevalecchendev commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Handles fallible get_per_commitment_point signer method (except for channel reestablish).

We make get_per_commitment_point return a result type so that in the Err case, we (LDK) can resume operation while an async signer fetches the commitment point. This will typically block sending out a message, but otherwise most state updates will occur. When the signature arrives, the user is expected to call ChannelManager::signer_unblocked and we will retry get_per_commitment_point however this time the signer will return the commitment point with Ok, and we'll send out our message.

This PR implements this flow for channel establishment, i.e. open_channel, accept_channel, and channel_ready.

Rough outline of how our HolderCommitmentPoint advances and gets new signatures:

  • sending open_channel - creates new outbound channel, immediately requests the first commit point, waits to have the first 2 commit points to be unblocked to send the message
  • sending accept_channel - same ^
  • sending funding_created - no op regarding commit points
  • receiving funding_created - uses point to verify first commitment, then advances commitment, requesting next point
  • sending funding_signed - no op
  • receiving funding_signed - same as above, verifies, advances, requests next point
  • sending channel_ready - waits to have the next commit point ready, then once unblocked sends the current point in the channel_ready message

@codecov-commenter

codecov-commenter commented Jun 10, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.42857% with 58 lines in your changes missing coverage. Please review.

Project coverage is 89.70%. Comparing base (1a8bf62) to head (d1e94bd).
Report is 8 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/channel.rs84.33%39 Missing and 8 partials ⚠️
lightning/src/ln/channelmanager.rs88.09%1 Missing and 4 partials ⚠️
lightning/src/ln/functional_test_utils.rs0.00%3 Missing ⚠️
lightning/src/util/test_utils.rs40.00%0 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3109 +/- ##
==========================================
- Coverage 89.74% 89.70% -0.05% 
==========================================
Files 130 130 Lines 107793 107882 +89 Branches 107793 107882 +89 ==========================================
+ Hits 96743 96778 +35 - Misses 8651 8695 +44 - Partials 2399 2409 +10 

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

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 1e5b210 to 07991faCompareJune 10, 2024 01:16
@alecchendev

alecchendev commented Jun 11, 2024

Copy link
Copy Markdown
ContributorAuthor

the commits from #3115 are second from the end, will rebase on top later this week, but generally most of the stuff here is in place

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 07991fa to 744f2caCompareJune 11, 2024 01:22
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 3 times, most recently from 73583e7 to b798ffaCompareJune 18, 2024 22:24
@alecchendev
alecchendev marked this pull request as ready for review June 18, 2024 22:27
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 0bdd4cc to 0ac4ad8CompareJuly 3, 2024 20:42
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

When we get back to this, please address the comments from #3152 (review)

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from 7ea6d3f to c109e18CompareJuly 20, 2024 00:00
Comment threadlightning/src/ln/channel.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This also needs rebase now.

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch 2 times, most recently from df8902b to 50b1d88CompareSeptember 16, 2024 23:44
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Will probably squash some of these commits together at some point but just kept them separate for now

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

rebased and force pushed with the new approach. Previous branch is still here if it's helpful to see

Comment on lines 9043 to +9620
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
let next_holder_commitment_point = self.context.holder_commitment_point.next_point();
let cur_holder_commitment_point = Some(self.holder_commitment_point.current_point());
let next_holder_commitment_point = self.holder_commitment_point.next_point();

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when reading, now that this will never be optional, can we unwrap/expect the value (or return a DecodeError on None)? what's the right way to handle this

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 50b1d88 to 760b055CompareSeptember 17, 2024 00:15

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, this is looking great. A few nits here and there but basically this LGTM.

Comment threadlightning/src/ln/channel.rs
} else {
log_debug!(logger, "Not producing channel_ready: the holder commitment point is not available.");
self.context.signer_pending_channel_ready = true;
None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think after this we are missing an update to get_announcement_sigs to replay it (can come in a followup, but right now if you do an async signer it will always be forced to be a non-public channel).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

sounds good, will do in a followup

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/async_signer_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 8f49a7d to 6041e37CompareDecember 6, 2024 00:00

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Going to give this another look next week but it looks good!

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8683 to +8687
fn generate_accept_channel_message<L: Deref>(&mut self, _logger: &L) -> Option<msgs::AcceptChannel>
where L::Target: Logger
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you format these method signatures per rustfmt, here and elsewhere in the PR:

Suggested change
fngenerate_accept_channel_message<L:Deref>(&mutself,_logger:&L) -> Option<msgs::AcceptChannel>
where L::Target:Logger
{
fn generate_accept_channel_message<L:Deref>(
&mutself, _logger:&L
) -> Option<msgs::AcceptChannel> where L::Target:Logger{

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Did a full scan of the function signatures changed in this PR and formatted them accordingly, lmk if i missed any

Comment threadlightning/src/ln/async_signer_tests.rs Outdated
}
if let Some(ref mut point) = self.unfunded_context.holder_commitment_point {
if !point.is_available() {
point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);

@valentinewallacevalentinewallaceDec 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah would it be easy to add test coverage for this case?

Edit: looking at this again, this looks potentially redundant with HolderCommitmentPoint::new, which already tries to fetch the next point?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This case is for if they already got the first commitment point, but not the next. HolderCommitmentPoint::new will try to fetch both points, but if one is ready before the other, I think we still need this check.

Unfortunately, I don't think it's easy to add coverage for this. Right now our test utils for disabling signer ops mainly just disable all calls for a certain signing method on a signer, whereas for this we'd need to disable it for the second call and not the first. I have an idea for how to do this, i.e. we could hold a queue of if the next signer calls should be disabled/enabled, but my first impression is it'll be a little complicated just to get this case. Open to suggestions + I'm happy to give it a try it if you want, but yea

Comment threadlightning/src/ln/channel.rs
Comment on lines +8702 to +8797
let mut holder_commitment_point = match self.unfunded_context.holder_commitment_point {
Some(point) => point,
None => return Err((self, ChannelError::close("Received funding_created before our first commitment point was available".to_owned()))),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In funding_signed we'll expect that holder_commitment_point is Some, but here we'll error and close the channel. Is there reason for the difference in handling there?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Unintentional, just changed funding_signed to return an error!

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
};

let chan = Self { context, unfunded_context };
let chan = Self { context, unfunded_context, signer_pending_open_channel: false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not obvious to me why this is set to false, seems like whether it's set should depend on the result of HolderCommitmentPoint::new above?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah yea, we just initialize to false here, and leave the logic for setting the flag for where we actually try to generate the open_channel message. Added a comment, lmk if that works

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +8542 to +8548
let open_channel = match self.unfunded_context.holder_commitment_point {
Some(ref mut point) if point.is_available() && self.signer_pending_open_channel => {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
}
_ => None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be simplified due to the existing checks in get_open_channel?

 let open_channel = if self.signer_pending_open_channel {
log_trace!(logger, "Attempting to generate open_channel...");
self.get_open_channel(chain_hash, logger)
} else { None };

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We check here point.is_available and don't in get_open_channel. But it does seem simpler to just do that check in get_open_channel (and be consistent in both places) so I'll just move it there and simplify this. Same with accept channel.

Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/ln/channel.rs
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 6041e37 to 20642a6CompareDecember 10, 2024 00:06
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

pushed some, still addressing more comments

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 20642a6 to ceae1c4CompareDecember 11, 2024 21:24
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

almost there...

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from ceae1c4 to 4aae0ffCompareDecember 11, 2024 22:01
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Okay, I think I've addressed all the comments up to now. Lmk when/if I'm good to squash!

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all the updates here! LGTM, feel free to squash IMO

@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from 4aae0ff to be67d99CompareDecember 13, 2024 20:35
@alecchendev

Copy link
Copy Markdown
ContributorAuthor

Squashed

@valentinewallace

Copy link
Copy Markdown
Contributor

Looks like CI is sad :(

@alecchendev

Copy link
Copy Markdown
ContributorAuthor

hmmm, nothing was changed in my force push and the checks it's failing are running fine locally...? The failure on the latest commit:

 Checking lightning v0.0.124 (/home/runner/work/rust-lightning/rust-lightning/lightning)
error[E0609]: no field `holder_commitment_point` on type `ChannelContext<SP>`
--> lightning/src/ln/channel.rs:7806:16
|
7806 | self.context.holder_commitment_point.try_resolve_pending(
| ^^^^^^^^^^^^^^^^^^^^^^^ unknown field
|
= note: available fields are: `config`, `prev_config`, `inbound_handshake_limits_override`, `user_id`, `channel_id` ... and 80 others

But I don't even have that line? It's also weird it says 0.0.124...should I maybe rebase or something?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI gets run rebased against upstream so you probably have a silent merge conflict. A rebase should turn it up and then we can land.

We are choosing to move the `HolderCommitmentPoint` (the struct that
tracks commitment points retrieved from the signer + the commitment
number) to handle channel establishment, where we have no commitment
point at all. Previously we introduced this struct to track when we were
pending a commitment point (because of an async signer) during normal
channel operation, which assumed we always had a commitment point to
start out with.
Intiially we tried to add an `Uninitialized` variant
that held no points, but that meant that we needed to handle that case
everywhere which left a bunch of scary/unnecessary unwraps/expects.
Instead, we just hold an optional `HolderCommitmentPoint` struct
on our unfunded channels, and a non-optional `HolderCommitmentPoint`
for funded channels.
This commit starts that transition. A following commit will remove the
holder commitment point from the current `ChannelContext`.
This also makes small fixups to the comments on the
HolderCommitmentPoint variants.
Following a previous commit adding `HolderCommitmentPoint` elsewhere, we
make the transition to use those commitment points and remove the
existing one.
In the event that a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for this before we
can send `open_channel`. We make sure to get the first two commitment
points, so when we advance commitments, we always have a commitment
point available.
When initializing a context, we set the `signer_pending_open_channel`
flag to false, and leave setting this flag for where we attempt to
generate a message.
When checking to send messages when a signer is unblocked, we must
handle both when we haven't gotten any commitment point, as well as when
we've gotten the first but not the second point.
For all of our async signing logic in channel establishment v1, we set
signer flags in the method where we create the raw lightning message
object. To keep things consistent, this commit moves setting the signer
flags to where we create funding_created, since this was being set
elsewhere before.
While we're doing this cleanup, this also slightly refactors our
funding_signed method to move some code out of an indent, as well
as removes a log to fix a nit from lightningdevkit#3152.
Similar to `open_channel`, if a signer cannot provide a commitment point
immediately, we set a flag to remember we're waiting for a point to send
`accept_channel`. We make sure to get the first two points before moving
on, so when we advance our commitment we always have a point available.
Here we handle the case where our signer is pending the next commitment
point when we try to send channel ready. We set a flag to remember to
send this message when our signer is unblocked. This follows the same
general pattern as everywhere else where we're waiting on a commitment
point from the signer in order to send a message.
Here we make a test that disables a channel signer's ability
to return commitment points upon being first derived for a channel.
We also fit in a couple cleanups: removing a comment referencing a
previous design with a `HolderCommitmentPoint::Uninitialized` variant,
as well as adding coverage for updating channel maps in async closing
signed.
@alecchendev
alecchendevforce-pushed the 2024-06-async-commit-point-funding branch from be67d99 to d1e94bdCompareDecember 13, 2024 21:08
)));
}
self.context.assert_no_commitment_advancement("initial commitment_signed");
let holder_commitment_point = &mut self.holder_commitment_point.clone();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm very much not a fan of this. Its really weird to clone something to update something that is in self before calling methods on self. Instead, we should probably consider tweaking InitialRemoteCommitmentReceiver to make the methods on it actually static instead of in the trait, allowing us to just borrow the fields we need out of self.

{
self.context.maybe_downgrade_channel_features(fee_estimator)?;
Ok(self.get_open_channel(chain_hash))
self.get_open_channel(chain_hash, logger).ok_or(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could use a comment describing why its okay to return an Err if get_open_channel failed. We use the result of maybe_handle_error_without_close to check if we have handled the error without needing to FC the channel, so this code reads like a bug. But, actually, because we'll reuse the same point, once get_open_channel returns a value once, it should always return a value (and never wait on a signer operation again), and thus if we get a failure here its because the peer sent an error before we ever sent the OpenChannel (which really shouldn't be possible, they shouldn't know the temporary channel id until we do).


let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some();
let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some()
|| channel.context.signer_pending_channel_ready;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to apply this diff to v2 as well.

Sign up for freeto 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

@alecchendev@codecov-commenter@TheBlueMatt@valentinewallace