-
Notifications
You must be signed in to change notification settings - Fork 414
Enable Creation of Offers and Refunds Without Blinded Path #3246
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
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3246 +/- ##
==========================================
+ Coverage 89.72% 90.58% +0.86%
==========================================
Files 164 164
Lines 133333 142506 +9173
Branches 133333 142506 +9173
==========================================
+ Hits 119627 129084 +9457
+ Misses 11031 10828 -203
+ Partials 2675 2594 -81 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
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.
Design LGTM, feel free to land it after review @jkczyz
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
Updated from pr3246.21 to pr3246.22 (diff): Changes:
|
In earlier versions of LDK, [`Router`] implemented [`MessageRouter`], but the two have since been decoupled. The removed documentation is outdated and no longer accurate, so it has been cleaned up in this commit.
Previously, the `compact_paths` flag was only used to determine whether to use a compact introduction node when creating compact blinded paths. With the upcoming change to accept `MessageForwardNode` in `create_blinded_paths`, there's a risk of SCIDs being passed (and used) even when the user intends to create a full-length blinded path. This patch updates the logic in `create_blinded_paths_from_iter` to ignore SCIDs unless `compact_paths` is explicitly true—preserving correct behavior for full-length blinded paths.
To prepare for supporting both standard and compact blinded paths, this commit updates the `create_blinded_paths` function to take a `Vec<MessageForwardNode>` as input. This change ensures the function has all the information it needs to handle both types of blinded path creation. This refactor that sets the stage for upcoming enhancements.
To make the purpose of each `MessageRouter` implementation unambiguous, this commit sets a direction where the type of `MessageRouter` used deterministically defines the kind of blinded paths created. As a step toward this goal, two new default routers are introduced: - `NodeIdMessageRouter` – creates full-length blinded paths using the peer's node ID. - `NullMessageRouter` – intentionally creates no blinded paths.
To allow choosing different message router types for testing nodes, convert `TestMessageRouter` to an enum with variants `DefaultMessageRouter` and `NodeIdMessageRouter`. This provides better flexibility when testing various scenarios.
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
To simplify blinded path creation and uphold the principle of "One `MessageRouter`, one `BlindedPath` type," this commit updates `create_offer_builder` to use the `create_blinded_paths` method of the `MessageRouter`. Now, when `create_offer_builder` is called, the offer will be created using the `MessageRouter` implementation that the `ChannelManager` or `OffersMessageFlow` is parameterized with. If a user wishes to create an offer with a different type of blinded path, they can explicitly use `create_offer_builder_using_router`, which allows passing a custom `MessageRouter`. The reasoning behind this change is to give users clearer, more deterministic control over the type of blinded path used in the offer. It also improves user awareness, ensuring that creating a non-default blinded path becomes an *intentional choice*.
This change mirrors the previous update to `create_offer_builder`, applying the **“One `MessageRouter`, one `BlindedPath` type”** principle to refund creation. Now, `create_refund_builder` uses the `create_blinded_paths` method of the `MessageRouter` associated with the `ChannelManager` or `OffersMessageFlow`. For non-default path behavior, users can call `create_refund_builder_using_router` and pass a custom `MessageRouter`. See previous commit for detailed reasoning.
This commit completes the series implementing the principle: **“One `MessageRouter`, one `BlindedPath` type.”** As the final step, it removes now-redundant variations of the blinded path creation functions, streamlining the API and simplifying the blinded path creation process.
Introduced tests to validate the behavior of Offers and Refunds created without blinded paths, using `NullMessageRouter`.
/// # Privacy | ||
/// | ||
/// Creating [`BlindedMessagePath`]s may affect privacy since, if a suitable path cannot be found, | ||
/// it will create a one-hop path using the recipient as the introduction node if it is a announced |
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.
s/a announced/an announced
/// [`NullMessageRouter`] **must not** be used as the type parameter for [`ChannelManager`], | ||
/// since [`ChannelManager`] requires a functioning [`MessageRouter`] to create blinded paths, | ||
/// which are necessary for constructing reply paths in onion message communication. | ||
/// However, [`NullMessageRouter`] *can* still be passed as an argument to methods that accept |
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.
s/methods/[`ChannelManager`] methods
} | ||
|
||
/// A special [`MessageRouter`] that performs no routing and does not create blinded paths. | ||
/// Its purpose is to enable the creation of [`Offers`] and [`Refunds`] without blinded paths, |
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.
s/[`Offers`]/s/[`Offer`]s
s/[`Refunds`]/s/[`Refund`]s
|
||
/// A special [`MessageRouter`] that performs no routing and does not create blinded paths. | ||
/// Its purpose is to enable the creation of [`Offers`] and [`Refunds`] without blinded paths, | ||
/// where the user's `signing_pubkey` is used directly as the [`Destination`]. |
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.
s/`signing_pubkey`/`node_id`
&'a TestLogger, | ||
&'a TestKeysInterface, | ||
>, | ||
pub enum TestMessageRouter<'a> { |
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.
Instead of introducing this enum, can each test that needs to a different MessageRouter
simply override NodeCfg::message_router
before calling create_node_chanmgrs
?
@@ -406,7 +416,7 @@ fn creates_short_lived_offer() { | |||
#[test] | |||
fn creates_long_lived_offer() { | |||
let chanmon_cfgs = create_chanmon_cfgs(2); | |||
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); | |||
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs); |
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.
We can use create_offer_builder_using_router
instead.
@@ -470,7 +480,7 @@ fn creates_short_lived_refund() { | |||
#[test] | |||
fn creates_long_lived_refund() { | |||
let chanmon_cfgs = create_chanmon_cfgs(2); | |||
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); | |||
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs); |
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.
Similar.
/// # Privacy | ||
/// | ||
/// Constructs a [`BlindedMessagePath`] for the offer using a custom [`MessageRouter`]. | ||
/// Users can implement a custom [`MessageRouter`] to define properties of the | ||
/// [`BlindedMessagePath`] as required or opt not to create any `BlindedMessagePath`. | ||
/// | ||
/// Also, uses a derived signing pubkey in the offer for recipient privacy. | ||
/// | ||
/// # Limitations | ||
/// | ||
/// See [`OffersMessageFlow::create_offer_builder`] for limitations on the offer builder. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Errors if the provided [`MessageRouter`] is unable to create a blinded path for the offer. | ||
/// | ||
/// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath | ||
/// [`Offer`]: crate::offers::offer::Offer | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest |
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.
Instead of duplicating the docs, could you instead reference ChannelManager::create_offer_builder
docs, noting the differences. Similarly in OffersMessageFlow
.
/// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the offer based on the given | ||
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for | ||
/// privacy implications. | ||
/// Uses the [`ChannelManager`]'s [`MessageRouter`] to construct a [`BlindedMessagePath`] for |
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.
Won't this use OffersMessageFlow
's message router? Seems like ChannelManager
no longer has one as it is being passed through to OffersMessageFlow
. Since we are only creating the flow internally now, perhaps we should word this as:
/// Uses the [`MessageRouter`] passed to [`ChannelManager`]'s constructor to ...
@@ -11024,6 +11024,72 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { | |||
|
|||
Ok(builder.into()) | |||
} | |||
|
|||
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the |
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.
Similarly regarding repeating the docs.
PR Description:
An Offer and a Refund can exist without a blinded path, where the
signing_pubkey
is used to determine the destination. While we could already handle Offers and Refunds without a blinded path, we didn’t have a way to create them like this. This PR addresses that gap and simplifies the blinded path creation flow.The Key Changes:
Pass Router to Builders:
Both
create_offer_builder
andcreate_refund_builder
now take a router as a parameter. This gives users control over how blinded paths are created. For example, users can now choose not to include any blinded paths, or use a custom router for specific behavior.Unified Blinded Path Creation:
The Blinded Path creation has been simplified by only keeping a single function for Blinded Path creation, i.e.,
create_blinded_path
. Older, separate variants likecreate_compact_blinded_paths
andcreate_blinded_paths_using_absolute_expiry
are now redundant and have been removed. This makes the flow cleaner and easier to maintain.Router-Driven Flexibility:
The
DefaultMessageRouter
now uses aRouterConfig
to allow it to create any kind of blinded path (if any) to create. This simplifies the flow, while maintaining the same functionality.This PR makes blinded path creation more straightforward and gives users better control, while preserving the core logic of the
MessageRouter
.blocked on #3412