-
Notifications
You must be signed in to change notification settings - Fork 382
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
Client-side of static invoice server #3618
base: main
Are you sure you want to change the base?
Client-side of static invoice server #3618
Conversation
We need to add a Vec to the config as part of async receive support, see upcoming commits. The vec will contain blinded paths to reach an always-online node that will serve static invoices on our behalf. None of those things implement Copy so remove support for the trait.
The new utility will be used in upcoming commits as part of supporting static invoice server.
We're about to add more onion message types, so this pre-emptively DRYs the code to extract the expected message context taken from the blinded path.
We need to include static invoices in the public API as part of the onion messages we're adding for static invoice server support. Utilities to create these static invoices and other parts of the async receive API will remain cfg-gated for now. Generally, we can't totally avoid exposing half baked async receive support in the public API because OnionMessenger is parameterized by an async payments message handler, which can't be cfg-gated easily.
In future commits, as part of being an async recipient, we will interactively build an offer and static invoice with an always-online node that will serve static invoices on our behalf. Once the offer is built and the static invoice is confirmed as persisted by the server, we will use this struct to cache the offer in ChannelManager.
As part of being an async recipient, we need to support interactively building an offer and static invoice with an always-online node that will serve static invoices on our behalf. Add a config field containing blinded message paths that async recipients can use to request blinded paths that will be included in their offer. Payers will forward invoice requests over the paths returned by the server, and receive a static invoice in response if the recipient is offline.
Because async recipients are not online to respond to invoice requests, the plan is for another node on the network that is always-online to serve static invoices on their behalf. The protocol is as follows: - Recipient is configured with blinded message paths to reach the static invoice server - On startup, recipient requests blinded message paths for inclusion in their offer from the static invoice server over the configured paths - Server replies with offer paths for the recipient - Recipient builds their offer using these paths and the corresponding static invoice and replies with the invoice - Server persists the invoice and confirms that they've persisted it, causing the recipient to cache the interactively built offer for use At pay-time, the payer sends an invoice request to the static invoice server, who replies with the static invoice after forwarding the invreq to the recipient (to give them a chance to provide a fresh invoice in case they're online). Here we add the requisite trait methods and onion messages to support this protocol.
As an async recipient, we need to interactively build a static invoice that an always-online node will serve to payers on our behalf. At the start of this process, we send a request for paths to include in our offer to the always-online node on startup and refresh the cached offer when it expires.
As an async recipient, we need to interactively build a static invoice that an always-online node will serve to payers on our behalf. As part of this process, the static invoice server sends us blinded message paths to include in our offer so they'll receive invoice requests from senders trying to pay us while we're offline. On receipt of these paths, create an offer and static invoice and send the invoice back to the server so they can provide the invoice to payers.
As an async recipient, we need to interactively build a static invoice that an always-online node will serve on our behalf. Once this invoice is built and persisted by the static invoice server, they will send us a confirmation onion message. At this time, cache the corresponding offer and mark it as ready to receive async payments.
Over the past several commits we've implemented interactively building an async receive offer with a static invoice server that will service invoice requests on our behalf as an async recipient. Here we add an API to retrieve this resulting offer so we can receive payments when we're offline.
Will go through the commits in a bit more detail before taking this out of draft, but conceptual feedback or feedback on the protocol itself is welcome, or the way the code is organized overall. It does add a significant amount of code to |
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.
Hmmm, I wonder if we shouldn't allow the client to cache N offers rather than only 1. I worry a bit about the privacy implications of having One Offer that gets reused across different contexts.
/// | ||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
offer: 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.
Not sure I understand why this has to be here explicitly? Doesn't the StaticInvoice
contain the Offer
('s fields) in 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.
As implemented, the async recipient will never cache the StaticInvoice
themselves. The recipient creates the invoice, sends it to the server, and includes the Offer
in the reply path to cache once the invoice is confirmed as persisted. I think the google doc is out of date here, sorry.
This made sense to me while implementing because we don't want to cache the offer until it's confirmed as persisted by the server and I couldn't think of a reason why the client would need to have to the static invoice.
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.
Oh, sorry, I misread which context this was (don't we need a context for sending the static invoice to the server? I guess that's a separate PR), this makes sense.
I think that makes sense, so they would interactively build and cache a few and then randomly(?) return one of them on It seems reasonable to save for follow-up although I could adapt the |
// Expire the offer at the same time as the static invoice so we automatically refresh both | ||
// at the same time. | ||
let offer_and_invoice_absolute_expiry = Duration::from_secs(core::cmp::min( | ||
offer_paths_absolute_expiry.as_secs(), | ||
duration_since_epoch.saturating_add(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY).as_secs() | ||
)); |
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.
One thing I want to address eventually (but maybe not in this PR) is that right now we cap the expiry of our offer/static invoice at 2 weeks, which doesn't work well for the "offer in Twitter bio" use case. Probably we can add something to UserConfig
for this, and expose a method for users to proactively rotate their offer if it never expires?
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 wonder if for that we shouldn't try to come up with a scheme to allow the offer to last longer than the static invoice? I mean ideally an offer lasts at least a few years, but it kinda could cause you just care about the storage server being reliable, you don't care much about the static invoice.
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.
That makes sense. We could include another set of long-lived paths in the OfferPaths
message that allows the recipient to refresh their invoice later while keeping the same offer [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.
I mean maybe the OffersPath
paths should just be super long-lived? I don't see a strong reason to have some concept of long-lived paths?
Yea, I dunno what to do for the fetch'er, maybe we just expose the whole list?
Makes sense, tho I imagine it would be a rather trivial diff, no? |
As part of being an async recipient, we need to interactively build an offer and static invoice with an always-online node that will serve static invoices on our behalf in response to invoice requests from payers.
While users could build this invoice manually, the plan is for LDK to automatically build it for them using onion messages. See this doc for more details on the protocol. Here we implement the client side of the linked protocol.
See lightning/bolts#1149 for more information on async payments.