-
Notifications
You must be signed in to change notification settings - Fork 415
Static invoice server #3628
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?
Static invoice server #3628
Changes from all commits
638b41b
2723602
3cf17b9
c428502
41f413b
3874612
98d2356
89d3757
5bc8015
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; | |
|
||
use core::mem; | ||
use core::ops::Deref; | ||
use core::time::Duration; | ||
|
||
/// A blinded path to be used for sending or receiving a message, hiding the identity of the | ||
/// recipient. | ||
|
@@ -342,6 +343,43 @@ pub enum OffersContext { | |
/// [`Offer`]: crate::offers::offer::Offer | ||
nonce: Nonce, | ||
}, | ||
/// Context used by a [`BlindedMessagePath`] within the [`Offer`] of an async recipient. | ||
/// | ||
/// This variant is received by the static invoice server when handling an [`InvoiceRequest`] on | ||
/// behalf of said async recipient. | ||
/// | ||
/// [`Offer`]: crate::offers::offer::Offer | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
StaticInvoiceRequested { | ||
/// An identifier for the async recipient for whom we as a static invoice server are serving | ||
/// [`StaticInvoice`]s. Used paired with the | ||
/// [`OffersContext::StaticInvoiceRequested::invoice_id`] when looking up a corresponding | ||
/// [`StaticInvoice`] to return to the payer if the recipient is offline. This id was previously | ||
/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::recipient_id`]. | ||
/// | ||
/// Also useful for rate limiting the number of [`InvoiceRequest`]s we will respond to on | ||
/// recipient's behalf. | ||
/// | ||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
recipient_id: Vec<u8>, | ||
|
||
/// A random unique identifier for a specific [`StaticInvoice`] that the recipient previously | ||
/// requested be served on their behalf. Useful when paired with the | ||
/// [`OffersContext::StaticInvoiceRequested::recipient_id`] to pull that specific invoice from | ||
/// the database when payers send an [`InvoiceRequest`]. This id was previously | ||
/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::invoice_id`]. | ||
/// | ||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
invoice_id: u128, | ||
|
||
/// The time as duration since the Unix epoch at which this path expires and messages sent over | ||
/// it should be ignored. | ||
/// | ||
/// Useful to timeout async recipients that are no longer supported as clients. | ||
path_absolute_expiry: Duration, | ||
}, | ||
/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an | ||
/// [`InvoiceRequest`]. | ||
/// | ||
|
@@ -405,6 +443,24 @@ pub enum OffersContext { | |
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage | ||
#[derive(Clone, Debug)] | ||
pub enum AsyncPaymentsContext { | ||
/// Context used by a [`BlindedMessagePath`] provided out-of-band to an async recipient, where the | ||
/// context is provided back to the static invoice server in corresponding [`OfferPathsRequest`]s. | ||
/// | ||
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest | ||
OfferPathsRequest { | ||
/// An identifier for the async recipient that is requesting blinded paths to include in their | ||
/// [`Offer::paths`]. This ID will be surfaced when the async recipient eventually sends a | ||
/// corresponding [`ServeStaticInvoice`] message, and can be used to rate limit the recipient. | ||
/// | ||
/// [`Offer::paths`]: crate::offers::offer::Offer::paths | ||
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice | ||
recipient_id: Vec<u8>, | ||
/// The time as duration since the Unix epoch at which this path expires and messages sent over | ||
/// it should be ignored. | ||
/// | ||
/// Useful to timeout async recipients that are no longer supported as clients. | ||
path_absolute_expiry: core::time::Duration, | ||
joostjager marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this can be unset, would it make sense for it to be an |
||
}, | ||
/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us as an async | ||
/// recipient in corresponding [`OfferPaths`] messages from the static invoice server. | ||
/// | ||
|
@@ -420,6 +476,41 @@ pub enum AsyncPaymentsContext { | |
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths | ||
path_absolute_expiry: core::time::Duration, | ||
}, | ||
/// Context used by a reply path to an [`OfferPaths`] message, provided back to us as the static | ||
/// invoice server in corresponding [`ServeStaticInvoice`] messages. | ||
/// | ||
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths | ||
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice | ||
ServeStaticInvoice { | ||
/// An identifier for the async recipient that is requesting that a [`StaticInvoice`] be served | ||
/// on their behalf. | ||
/// | ||
/// Useful when surfaced alongside the below `invoice_id` when payers send an | ||
/// [`InvoiceRequest`], to pull the specific static invoice from the database. | ||
/// | ||
/// Also useful to rate limit the invoices being persisted on behalf of a particular recipient. | ||
/// | ||
/// This id will be provided back to us as the static invoice server via | ||
/// [`OffersContext::StaticInvoiceRequested::recipient_id`]. | ||
/// | ||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
recipient_id: Vec<u8>, | ||
/// A random identifier for the specific [`StaticInvoice`] that the recipient is requesting be | ||
/// served on their behalf. Useful when surfaced alongside the above `recipient_id` when payers | ||
/// send an [`InvoiceRequest`], to pull the specific static invoice from the database. This id | ||
/// will be provided back to us as the static invoice server via | ||
/// [`OffersContext::StaticInvoiceRequested::invoice_id`]. | ||
/// | ||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
invoice_id: u128, | ||
/// The time as duration since the Unix epoch at which this path expires and messages sent over | ||
/// it should be ignored. | ||
/// | ||
/// Useful to timeout async recipients that are no longer supported as clients. | ||
path_absolute_expiry: core::time::Duration, | ||
}, | ||
/// Context used by a reply path to a [`ServeStaticInvoice`] message, provided back to us in | ||
/// corresponding [`StaticInvoicePersisted`] messages. | ||
/// | ||
|
@@ -508,6 +599,11 @@ impl_writeable_tlv_based_enum!(OffersContext, | |
(1, nonce, required), | ||
(2, hmac, required) | ||
}, | ||
(3, StaticInvoiceRequested) => { | ||
(0, recipient_id, required), | ||
(2, invoice_id, required), | ||
(4, path_absolute_expiry, required), | ||
}, | ||
); | ||
|
||
impl_writeable_tlv_based_enum!(AsyncPaymentsContext, | ||
|
@@ -528,6 +624,15 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, | |
(0, offer_id, required), | ||
(2, path_absolute_expiry, required), | ||
}, | ||
(4, OfferPathsRequest) => { | ||
(0, recipient_id, required), | ||
(2, path_absolute_expiry, required), | ||
}, | ||
(5, ServeStaticInvoice) => { | ||
(0, recipient_id, required), | ||
(2, invoice_id, required), | ||
(4, path_absolute_expiry, required), | ||
}, | ||
); | ||
|
||
/// Contains a simple nonce for use in a blinded path's context. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1582,6 +1582,78 @@ pub enum Event { | |
/// onion messages. | ||
peer_node_id: PublicKey, | ||
}, | ||
/// As a static invoice server, we received a [`StaticInvoice`] from an async recipient that wants | ||
/// us to serve the invoice to payers on their behalf when they are offline. This event will only | ||
/// be generated if we previously created paths using | ||
/// [`ChannelManager::blinded_paths_for_async_recipient`] and the recipient was configured with | ||
/// them via [`ChannelManager::set_paths_to_static_invoice_server`]. | ||
/// | ||
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient | ||
/// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server | ||
#[cfg(async_payments)] | ||
PersistStaticInvoice { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume there has been prior discussion around this that I'm unware of, but it strikes me as a bit odd to manage persistence via the event queue, if we have Pushing/popping the event will result in at least two additional There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is similar to the approach that was taken in #2973, though unfortunately I can't find a link to any discussion that took place landing on that approach... But, either way, I think the server needs to verify the
Hopefully @joostjager and I can get rid of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm, but even then we'd repersist the event queue twice, no? Granted, it would be much less overhead, but we'd still eat the IO latency once or twice before actually getting to persist the data (incurring more IO latency). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The link to I do see some intersection with #3778. The events as proposed in this PR would allow for async persistence, but it is yet again another way to do it alongside a native async interface and the InProgress/callback mechanism in chain monitor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm... Thoughts on a separate PR that would allow us to process the event queue and skip manager persistence entirely for for certain events? With these events and several others I can think of, no manager persistence is needed and an event being dropped/failed to handle/redundantly handled should not be a problem. I guess I'm just not sure how we can properly rate limit without knowing anything about the underlying database and its limitations. It feels like an instance where finer-grained control might be desirable and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming almost all users would use ldk-node, it doesn't matter so much in which layer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree with this sentiment: generally I don't have too much of a strong opinion on how we do it, but (ab)using the persisted event queue for persistence calls seems like unnecessary overhead, IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned, I think we can avoid persisting the event queue when these events are present specifically, as well as existing non-critical events that are in the same boat like But, a separate trait is another approach that has been discussed in the past that we can definitely revisit, will start an offline discussion. Edit: discussed offline, all the options have downsides so going with the event approach for now. A separate trait would mean blocking other onion messenger operations on persistence. |
||
/// The invoice that should be persisted and later provided to payers when handling a future | ||
/// [`Event::StaticInvoiceRequested`]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, seems they're linked but the fixup went into the wrong commit. |
||
invoice: StaticInvoice, | ||
/// Useful for the recipient to replace a specific invoice stored by us as the static invoice | ||
/// server. | ||
/// | ||
/// When this invoice and its metadata are persisted, this slot number should be included so if | ||
/// we receive another [`Event::PersistStaticInvoice`] containing the same slot number we can | ||
/// swap the existing invoice out for the new one. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So what would this mean concretely for our persistence scheme? For example, how would we build our |
||
invoice_slot: u16, | ||
/// An identifier for the recipient, originally provided to | ||
/// [`ChannelManager::blinded_paths_for_async_recipient`]. | ||
/// | ||
/// When an [`Event::StaticInvoiceRequested`] comes in for the invoice, this id will be surfaced | ||
/// and can be used alongside the `invoice_id` to retrieve the invoice from the database. | ||
recipient_id: Vec<u8>, | ||
/// A random identifier for the invoice. When an [`Event::StaticInvoiceRequested`] comes in for | ||
/// the invoice, this id will be surfaced and can be used alongside the `recipient_id` to | ||
/// retrieve the invoice from the database. | ||
/// | ||
/// Note that this id will remain the same for all invoice updates corresponding to a particular | ||
/// offer that the recipient has cached. | ||
invoice_id: u128, | ||
/// Once the [`StaticInvoice`], `invoice_slot` and `invoice_id` are persisted, | ||
/// [`ChannelManager::static_invoice_persisted`] should be called with this responder to confirm | ||
/// to the recipient that their [`Offer`] is ready to be used for async payments. | ||
/// | ||
/// [`ChannelManager::static_invoice_persisted`]: crate::ln::channelmanager::ChannelManager::static_invoice_persisted | ||
/// [`Offer`]: crate::offers::offer::Offer | ||
invoice_persisted_path: Responder, | ||
}, | ||
/// As a static invoice server, we received an [`InvoiceRequest`] on behalf of an often-offline | ||
/// recipient for whom we are serving [`StaticInvoice`]s. | ||
/// | ||
/// This event will only be generated if we previously created paths using | ||
/// [`ChannelManager::blinded_paths_for_async_recipient`] and the recipient was configured with | ||
/// them via [`ChannelManager::set_paths_to_static_invoice_server`]. | ||
/// | ||
/// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that | ||
/// matches the below `recipient_id` and `invoice_id`, that invoice should be retrieved now | ||
/// and forwarded to the payer via [`ChannelManager::send_static_invoice`]. | ||
/// | ||
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient | ||
/// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice | ||
#[cfg(async_payments)] | ||
StaticInvoiceRequested { | ||
/// An identifier for the recipient previously surfaced in | ||
/// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_id` to | ||
/// retrieve the [`StaticInvoice`] requested by the payer. | ||
recipient_id: Vec<u8>, | ||
/// A random identifier for the invoice being requested, previously surfaced in | ||
/// [`Event::PersistStaticInvoice::invoice_id`]. Useful when paired with the `recipient_id` to | ||
/// retrieve the [`StaticInvoice`] requested by the payer. | ||
invoice_id: u128, | ||
/// The path over which the [`StaticInvoice`] will be sent to the payer, which should be | ||
/// provided to [`ChannelManager::send_static_invoice`] along with the invoice. | ||
/// | ||
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice | ||
reply_path: Responder, | ||
}, | ||
} | ||
|
||
impl Writeable for Event { | ||
|
@@ -2012,6 +2084,17 @@ impl Writeable for Event { | |
(8, former_temporary_channel_id, required), | ||
}); | ||
}, | ||
#[cfg(async_payments)] | ||
&Event::PersistStaticInvoice { .. } => { | ||
45u8.write(writer)?; | ||
joostjager marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// No need to write these events because we can just restart the static invoice negotiation | ||
// on startup. | ||
}, | ||
#[cfg(async_payments)] | ||
&Event::StaticInvoiceRequested { .. } => { | ||
47u8.write(writer)?; | ||
// Never write StaticInvoiceRequested events as buffered onion messages aren't serialized. | ||
}, | ||
// Note that, going forward, all new events must only write data inside of | ||
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write | ||
// data via `write_tlv_fields`. | ||
|
@@ -2583,6 +2666,12 @@ impl MaybeReadable for Event { | |
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(), | ||
})) | ||
}, | ||
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events. | ||
#[cfg(async_payments)] | ||
45u8 => Ok(None), | ||
// Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events. | ||
#[cfg(async_payments)] | ||
47u8 => Ok(None), | ||
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. | ||
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt | ||
// reads. | ||
|
Uh oh!
There was an error while loading. Please reload this page.