-
Notifications
You must be signed in to change notification settings - Fork 366
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
Enable Creation of Offers and Refunds Without Blinded Path #3246
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3246 +/- ##
==========================================
+ Coverage 89.59% 90.01% +0.42%
==========================================
Files 127 127
Lines 103438 107553 +4115
Branches 103438 107553 +4115
==========================================
+ Hits 92672 96812 +4140
- Misses 8049 8097 +48
+ Partials 2717 2644 -73 ☔ View full report in Codecov by Sentry. |
lightning/src/ln/offers_tests.rs
Outdated
assert_eq!(refund.payer_id(), alice_id); | ||
assert!(refund.paths().is_empty()); | ||
} | ||
|
||
/// Checks that blinded paths are compact for short-lived offers. | ||
#[test] | ||
fn creates_short_lived_offer() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming at least for the following tests is no longer relevant, it seems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, with the introduction of BlindedPathParameters
, it seems like the behavior these tests were checking isn't relevant anymore. Should we think about removing these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait on the resolution of #3246 (comment). We'll ultimately want to test cases allowed by whatever interface is exposed to the user.
7a18051
to
bc00884
Compare
Updated from pr3246.06 to pr3246.07 (diff): Changes:
|
bc00884
to
3bcc420
Compare
lightning/src/ln/channelmanager.rs
Outdated
let context = MessageContext::Offers(context); | ||
let path = $self | ||
.create_blinded_paths(context) | ||
.and_then(|paths| paths.into_iter().next().ok_or(())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... @TheBlueMatt I don't think this interface is sufficient to allow someone to specify they want more than one path in the offer, unless we include all the paths returned by the MessageRouter
instead of just one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, IMO we should include all paths - if the router wants to do something funky in the offer, so be it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should we have DefaultMessageRouter
do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we currently do (use the context of why we're asking for a path to decide how many paths to include)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose DefaultMessageRouter
could use both MessageContex
and BlindedPathType
-- or which MessageRouter
method is called, if we revert to that state -- to determine if more than one path should be returned. That way we'd allow users to pass BlindedPathType::Full
to create_offer_builder
such that they can create an offer with more than one path (e.g., when an offer doesn't need to be in a QR code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that makes sense but then I'm confused about your previous comment about keeping get interface the same as it is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we change MessageRouter
to only have one method, then we need to pass in BlindedPathType
. But then the peers
parameter will need to be Vec<MessageForwardNode>
even when used with BlindedPathType::Full
instead of Vec<PublicKey>
. So callers could pass in a MessageForwardNode
with an scid even though it isn't expected, resulting in the last hop being compact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So callers could pass in a MessageForwardNode with an scid even though it isn't expected, resulting in the last hop being compact.
Makes sense! I have reintroduced the two function flow back in pr3246.11
Also, I have updated the DefaultMessageRouter's create_compact_blinded_paths
to only return a single path in pr3246.11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this is a concern, if the MessageRouter
insists on a specific type of blinded path in general, so what? Ideally the MessageRouter
gets to pick what it wants to do, though I think we're not allowed to have a compact introduction point in reply paths? Even in that case, though, ISTM we can communicate the restrictions and if the MessageRouter
is buggy we can just fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this is a concern, if the
MessageRouter
insists on a specific type of blinded path in general, so what? Ideally theMessageRouter
gets to pick what it wants to do,
Nothing is preventing a MessageRouter
from doing so. It would just need access to the channels.
It's just kinda ugly for DefaultMessageRouter
to need to clear short_channel_id
depending on how it is called. Plus, ChannelManager
needs to do additional lookups to get this information just to have it discarded. I'm fine either way, but it doesn't optimize the usual case (i.e., use non-compact paths for reply paths).
though I think we're not allowed to have a compact introduction point in reply paths? Even in that case, though, ISTM we can communicate the restrictions and if the
MessageRouter
is buggy we can just fail.
Yeah, though it doesn't look like we are enforcing this at the moment.
Updated from pr3246.10 to pr3246.11 (diff): Changes:
|
@@ -222,6 +222,34 @@ pub enum PendingHTLCRouting { | |||
}, | |||
} | |||
|
|||
/// Represents the types of [`BlindedMessagePath`] that can be created. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should be requesting specific types of compact paths from the ChannelManager
. This is still the ChannelManager
deciding what kind of blinded path it wants, but IMO we should be moving towards the ChannelManager
communicating why it wants a blinded path and letting the MessageRouter
pick what kind of blinded path is best (number of paths, number of hops, type, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you clarify what sort of interface you want for create_offer_builder
and create_refund_builder
? It's not clear to me what you're looking for that will satisfy the three use cases that I outlined in #3246 (comment). Are you just asking for a rename or something else?
lightning/src/ln/channelmanager.rs
Outdated
let context = MessageContext::Offers(context); | ||
let path = $self | ||
.create_blinded_paths(context) | ||
.and_then(|paths| paths.into_iter().next().ok_or(())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this is a concern, if the MessageRouter
insists on a specific type of blinded path in general, so what? Ideally the MessageRouter
gets to pick what it wants to do, though I think we're not allowed to have a compact introduction point in reply paths? Even in that case, though, ISTM we can communicate the restrictions and if the MessageRouter
is buggy we can just fail.
|
||
let builder = match absolute_expiry { | ||
None => builder, | ||
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to keep passing absolute expiry? Presumably this would be a part of the information communicated to the router, as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you be explicit about what you want the MessageRouter
interface to look like? Feels like we are going back and forth without much progress...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently this PR isn't changing MessageRouter
, but I was thinking basically (but with better names):
enum WhyWeWantABlindedPath {
ForOffer { offer_expiry_time, ... },
ForRefund { ... },
ForResponsePath { ... },
}
trait MessageRouter {
fn create_blinded_path(&self, recipient: PublicKey, context: MessageContext, why: WhyWeWantABlindedPath, peers: Vec<MessageForwardNode>, secp_ctx: &Secp256k1<T>) -> Result<Vec<BlindedMessagePath>, ...>;
}
ie focusing very much on the why, not the how in the interface. You're right that building the MessageForwardNode
when we don't actually want a compact path is a bit more annoying, but iterating the channel list for each peer after we already have the peer look should be basically free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this approach makes sense!
Assuming the scid
lookup is almost free, if MessageRouter
has access to MessageForwardNode
and WhyWeWantABlindedPath
, it can make the right choice between creating a full-length or compact blinded path.
Additionally, if it’s the main decider on path compactness, it could manage the following setting:
for path in &mut paths {
path.use_compact_introduction_node(&network_graph);
}
This way, it can help avoid creating a compact last hop for full-length paths.
To support the three cases Jeff mentioned in his comment, we could consider updating the create_offer_builder
input fields like so:
fn create_offer_builder(..., why: Option<WhyWeWantABlindedPath>) -> Offer {
...
}
- Passing
None
supports creating an offer without a blinded path. - Passing
WhyWeWantABlindedPath::ForOffer { offer_expiry_time, is_size_constraint: true, ... }
covers the offer for QR codes. - Passing
WhyWeWantABlindedPath::ForOffer { offer_expiry_time, is_size_constraint: false, ... }
covers the unconstrained setting (e.g., an offer on wire).
I do have a couple of things I’m curious about with this approach:
→ Since MessageRouter
is a pub
trait and could be implemented outside of LDK, what if someone uses it in a setting where scid
lookup for creating MessageForwardNode
is resource-intensive?
→ Would it be useful for MessageRouter
to send a note upstream on which type of Blinded Path it ultimately created?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like there would be some duplication with MessageContext
. And if a new variant is added to MessageContext
, then one would need to be added to WhyWeWantABlindedPath
, too. How about:
pub enum BlindedPathUse {
Persistent,
Ephemeral { expiry: Duration },
ReplyPath,
}
That way were aren't tying it strictly to existing BOLT12 use cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe? I was thinking about the context because someone might want to have a different policy for refunds vs offers, no matter the specific expiry, but maybe we don't need to support that?
It's still possible to differentiate using the passed MessageContext
.
Indeed, supporting non-BOLT12 is a bit difficult, though, so maybe the more generic terminology is right.
Yeah, I think I'd prefer that, but we still need to figure out the ChannelManager
API. We'll need to continue to pass the absolute expiry, but now we need another parameter to indicate whether to include any blinded paths. Using Option<BlindedPathUse>
won't work since BlindedPathUse::ReplyPath
isn't applicable.
We could drop that variant in favor of using Persistent
for both that and long-lived offers/refund given we don't need to use the compact representation for reply paths, even though their lifetime is ephemeral.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but we still need to figure out the ChannelManager API. We'll need to continue to pass the absolute expiry, but now we need another parameter to indicate whether to include any blinded paths. Using Option won't work since BlindedPathUse::ReplyPath isn't applicable.
I'm not sure I'm convinced it needs to change. The interface currently allows someone to decide to/not to include a blinded path based on the type of thing we're building, so the question is whether someone is going to want to generate both blinded and non-blinded versions of the same object(s) at the same time in the same node. I can see someone wanting an option to switch it, but that option can be in their route-wrapper, the question is if they will hit races in doing so.
If a user does want this, however, they might also want some different path selection that isn't just "blinded path or not", they might want more/fewer/longer/shorter/compact/non-compact paths based on the specific call. The only way to allow for that, I think, is to pass some kind of opaque "ID" through the ChannelManager
to the router which the user can map on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm convinced it needs to change. The interface currently allows someone to decide to/not to include a blinded path based on the type of thing we're building, so the question is whether someone is going to want to generate both blinded and non-blinded versions of the same object(s) at the same time in the same node. I can see someone wanting an option to switch it, but that option can be in their route-wrapper, the question is if they will hit races in doing so.
Regarding races, seems we could make methods like create_offer_builder_using_router
in order to pass a custom router. DefaultMessageRouter
is cheap to construct, so I can imagine we could also provide methods on it to customize it for one-time use (i.e., by wrapping it). For example:
let router = DefaultMessageRouter::new(network_graph, entropy_source).without_blinded_paths();
let offer = channel_manager.create_offer_builder_with_router(router).build().unwrap();
Or provide a separate MessageRouter
implementation that returns no paths. Either approach would have equivalent behavior.
If a user does want this, however, they might also want some different path selection that isn't just "blinded path or not", they might want more/fewer/longer/shorter/compact/non-compact paths based on the specific call. The only way to allow for that, I think, is to pass some kind of opaque "ID" through the
ChannelManager
to the router which the user can map on.
Passing a custom router would be cleaner, IMO, given it can be essentially stateless. ChannelManager
only needs to hold one for replies. Though arguably a future OffersMessageFlow
would own that instead. In that world, we could have the OffersMessageFlow
's user-provided trait parameterization specify how to create reply routes. It would make it such that OffersMessageFlow
wouldn't need a direct MessageRouter
parameterization, though that may not be an entirely necessary thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding races, seems we could make methods like create_offer_builder_using_router in order to pass a custom router.
Makes sense to me. Hadn't considered that. Keeping today's methods and default to the ChannelManager
MessageRouter
, and then we can add methods to override it if a user wants, makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that world, we could have the OffersMessageFlow's user-provided trait parameterization specify how to create reply routes. It would make it such that OffersMessageFlow wouldn't need a direct MessageRouter parameterization, though that may not be an entirely necessary thing to do.
Hmm, not quite visualizing this one but ISTM that we'd want OffersMessageFlow
to do most of the things ChannelManager
does today, mostly just moving the code into the new flow manager.
1. Introduce a new enum that allows the user to specify the type of Blinded Path (whether Compact or Full-Length) when creating a Blinded Path. 2. This PR utilizes this new enum in two ways in the upcoming commits: - Allows explicitly providing the type of Blinded Path to be created instead of relying on the absolute expiry time for offers and refunds. - Simplifies the Blinded Path creation process by enabling a single function flow for creating both types of Blinded Path.
…ilder` 1. This commit introduces flexibility for the user to specify the type of Blinded Path they want, rather than relying on the Offers' absolute expiry to determine the Blinded Path type. 2. It also adds support for creating an Offer without a Blinded Path, using its signing public key for responding to the offer instead.
Similar to the offer case, this commit introduces the ability to explicitly specify the Blinded Path type.
- Instead of appending just a single blinded path to offers and refunds, update the code to append all paths returned by MessageRouter. - This change allows MessageRouter to control the number of blinded paths appended to offers and refunds, providing flexibility for them to contain more than one path.
PR Description:
An Offer and a Refund can exist without a blinded path. In such cases, the corresponding
signing_pubkey
is used to determine the destination. However, while we currently have the ability to handle Offers and Refunds without a blinded path, we lack the ability to create them this way.This PR addresses that gap by introducing a Blinded Path Type enum, allowing for a more flexible creation process.
The Key Changes:
None
, allowing the Offer or Refund to be created without a blinded path.Important Notes:
scid
lookups required for compact blinded paths are avoided when the creator does not preferably want them to begin with.By introducing this simple yet effective enum, we provide the flexibility to create Offers and Refunds without a blinded path, while still preserving the core logic of the MessageRouter.