Skip to content

Conversation

shaavan
Copy link
Member

@shaavan shaavan commented Jul 26, 2025

This PR refactors how invoice building works from a VerifiedInvoiceRequest, replacing runtime checks with compile-time guarantees. Previously, invoice builder selection (between using_derived_key and using_explicit_key) was based on an optional key field, which left room for misuse and bugs that could only be caught at runtime.

To address this, VerifiedInvoiceRequest is now parameterized by a SigningPubkeyStrategy, exposing only the appropriate builder method for each variant. This enforces correctness at the type level and prevents invalid combinations from compiling.

Additional changes:

  • Builder method split: The OffersMessageFlow interface now uses distinct builder paths based on the VerifiedInvoiceRequest variant, shifting variant matching to compile time.
  • Signing moved to ChannelManager: Aligns with existing patterns and gives users flexibility to customize the InvoiceBuilder before signing.
  • Closure for hash/secret generation: Centralizes (payment_hash, payment_secret) creation and consolidates amount_msats into a single authoritative source.

Reasoning:

This change lays the groundwork for #3833, which introduces Flow events to support manual handling of OffersMessages.

Invoice building from an InvoiceRequest depends on a SigningPubkeyStrategy, which can be either:

  • DerivedSigningPubkey: the key is derived from the offer inside the request, or
  • ExplicitSigningPubkey: the key is supplied by the user when signing the final invoice.

Previously, we relied on runtime checks to validate builder–strategy alignment. While this worked, it lacked compile-time safety—invalid combinations could still compile and only fail at runtime.

This was acceptable under the previous model, where users couldn’t build invoices manually (e.g., when using the default OffersMessageFlow with ChannelManager). But with the Flow event API introduced in #3833, users will now handle invoice requests asynchronously and construct invoices directly.

With this PR, only the valid builder–strategy pairs are allowed at compile time. This improves type safety, prevents incorrect usage, and makes the manual invoice-generation flow more robust as LDK evolves.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jul 26, 2025

👋 I see @valentinewallace was un-assigned.
If you'd like another reviewer assignment, please click here.

@shaavan
Copy link
Member Author

shaavan commented Jul 26, 2025

cc @jkczyz

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz self-requested a review July 29, 2025 15:21
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@shaavan
Copy link
Member Author

shaavan commented Aug 7, 2025

Updated from pr3964.01 to pr3964.02 (diff):
Addressed @jkczyz comments

Changes:

  1. Improved functional naming, and documentations.

Copy link

codecov bot commented Aug 7, 2025

Codecov Report

❌ Patch coverage is 85.87571% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.77%. Comparing base (ac8f897) to head (72a20a4).
⚠️ Report is 59 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/offers/invoice_request.rs 81.48% 10 Missing ⚠️
lightning/src/offers/flow.rs 87.09% 0 Missing and 8 partials ⚠️
lightning/src/offers/invoice.rs 76.47% 3 Missing and 1 partial ⚠️
lightning/src/ln/channelmanager.rs 94.11% 2 Missing ⚠️
lightning/src/ln/offers_tests.rs 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3964      +/-   ##
==========================================
- Coverage   88.85%   88.77%   -0.09%     
==========================================
  Files         175      175              
  Lines      127710   127980     +270     
  Branches   127710   127980     +270     
==========================================
+ Hits       113478   113608     +130     
- Misses      11675    11806     +131     
- Partials     2557     2566       +9     
Flag Coverage Δ
fuzzing 21.67% <0.00%> (-0.19%) ⬇️
tests 88.60% <85.87%> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot
Copy link

🔔 6th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 7th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 8th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@shaavan
Copy link
Member Author

shaavan commented Aug 15, 2025

Updated from pr3964.02 to pr3964.03 (diff):
Addressed @jkczyz comments

Changes:

  • Rename InvoiceSigningInfo -> InvoiceRequestVerifiedFromOffer.

@shaavan
Copy link
Member Author

shaavan commented Aug 15, 2025

Updated from pr3964.03 to pr3964.04 (diff):

Changes:

  1. Rebase on main

@ldk-reviews-bot
Copy link

🔔 9th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 10th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 11th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@valentinewallace
Copy link
Contributor

I'm unfortunately not going to have time to review this in a timely manner, so unassigning myself and letting the bot take the wheel

@valentinewallace valentinewallace removed their request for review August 20, 2025 20:15
@ldk-reviews-bot
Copy link

✅ Added second reviewer: @joostjager

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

After an initial pass, my feelings about this PR are mixed. Like probably every other rust dev, I also like strong types and compile time checks. But I am not sure if the old situation with the keys option was a big enough problem to justify the code changes proposed in this PR. Especially because the end result may not be considered fully clean either (panic match arms, code duplication).

Other considerations are dev/review resources available, risk that every change introduces and priority. Perhaps it would be good for this PR (and PRs in general) to add a few lines to the description elaborating on that. Could be downstream advantages for example that aren't visible in the PR itself.

@@ -961,9 +961,43 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
}
} }

macro_rules! fields_accessor {
Copy link
Contributor

Choose a reason for hiding this comment

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

My impression is that we want to get rid of macros as much as possible, not introduce new ones?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the pointer, Joost!

I went with a macro here to avoid duplicating the same function three times across different structs. It felt like a minimally intrusive way to solve the problem while keeping things consistent with the surrounding code.

That said, I agree we could move away from macros in the long term. Removing this one (and similar ones — e.g. in invoice_request.rs) probably makes more sense as part of a broader refactor, which I think is best handled in a separate PR.

Let me know what you think!

},
};
let payment_paths = self
.create_blinded_payment_paths(
Copy link
Contributor

Choose a reason for hiding this comment

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

Not ideal that code needs to be duplicated now.

@joostjager
Copy link
Contributor

Final thought, not fully checked, is whether there is a simpler way to make the api safe by just moving the respond methods from InvoiceRequest to a place where they can't be misused.

@jkczyz
Copy link
Contributor

jkczyz commented Aug 21, 2025

Other considerations are dev/review resources available, risk that every change introduces and priority. Perhaps it would be good for this PR (and PRs in general) to add a few lines to the description elaborating on that. Could be downstream advantages for example that aren't visible in the PR itself.

Agreed that the PR description should include motivation for the change. Namely, use for custom handling of invoice requests / building custom invoices.

@joostjager
Copy link
Contributor

joostjager commented Aug 22, 2025

Agreed that the PR description should include motivation for the change. Namely, use for custom handling of invoice requests / building custom invoices.

Can I see this somewhere? So that it becomes more clear where the benefits are of the stronger typing.

@shaavan
Copy link
Member Author

shaavan commented Aug 22, 2025

@joostjager

Can I see this somewhere? So that it becomes more clear where the benefits are of the stronger typing.

Thanks for the thoughtful review, Joost! I’m working through your points and will follow up with concrete changes soon. In the meantime, I’ve updated the PR description to explain the motivation a bit more, especially around the manual invoice request handling mentioned by Jeff. Would love your thoughts when you get a chance.

In the following commits we will introduce `fields` function
for other types as well, so to keep code DRY we convert the
function to a macro.
This commit reintroduces `VerifiedInvoiceRequest`, now parameterized by
`SigningPubkeyStrategy`.

The key motivation is to restrict which functions can be called on a
`VerifiedInvoiceRequest` based on its strategy type. This enables
compile-time guarantees — ensuring that an incorrect `InvoiceBuilder`
cannot be constructed for a given request, and misuses are caught early.
This change improves type safety and architectural clarity
by introducing dedicated `InvoiceBuilder` methods tied to
each variant of `VerifiedInvoiceRequestEnum`.

With this change, users are now required to match on the
enum variant before calling the corresponding builder method.
This pushes the responsibility of selecting the correct
builder to the user and ensures that invalid builder
usage is caught at compile time, rather than relying
on runtime checks.

The signing logic has also been moved from the builder
to the `ChannelManager`. This shift simplifies the
builder's role and aligns it with the rest of the API,
where builder methods return a configurable object that
can be extended before signing. The result is a more
consistent and predictable interface that separates
concerns cleanly and makes future maintenance easier.
To ensure correct Bolt12 payment flow behavior, the `amount_msats`
used for generating the `payment_hash`, `payment_secret`,
and payment path must remain consistent. Previously, these steps
could inadvertently diverge due to separate sources of `amount_msats`.

This commit refactors the interface to use a `get_payment_info` closure,
which captures the required variables and provides a single source of
truth for both payment info (payment_hash, payment_secret) and path
generation. This ensures consistency and eliminates subtle bugs
that could arise from mismatched amounts across the flow.
@joostjager
Copy link
Contributor

joostjager commented Aug 25, 2025

Just to be sure that I am looking at the benefits in the right location, I posted #3833 (comment)

@shaavan
Copy link
Member Author

shaavan commented Aug 25, 2025

Updated from pr3964.04 to pr3964.05 (diff):
Addressed @joostjager comments

Changes:

  1. Cleaned up commit history, by squashing the relevant commits.
  2. Introduce commit to remove a [rustfmt::skip]

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

HIgh-level ack, although I remain doubtful of the cost/benefit of this PR. A runtime check isn't the end of the world, especially if it isn't hard to hit the case during testing.

The end result isn't completely ideal either, taking some away from the benefit side of the scale:

  • Enum + generic feels a bit redundant, even though I understand the reason for doing it
  • Code duplication
  • Need for macros which we generally want to get rid of

Will leave full final review to @jkczyz

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.

5 participants