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

Blinded pathfinding groundwork #2146

Conversation

valentinewallace
Copy link
Contributor

This lays some groundwork commits for #2120. Split out because we'll need some more interface changes and I don't want to hold up the serialization breakage portion, which we want to land for 0.0.115.

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from 9ffccf0 to 510db9f Compare April 3, 2023 22:28
@valentinewallace
Copy link
Contributor Author

Oops, benchmark made me realize that the scoring methods need updates too. Will push in a bit.

@valentinewallace valentinewallace added this to the 0.0.115 milestone Apr 3, 2023
@codecov-commenter
Copy link

codecov-commenter commented Apr 4, 2023

Codecov Report

Patch coverage: 84.70% and project coverage change: +0.30 🎉

Comparison is base (b8ed4d2) 91.39% compared to head (b131634) 91.70%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2146      +/-   ##
==========================================
+ Coverage   91.39%   91.70%   +0.30%     
==========================================
  Files         104      104              
  Lines       51431    53043    +1612     
  Branches    51431    53043    +1612     
==========================================
+ Hits        47007    48643    +1636     
+ Misses       4424     4400      -24     
Impacted Files Coverage Δ
lightning/src/lib.rs 67.74% <ø> (ø)
lightning/src/offers/invoice_request.rs 93.40% <ø> (ø)
lightning/src/offers/offer.rs 91.31% <ø> (+1.42%) ⬆️
lightning/src/offers/refund.rs 96.70% <ø> (+2.33%) ⬆️
lightning/src/offers/test_utils.rs 96.72% <ø> (ø)
lightning/src/onion_message/packet.rs 75.53% <ø> (+0.78%) ⬆️
lightning/src/routing/router.rs 95.20% <ø> (-0.15%) ⬇️
lightning/src/util/ser.rs 89.42% <ø> (-0.20%) ⬇️
lightning/src/events/mod.rs 34.19% <5.88%> (+0.17%) ⬆️
lightning/src/ln/functional_test_utils.rs 92.72% <60.00%> (-0.02%) ⬇️
... and 19 more

... and 35 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace valentinewallace marked this pull request as draft April 4, 2023 19:34
@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 3 times, most recently from 6e98fa5 to b5f4349 Compare April 5, 2023 17:25
@valentinewallace
Copy link
Contributor Author

Rebased.

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from b5f4349 to f2a9bb2 Compare April 5, 2023 18:09
@valentinewallace valentinewallace marked this pull request as ready for review April 5, 2023 21:26
lightning/src/util/ser.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from f2a9bb2 to 86056fc Compare April 6, 2023 20:05
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.

Generally lgtm, note that we'll want to squash down the commits that fix things after the path introduction to avoid having broken commits in the history. To make it simpler, I wonder if it isn't easier to split the vec->path commit into one commit that does that transition without adding the blinded_tail field, and then a separate one that does.

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
}
}
if blinded_tails.is_some() && blinded_tails.as_ref().unwrap().len() != self.paths.len() {
return Err(io::Error::new(io::ErrorKind::Other, "Missing blinded tail for path (if blinded tails are included, there must be 1 set per path)"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just make the serialization format support this by pushing the option onto the vec? I mean I guess we hopefully won't ever actually use this, but in general all of our in-memory structs have a strong guarantee that they will never fail to serialize as long as the underlying Write doesn't fail a push.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm assuming we don't want to write anything unless a blinded tail is actually present, since it's an even tlv. So we could do what you're suggesting, but that would unnecessarily allocate a vec of Nones in most cases, and then we'd have to check that none of them are Some before writing, which all seems unfortunate(?).

I think it would be ok to just remove this check, since we have strong in-memory guarantees as you say?

Copy link
Collaborator

Choose a reason for hiding this comment

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

On second thought, let's support writing the Nones? We don't have to always allocate - we can allocate the required number when we find the first blinded_tail set. The issue here is we dont have any in-memory guarantees that there arent some paths that have a blinded tail and others that dont, and while its kinda nonsense, its also the case that someone could construct such a route and we could pay it without issue.

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
@TheBlueMatt
Copy link
Collaborator

Also needs rebase, sorry :(.

Copy link
Contributor

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

Concept ACK

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
lightning/src/routing/router.rs Outdated Show resolved Hide resolved
lightning/src/routing/router.rs Outdated Show resolved Hide resolved
lightning/src/routing/scoring.rs Outdated Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 5 times, most recently from 4d1c9a0 to 96e3ff0 Compare April 12, 2023 19:36
@valentinewallace
Copy link
Contributor Author

Rebased.

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
lightning/src/routing/router.rs Outdated Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 3 times, most recently from d74e4ed to 35e6e58 Compare April 18, 2023 18:13
lightning/src/routing/router.rs Outdated Show resolved Hide resolved

/// A path in a [`Route`] to the payment recipient. Must always be at least length one. While the
/// maximum length of any given path is variable, keeping the length of any path less than or equal
/// to 19 should currently ensure it is viable.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this true with blinded paths, we have to give each hop a pubkey right, so the max length is much shorter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I meant that path.len() should be <=19, which includes blinded hops. Will clarify.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But my point is that its not 19, as each blinded hop is substantially longer than non-blinded ones, the maximum length is variable. cc #2201

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 have to give each hop a pubkey right

I believe we only need to give the intro node's payload a (blinding point) pubkey. But there is more data in blinded hop payloads, yeah.

I updated the comment to caveat that 19 only applies to paths without a blinded tail. Do you want to go through updating MAX_PATH_LENGTH_ESTIMATE and other docs updates in this PR, or save this for later addressing #2201?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can address in 2201, I think, we're not generating such paths yet anyway.

}
}
if blinded_tails.is_some() && blinded_tails.as_ref().unwrap().len() != self.paths.len() {
return Err(io::Error::new(io::ErrorKind::Other, "Missing blinded tail for path (if blinded tails are included, there must be 1 set per path)"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

On second thought, let's support writing the Nones? We don't have to always allocate - we can allocate the required number when we find the first blinded_tail set. The issue here is we dont have any in-memory guarantees that there arent some paths that have a blinded tail and others that dont, and while its kinda nonsense, its also the case that someone could construct such a route and we could pay it without issue.

lightning/src/ln/channelmanager.rs Show resolved Hide resolved
let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta;
max_path_offset = cmp::max(
max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA),
max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA);
shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);

// Add 'shadow' CLTV offset to the final hop
if let Some(last_hop) = path.last_mut() {
if let Some(tail) = path.blinded_tail.as_mut() {
tail.final_cltv_expiry_delta = tail.final_cltv_expiry_delta
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we skip adding it to the last hop 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.

For the current docs to be accurate, we would need to mirror the cltv_expiry_delta in path.hops.last() and BlindedTail for 1-hop blinded paths. We could add a special case to the docs and make it 0 in the last hop, if you prefer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh oh, right, because the last hop includes the blinded CLTV delta, okay, nvm.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, wait, I'm confused on this again - the way I read the current docs the blinded path's final_cltv_expiry_delta is the final cltv expiry delta the recipient wants to receive, and the cltv_expiry_delta of the last hop is the difference between that and the introduction point's cltv expiry. Thus, if we only want to change the last hop's cltv (and have it bubble up through the route) we should just change the blinded path part.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yeah, you're right. Updating this and the docs. I had the last hop as containing the entire blinded path's cltv delta, including the recipient's, but that doesn't make sense.

Btw, I'm not entirely sure how to get the final_cltv_expiry_delta for the last hop. It appears that eclair computes it as 0 + random_shadow_offset. The route blinding spec indicates that a max_cltv_expiry should be provided by the receiver in the BOLT12 invoice, but that doesn't seem to be a thing in the offers spec.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah! I guess the cltv delta in the BlindedPayInfo includes the recipient, then? Let's just remove the cltv delta from the blinded tail, then?

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 2 times, most recently from a526988 to 9861527 Compare April 19, 2023 17:15
@valentinewallace
Copy link
Contributor Author

Squashed and rebased.

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
lightning/src/routing/router.rs Show resolved Hide resolved
lightning/src/util/macro_logger.rs Outdated Show resolved Hide resolved
lightning/src/events/mod.rs Show resolved Hide resolved
Comment on lines +365 to +379
if let Some(blinded_tail) = &path.blinded_tail {
if blinded_tails.is_empty() {
blinded_tails = Vec::with_capacity(path.hops.len());
for _ in 0..idx {
blinded_tails.push(None);
}
}
blinded_tails.push(Some(blinded_tail));
} else if !blinded_tails.is_empty() { blinded_tails.push(None); }
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be the case that if one path has a blinded tail then all should? Should we fail if that's not the 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.

Originally I required all-or-nothing for Path BlindedTails, but ended up allowing it because even though we'll never construct such Route's, we'd have no issue paying them. See #2146 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should, but I complained because in general we have a strong guarantee that if something exists in-memory, it can be serialized. We don't have a reasonable way to provide/handle errors serializing objects, so we generally panic when writing to a sink that cannot fail (eg &mut Vec::new()). If we're particularly worried about it, we should make the fields private and enforce the preconditions when updating/creating a route, but I'm not sure we need to be - while somewhat nonsensical, it is possible to pay such a route just fine.

BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }
],
};
// (De)serialize a Route with 1 blinded path out of two total paths.
Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise, would this ever happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably not, but it was broken in a prior iteration so thought I'd test it. Fine to remove.

lightning/src/routing/scoring.rs Outdated Show resolved Hide resolved
@@ -2111,6 +2111,8 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
let network_nodes = network_graph.nodes();

for path in route.paths.iter_mut() {
if path.blinded_tail.as_ref().map_or(false, |tail| tail.hops.len() > 1) { continue }
Copy link
Contributor

Choose a reason for hiding this comment

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

Could probably use a comment as to why this will skip.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm also confused as to why we're doing this? I guess we just assume there's enough privacy if we're paying a blinded path? Maybe let's make the constant 2 instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I was concerned we might blow up the cltv expiry if blinded path recipients add their own shadow offsets, but I think it makes more sense to just always add a shadow offset for blinded paths, rather than only adding it for 1-hop blinded paths. Updated.

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.

Given we're refusing to pay blinded paths and we moved the value calculation to happen before the addition of the blinded paths object, I don't really care if this is squashed anymore or not. Because our value calculations are right I'm okay with not, even if its a bit weird our serialization is lossy in the intermediate commits.

lightning/src/routing/router.rs Outdated Show resolved Hide resolved
let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta;
max_path_offset = cmp::max(
max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA),
max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA);
shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);

// Add 'shadow' CLTV offset to the final hop
if let Some(last_hop) = path.last_mut() {
if let Some(tail) = path.blinded_tail.as_mut() {
tail.final_cltv_expiry_delta = tail.final_cltv_expiry_delta
Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, wait, I'm confused on this again - the way I read the current docs the blinded path's final_cltv_expiry_delta is the final cltv expiry delta the recipient wants to receive, and the cltv_expiry_delta of the last hop is the difference between that and the introduction point's cltv expiry. Thus, if we only want to change the last hop's cltv (and have it bubble up through the route) we should just change the blinded path part.

@@ -2111,6 +2111,8 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
let network_nodes = network_graph.nodes();

for path in route.paths.iter_mut() {
if path.blinded_tail.as_ref().map_or(false, |tail| tail.hops.len() > 1) { continue }
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm also confused as to why we're doing this? I guess we just assume there's enough privacy if we're paying a blinded path? Maybe let's make the constant 2 instead?

@@ -721,8 +718,8 @@ impl OutboundPayments {
}
};
for path in route.paths.iter() {
if path.len() == 0 {
log_error!(logger, "length-0 path in route");
if path.hops.len() == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wait, isn't this actually okay? If we're the introduction node for a blinded path away from us the hops should be empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case that we're the intro node, we'll want to advance the blinded path before pathfinding (otherwise how can we pathfind to ourselves?). So I think we-are-the-intro-node should be an error in payment parameters initialization.

Therefore, I think hops.len() will always be > 0? If that's not the case, I have a bunch of path.hops.first().unwrap()s to update..

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, okay, I wasn't sure what the approach was, that makes sense.

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 2 times, most recently from dd8e87b to d974016 Compare April 21, 2023 15:33
@TheBlueMatt
Copy link
Collaborator

LGTM, feel free to squash.

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from d974016 to 6383245 Compare April 21, 2023 15:48
@valentinewallace
Copy link
Contributor Author

Squashed without diff

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch 2 times, most recently from 871a609 to 7631b6a Compare April 21, 2023 18:35
@valentinewallace
Copy link
Contributor Author

Squashed the commit disallowing paying blinded payment paths into the commit that adds blinded path info to Path. So we shouldn't need to squash the last 10 commits into one anymore

@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from 7631b6a to dc426a4 Compare April 21, 2023 19:34
@valentinewallace valentinewallace force-pushed the 2023-03-blinded-pathfinding-groundwork branch from dc426a4 to b131634 Compare April 21, 2023 19:35
@TheBlueMatt TheBlueMatt merged commit 607727f into lightningdevkit:main Apr 24, 2023
14 checks passed
@jkczyz jkczyz mentioned this pull request May 10, 2023
57 tasks
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.

None yet

5 participants