Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compact blinded path creation #3011

Merged

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Apr 22, 2024

Add support for creating a compact BlindedPath, which consists of:

  • using an SCID instead of a node id in BlindedHop::encrypted_payload
  • using IntroductionNode::DirectedShortChannelId

The first is accomplished by specifying SCIDs when calling BlindedPath::new_for_message using a new message::ForwardNode struct. The second is through calling BlindedPath::use_compact_introduction_node. Both are called by DefaultMessageRouter.

@codecov-commenter
Copy link

codecov-commenter commented Apr 22, 2024

Codecov Report

Attention: Patch coverage is 92.19858% with 11 lines in your changes are missing coverage. Please review.

Project coverage is 90.29%. Comparing base (a95338a) to head (e4661fe).
Report is 7 commits behind head on main.

Files Patch % Lines
lightning/src/ln/offers_tests.rs 81.25% 0 Missing and 6 partials ⚠️
lightning/src/blinded_path/mod.rs 86.20% 3 Missing and 1 partial ⚠️
lightning/src/util/test_utils.rs 50.00% 1 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3011      +/-   ##
==========================================
+ Coverage   89.87%   90.29%   +0.42%     
==========================================
  Files         117      117              
  Lines       96952   100069    +3117     
  Branches    96952   100069    +3117     
==========================================
+ Hits        87134    90357    +3223     
+ Misses       7273     7130     -143     
- Partials     2545     2582      +37     

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

Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

Basically LGTM

lightning/src/onion_message/messenger.rs Outdated Show resolved Hide resolved

/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
/// short channel id from a channel in `network_graph` leading to the introduction node.
pub fn compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Pedantic but the convention is for setters to use the set_ prefix: https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md#gettersetter-apis and I think it would read a bit cleaner in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm... this is not a setter in the normal sense, though, as we don't pass a value to set a field with. Nor is the field private, which would necessitate a setter. It also may not update the field if a value cannot be found in the network graph. Happy to go with a better name. Just not sure if a set_ prefix is appropriate here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Still, it does seem a bit strange that we're mutating the blinded path with a fn name that sounds like it could just as well be a straight getter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, added a use_ prefix. I can see how the docs make it less clear that compact was a verb in the name.

T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, peers: Vec<PublicKey>, secp_ctx: &Secp256k1<T>,
&self, recipient: PublicKey, peers: Vec<PublicKey>, scid_lookup: &SL,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than passing a list of our peers and a trait so that create_blinded_paths can make a callback to the caller to ask for an SCID for a given peer, shouldn't we just pass in the peers as a ForwardNode or (PublicKey, u64)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, come to think of it that is probably cleaner. That would also allow the caller to use the compact hop representation only when desired. (e.g., in offer/refund but not in reply paths).

@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch from 084d070 to a74e84f Compare May 9, 2024 22:55
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Looks like this needs a rebase.

node_id: *node_id,
short_channel_id: peer.channel_by_id
.iter()
.find(|(_, channel)| channel.context().is_usable())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to sort by oldest or biggest or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! Oldest is probably best as this is only for onion messages, so we just want to make sure the channel announcement has been propagated in the gossip.

@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch 2 times, most recently from 5d08b1f to f3726a7 Compare May 13, 2024 21:48
@jkczyz
Copy link
Contributor Author

jkczyz commented May 13, 2024

Rebased

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Basically LGTM, but IMO we really should make this optional somehow. It could happen in a followup if we want, cause we should also change how many hops we include based on if an offfer is long-lived.


/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
/// short channel id from a channel in `network_graph` leading to the introduction node.
pub fn compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Still, it does seem a bit strange that we're mutating the blinded path with a fn name that sounds like it could just as well be a straight getter.

@@ -213,6 +217,30 @@ impl BlindedPath {
},
}
}

/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
/// short channel id from a channel in `network_graph` leading to the introduction node.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should probably have some kind of discussion of how this makes paths shorter but if a channel closes will invalidate it.

if let Some((scid, channel_info)) = node_info
.channels
.iter()
.find_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar here, should we sort by size/age?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good catch. Using the block height from the scid as it seems the timestamp in the update is really a counter specific to the channel.

.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
.map(|(node_id, peer)| ForwardNode {
node_id: *node_id,
short_channel_id: peer.channel_by_id
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we make setting this optional somehow? I feel like if I'm building a super long-term offer I may have a different preference from something being scanned right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... arguably we shouldn't bother with it for reply paths, either. Just not sure exactly how we want to convey it through the MessageRouter trait. Currently, the caller makes the decision for the penultimate hop using ForwardNode, but when adding more hops the MessageRouter makes the decision since it needs a NetworkGraph to find more hops. Similarly for the introduction node.

So right now it's partly an implementation concern given you need a NetworkGraph. I guess we can just add a bool parameter and it say it is best effort? Any other ideas?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see what you mean... if there's an obvious "default behavior" that stands out, we could have a separate method, e.g. create_long_term_blinded_paths, or have a Config struct. That way we could also have a config setting for compact offers vs offers that don't need to be QR-scanned, or privacy-oriented offers that want longer blinded paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened #3080 with separate MessageRouter methods, using compact paths for Offer::paths and Refund::paths and non-compact paths for onion message reply paths. Basically, the trait allows either for the caller to decide.

As for how this is exposed in ChannelManager utilities, I'm not sure how we should handle short- vs long-lived offers. One thought was to chose the type of path based on the expiry. But for offers, the expiry is set by the user after the builder is returned and path already set. For refunds created via ChannelManager, we require an expiration (even though the spec does not), though, so we could infer there.

Alternatives would be:

  • making a special purpose create_offer_builder for long-lived offers
  • adding a parameter to create_offer_builder indicating if short- or long-lived
  • adding a Optional absolute expiry parameter to create_offer_builder used to infer which type of path to create

Any preferences other alternatives?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm good with whichever of those options you think is best!

@@ -450,7 +455,12 @@ where
Err(())
}
},
}?;
for path in &mut paths {
path.compact_introduction_node(&network_graph);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar here, can we make this optional on a per-offer/message basis?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#3080 refactors MessageRouter to have two different methods.

@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch from f3726a7 to b295e98 Compare May 14, 2024 17:17
Copy link
Contributor Author

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Oops, forgot to publish these comments.


/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
/// short channel id from a channel in `network_graph` leading to the introduction node.
pub fn compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, added a use_ prefix. I can see how the docs make it less clear that compact was a verb in the name.

if let Some((scid, channel_info)) = node_info
.channels
.iter()
.find_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good catch. Using the block height from the scid as it seems the timestamp in the update is really a counter specific to the channel.

.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
.map(|(node_id, peer)| ForwardNode {
node_id: *node_id,
short_channel_id: peer.channel_by_id
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... arguably we shouldn't bother with it for reply paths, either. Just not sure exactly how we want to convey it through the MessageRouter trait. Currently, the caller makes the decision for the penultimate hop using ForwardNode, but when adding more hops the MessageRouter makes the decision since it needs a NetworkGraph to find more hops. Similarly for the introduction node.

So right now it's partly an implementation concern given you need a NetworkGraph. I guess we can just add a bool parameter and it say it is best effort? Any other ideas?

Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

LGTM pending maybe making the compact encoding optional. Looks like it needs another rebase as well.

lightning/src/blinded_path/mod.rs Outdated Show resolved Hide resolved
@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch 2 times, most recently from 2dd24ed to 7d85abd Compare May 14, 2024 22:48
@jkczyz
Copy link
Contributor Author

jkczyz commented May 14, 2024

LGTM pending maybe making the compact encoding optional. Looks like it needs another rebase as well.

Rebased now. Yeah, maybe best to do that in a follow-up as it may be a little more involved. Let me know if you have any thoughts on #3011 (comment).

@TheBlueMatt
Copy link
Collaborator

Feel free to squash and we can land this IMO. We should open an issue and block the next release on the followup.

@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch 2 times, most recently from e7f5035 to bb5dc03 Compare May 22, 2024 22:28
@jkczyz
Copy link
Contributor Author

jkczyz commented May 22, 2024

Feel free to squash and we can land this IMO. We should open an issue and block the next release on the followup.

Rebased and opened #3080. Looking for feedback on the ChannelManager API (see #3011 (comment)).

lightning/src/blinded_path/message.rs Show resolved Hide resolved
lightning/src/blinded_path/mod.rs Show resolved Hide resolved
@@ -171,7 +172,7 @@ impl< G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, ES: Deref, S: Deref,
fn create_blinded_paths<
T: secp256k1::Signing + secp256k1::Verification
> (
&self, recipient: PublicKey, peers: Vec<PublicKey>, secp_ctx: &Secp256k1<T>,
&self, recipient: PublicKey, peers: Vec<message::ForwardNode>, secp_ctx: &Secp256k1<T>,
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going to split this into two methods, do we still need message::ForwardNode? Can't one method take pubkeys and the other take scids?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We alwasys need the pubkeys when creating the blinded path, so we'd need to either make a new struct or use a tuple. It's kinda nice having it use an Option for short_channel_id, though. It makes it easy in ChannelManager::create_blinded_path to fallback to None if a usable channel can't be found.

@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch from bb5dc03 to f0d81ea Compare May 23, 2024 20:53
TheBlueMatt
TheBlueMatt previously approved these changes May 28, 2024
lightning/src/blinded_path/message.rs Show resolved Hide resolved
jkczyz added 4 commits May 28, 2024 16:35
When sending an onion message to a blinded path, the short channel id
between hops isn't need in each hop's encrypted_payload since it is not
a payment. However, using the short channel id instead of the node id
gives a more compact representation. Update BlindedPath::new_for_message
to allow for this.
Instead of passing Vec<PublicKey> to MessageRouter::crate_blinded_path,
pass Vec<ForwardNode>. This way callers can include a short_channel_id
for a more compact BlindedPath encoding.
Add a method to BlindedPath that given a network graph will compact the
IntroductionNode as the DirectedShortChannelId variant. Call this method
from DefaultMessageRouter so that Offer paths use the compact
representation (along with reply paths). This leaves payment paths in
Bolt12Invoice using the NodeId variant, as the compact representation
isn't as useful there.
@jkczyz jkczyz force-pushed the 2024-04-compact-blinded-path-creation branch from f0d81ea to e4661fe Compare May 28, 2024 21:42
@jkczyz
Copy link
Contributor Author

jkczyz commented May 28, 2024

Added derives to ForwardNode.

@TheBlueMatt TheBlueMatt merged commit df01208 into lightningdevkit:main May 29, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants