Skip to content

Commit 389b6d1

Browse files
Merge pull request #3628 from valentinewallace/2025-02-static-invoice-server
Static invoice server
2 parents 79fc513 + 9288153 commit 389b6d1

File tree

12 files changed

+1753
-209
lines changed

12 files changed

+1753
-209
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use bitcoin::hashes::sha256::Hash as Sha256;
3535

3636
use core::mem;
3737
use core::ops::Deref;
38+
use core::time::Duration;
3839

3940
/// A blinded path to be used for sending or receiving a message, hiding the identity of the
4041
/// recipient.
@@ -342,6 +343,43 @@ pub enum OffersContext {
342343
/// [`Offer`]: crate::offers::offer::Offer
343344
nonce: Nonce,
344345
},
346+
/// Context used by a [`BlindedMessagePath`] within the [`Offer`] of an async recipient.
347+
///
348+
/// This variant is received by the static invoice server when handling an [`InvoiceRequest`] on
349+
/// behalf of said async recipient.
350+
///
351+
/// [`Offer`]: crate::offers::offer::Offer
352+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
353+
StaticInvoiceRequested {
354+
/// An identifier for the async recipient for whom we as a static invoice server are serving
355+
/// [`StaticInvoice`]s. Used paired with the
356+
/// [`OffersContext::StaticInvoiceRequested::invoice_id`] when looking up a corresponding
357+
/// [`StaticInvoice`] to return to the payer if the recipient is offline. This id was previously
358+
/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::recipient_id`].
359+
///
360+
/// Also useful for rate limiting the number of [`InvoiceRequest`]s we will respond to on
361+
/// recipient's behalf.
362+
///
363+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
364+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
365+
recipient_id: Vec<u8>,
366+
367+
/// A random unique identifier for a specific [`StaticInvoice`] that the recipient previously
368+
/// requested be served on their behalf. Useful when paired with the
369+
/// [`OffersContext::StaticInvoiceRequested::recipient_id`] to pull that specific invoice from
370+
/// the database when payers send an [`InvoiceRequest`]. This id was previously
371+
/// provided via [`AsyncPaymentsContext::ServeStaticInvoice::invoice_id`].
372+
///
373+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
374+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
375+
invoice_id: u128,
376+
377+
/// The time as duration since the Unix epoch at which this path expires and messages sent over
378+
/// it should be ignored.
379+
///
380+
/// Useful to timeout async recipients that are no longer supported as clients.
381+
path_absolute_expiry: Duration,
382+
},
345383
/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an
346384
/// [`InvoiceRequest`].
347385
///
@@ -405,6 +443,24 @@ pub enum OffersContext {
405443
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
406444
#[derive(Clone, Debug)]
407445
pub enum AsyncPaymentsContext {
446+
/// Context used by a [`BlindedMessagePath`] provided out-of-band to an async recipient, where the
447+
/// context is provided back to the static invoice server in corresponding [`OfferPathsRequest`]s.
448+
///
449+
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
450+
OfferPathsRequest {
451+
/// An identifier for the async recipient that is requesting blinded paths to include in their
452+
/// [`Offer::paths`]. This ID will be surfaced when the async recipient eventually sends a
453+
/// corresponding [`ServeStaticInvoice`] message, and can be used to rate limit the recipient.
454+
///
455+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
456+
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
457+
recipient_id: Vec<u8>,
458+
/// An optional field indicating the time as duration since the Unix epoch at which this path
459+
/// expires and messages sent over it should be ignored.
460+
///
461+
/// Useful to timeout async recipients that are no longer supported as clients.
462+
path_absolute_expiry: Option<core::time::Duration>,
463+
},
408464
/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us as an async
409465
/// recipient in corresponding [`OfferPaths`] messages from the static invoice server.
410466
///
@@ -420,6 +476,41 @@ pub enum AsyncPaymentsContext {
420476
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
421477
path_absolute_expiry: core::time::Duration,
422478
},
479+
/// Context used by a reply path to an [`OfferPaths`] message, provided back to us as the static
480+
/// invoice server in corresponding [`ServeStaticInvoice`] messages.
481+
///
482+
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
483+
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
484+
ServeStaticInvoice {
485+
/// An identifier for the async recipient that is requesting that a [`StaticInvoice`] be served
486+
/// on their behalf.
487+
///
488+
/// Useful when surfaced alongside the below `invoice_id` when payers send an
489+
/// [`InvoiceRequest`], to pull the specific static invoice from the database.
490+
///
491+
/// Also useful to rate limit the invoices being persisted on behalf of a particular recipient.
492+
///
493+
/// This id will be provided back to us as the static invoice server via
494+
/// [`OffersContext::StaticInvoiceRequested::recipient_id`].
495+
///
496+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
497+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
498+
recipient_id: Vec<u8>,
499+
/// A random identifier for the specific [`StaticInvoice`] that the recipient is requesting be
500+
/// served on their behalf. Useful when surfaced alongside the above `recipient_id` when payers
501+
/// send an [`InvoiceRequest`], to pull the specific static invoice from the database. This id
502+
/// will be provided back to us as the static invoice server via
503+
/// [`OffersContext::StaticInvoiceRequested::invoice_id`].
504+
///
505+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
506+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
507+
invoice_id: u128,
508+
/// The time as duration since the Unix epoch at which this path expires and messages sent over
509+
/// it should be ignored.
510+
///
511+
/// Useful to timeout async recipients that are no longer supported as clients.
512+
path_absolute_expiry: core::time::Duration,
513+
},
423514
/// Context used by a reply path to a [`ServeStaticInvoice`] message, provided back to us in
424515
/// corresponding [`StaticInvoicePersisted`] messages.
425516
///
@@ -508,6 +599,11 @@ impl_writeable_tlv_based_enum!(OffersContext,
508599
(1, nonce, required),
509600
(2, hmac, required)
510601
},
602+
(3, StaticInvoiceRequested) => {
603+
(0, recipient_id, required),
604+
(2, invoice_id, required),
605+
(4, path_absolute_expiry, required),
606+
},
511607
);
512608

513609
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
@@ -528,6 +624,15 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
528624
(0, offer_id, required),
529625
(2, path_absolute_expiry, required),
530626
},
627+
(4, OfferPathsRequest) => {
628+
(0, recipient_id, required),
629+
(2, path_absolute_expiry, option),
630+
},
631+
(5, ServeStaticInvoice) => {
632+
(0, recipient_id, required),
633+
(2, invoice_id, required),
634+
(4, path_absolute_expiry, required),
635+
},
531636
);
532637

533638
/// Contains a simple nonce for use in a blinded path's context.

lightning/src/events/mod.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,78 @@ pub enum Event {
15821582
/// onion messages.
15831583
peer_node_id: PublicKey,
15841584
},
1585+
/// As a static invoice server, we received a [`StaticInvoice`] from an async recipient that wants
1586+
/// us to serve the invoice to payers on their behalf when they are offline. This event will only
1587+
/// be generated if we previously created paths using
1588+
/// [`ChannelManager::blinded_paths_for_async_recipient`] and the recipient was configured with
1589+
/// them via [`ChannelManager::set_paths_to_static_invoice_server`].
1590+
///
1591+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1592+
/// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server
1593+
#[cfg(async_payments)]
1594+
PersistStaticInvoice {
1595+
/// The invoice that should be persisted and later provided to payers when handling a future
1596+
/// [`Event::StaticInvoiceRequested`].
1597+
invoice: StaticInvoice,
1598+
/// Useful for the recipient to replace a specific invoice stored by us as the static invoice
1599+
/// server.
1600+
///
1601+
/// When this invoice and its metadata are persisted, this slot number should be included so if
1602+
/// we receive another [`Event::PersistStaticInvoice`] containing the same slot number we can
1603+
/// swap the existing invoice out for the new one.
1604+
invoice_slot: u16,
1605+
/// An identifier for the recipient, originally provided to
1606+
/// [`ChannelManager::blinded_paths_for_async_recipient`].
1607+
///
1608+
/// When an [`Event::StaticInvoiceRequested`] comes in for the invoice, this id will be surfaced
1609+
/// and can be used alongside the `invoice_id` to retrieve the invoice from the database.
1610+
recipient_id: Vec<u8>,
1611+
/// A random identifier for the invoice. When an [`Event::StaticInvoiceRequested`] comes in for
1612+
/// the invoice, this id will be surfaced and can be used alongside the `recipient_id` to
1613+
/// retrieve the invoice from the database.
1614+
///
1615+
/// Note that this id will remain the same for all invoice updates corresponding to a particular
1616+
/// offer that the recipient has cached.
1617+
invoice_id: u128,
1618+
/// Once the [`StaticInvoice`], `invoice_slot` and `invoice_id` are persisted,
1619+
/// [`ChannelManager::static_invoice_persisted`] should be called with this responder to confirm
1620+
/// to the recipient that their [`Offer`] is ready to be used for async payments.
1621+
///
1622+
/// [`ChannelManager::static_invoice_persisted`]: crate::ln::channelmanager::ChannelManager::static_invoice_persisted
1623+
/// [`Offer`]: crate::offers::offer::Offer
1624+
invoice_persisted_path: Responder,
1625+
},
1626+
/// As a static invoice server, we received an [`InvoiceRequest`] on behalf of an often-offline
1627+
/// recipient for whom we are serving [`StaticInvoice`]s.
1628+
///
1629+
/// This event will only be generated if we previously created paths using
1630+
/// [`ChannelManager::blinded_paths_for_async_recipient`] and the recipient was configured with
1631+
/// them via [`ChannelManager::set_paths_to_static_invoice_server`].
1632+
///
1633+
/// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that
1634+
/// matches the below `recipient_id` and `invoice_id`, that invoice should be retrieved now
1635+
/// and forwarded to the payer via [`ChannelManager::send_static_invoice`].
1636+
///
1637+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1638+
/// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server
1639+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1640+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1641+
#[cfg(async_payments)]
1642+
StaticInvoiceRequested {
1643+
/// An identifier for the recipient previously surfaced in
1644+
/// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_id` to
1645+
/// retrieve the [`StaticInvoice`] requested by the payer.
1646+
recipient_id: Vec<u8>,
1647+
/// A random identifier for the invoice being requested, previously surfaced in
1648+
/// [`Event::PersistStaticInvoice::invoice_id`]. Useful when paired with the `recipient_id` to
1649+
/// retrieve the [`StaticInvoice`] requested by the payer.
1650+
invoice_id: u128,
1651+
/// The path over which the [`StaticInvoice`] will be sent to the payer, which should be
1652+
/// provided to [`ChannelManager::send_static_invoice`] along with the invoice.
1653+
///
1654+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1655+
reply_path: Responder,
1656+
},
15851657
}
15861658

15871659
impl Writeable for Event {
@@ -2012,6 +2084,17 @@ impl Writeable for Event {
20122084
(8, former_temporary_channel_id, required),
20132085
});
20142086
},
2087+
#[cfg(async_payments)]
2088+
&Event::PersistStaticInvoice { .. } => {
2089+
45u8.write(writer)?;
2090+
// No need to write these events because we can just restart the static invoice negotiation
2091+
// on startup.
2092+
},
2093+
#[cfg(async_payments)]
2094+
&Event::StaticInvoiceRequested { .. } => {
2095+
47u8.write(writer)?;
2096+
// Never write StaticInvoiceRequested events as buffered onion messages aren't serialized.
2097+
},
20152098
// Note that, going forward, all new events must only write data inside of
20162099
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20172100
// data via `write_tlv_fields`.
@@ -2583,6 +2666,12 @@ impl MaybeReadable for Event {
25832666
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
25842667
}))
25852668
},
2669+
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
2670+
#[cfg(async_payments)]
2671+
45u8 => Ok(None),
2672+
// Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events.
2673+
#[cfg(async_payments)]
2674+
47u8 => Ok(None),
25862675
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
25872676
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
25882677
// reads.

0 commit comments

Comments
 (0)