Skip to content

Commit f8859f7

Browse files
Static invoice server: persist invoices once built
As part of serving static invoices to payers on behalf of often-offline recipients, the recipient will send us the final static invoice once it's done being interactively built. We will then persist this invoice and confirm to them that the corresponding offer is ready to be used for async payments. Surface an event once the invoice is received and expose an API to tell the recipient that it's ready for payments.
1 parent c4e6bd2 commit f8859f7

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

lightning/src/events/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,44 @@ 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 is persisted, this slot number should be included so if we receive another
1602+
/// [`Event::PersistStaticInvoice`] containing the same slot number we can swap the existing
1603+
/// 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 unique 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+
invoice_id: u128,
1615+
/// Once the [`StaticInvoice`], `invoice_slot` and `invoice_id` are persisted,
1616+
/// [`ChannelManager::static_invoice_persisted`] should be called with this responder to confirm
1617+
/// to the recipient that their [`Offer`] is ready to be used for async payments.
1618+
///
1619+
/// [`ChannelManager::static_invoice_persisted`]: crate::ln::channelmanager::ChannelManager::static_invoice_persisted
1620+
/// [`Offer`]: crate::offers::offer::Offer
1621+
invoice_persisted_path: Responder,
1622+
},
15851623
}
15861624

15871625
impl Writeable for Event {
@@ -2012,6 +2050,12 @@ impl Writeable for Event {
20122050
(8, former_temporary_channel_id, required),
20132051
});
20142052
},
2053+
#[cfg(async_payments)]
2054+
&Event::PersistStaticInvoice { .. } => {
2055+
45u8.write(writer)?;
2056+
// No need to write these events because we can just restart the static invoice negotiation
2057+
// on startup.
2058+
},
20152059
// Note that, going forward, all new events must only write data inside of
20162060
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20172061
// data via `write_tlv_fields`.
@@ -2583,6 +2627,9 @@ impl MaybeReadable for Event {
25832627
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
25842628
}))
25852629
},
2630+
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
2631+
#[cfg(async_payments)]
2632+
45u8 => Ok(None),
25862633
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
25872634
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
25882635
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5272,6 +5272,13 @@ where
52725272
}
52735273
}
52745274

5275+
/// Should be called after handling an [`Event::PersistStaticInvoice`], where the `Responder`
5276+
/// comes from [`Event::PersistStaticInvoice::invoice_persisted_path`].
5277+
#[cfg(async_payments)]
5278+
pub fn static_invoice_persisted(&self, invoice_persisted_path: Responder) {
5279+
self.flow.static_invoice_persisted(invoice_persisted_path);
5280+
}
5281+
52755282
#[cfg(async_payments)]
52765283
fn initiate_async_payment(
52775284
&self, invoice: &StaticInvoice, payment_id: PaymentId,
@@ -13535,6 +13542,31 @@ where
1353513542
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
1353613543
_responder: Option<Responder>,
1353713544
) {
13545+
#[cfg(async_payments)]
13546+
{
13547+
let responder = match _responder {
13548+
Some(resp) => resp,
13549+
None => return,
13550+
};
13551+
13552+
let (recipient_id, invoice_id) =
13553+
match self.flow.verify_serve_static_invoice_message(&_message, _context) {
13554+
Ok(nonce) => nonce,
13555+
Err(()) => return,
13556+
};
13557+
13558+
let mut pending_events = self.pending_events.lock().unwrap();
13559+
pending_events.push_back((
13560+
Event::PersistStaticInvoice {
13561+
invoice: _message.invoice,
13562+
invoice_slot: _message.invoice_slot,
13563+
recipient_id,
13564+
invoice_id,
13565+
invoice_persisted_path: responder,
13566+
},
13567+
None,
13568+
));
13569+
}
1353813570
}
1353913571

1354013572
fn handle_static_invoice_persisted(

lightning/src/offers/flow.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use {
6868
crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder},
6969
crate::onion_message::async_payments::{
7070
HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice,
71+
StaticInvoicePersisted,
7172
},
7273
crate::onion_message::messenger::Responder,
7374
};
@@ -1526,6 +1527,49 @@ where
15261527
Ok((invoice, forward_invoice_request_path))
15271528
}
15281529

1530+
/// Verifies an incoming [`ServeStaticInvoice`] onion message from an often-offline recipient who
1531+
/// wants us as a static invoice server to serve the [`ServeStaticInvoice::invoice`] to payers on
1532+
/// their behalf.
1533+
///
1534+
/// On success, returns (recipient_id, invoice_id) for use in persisting and later retrieving
1535+
/// the static invoice from the database.
1536+
///
1537+
/// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice
1538+
#[cfg(async_payments)]
1539+
pub fn verify_serve_static_invoice_message(
1540+
&self, message: &ServeStaticInvoice, context: AsyncPaymentsContext,
1541+
) -> Result<(Vec<u8>, u128), ()> {
1542+
if message.invoice.is_expired_no_std(self.duration_since_epoch()) {
1543+
return Err(());
1544+
}
1545+
match context {
1546+
AsyncPaymentsContext::ServeStaticInvoice {
1547+
recipient_id,
1548+
invoice_id,
1549+
path_absolute_expiry,
1550+
} => {
1551+
if self.duration_since_epoch() > path_absolute_expiry {
1552+
return Err(());
1553+
}
1554+
1555+
return Ok((recipient_id, invoice_id));
1556+
},
1557+
_ => return Err(()),
1558+
};
1559+
}
1560+
1561+
/// Indicates that a [`ServeStaticInvoice::invoice`] has been persisted and is ready to be served
1562+
/// to payers on behalf of an often-offline recipient. This method must be called after persisting
1563+
/// a [`StaticInvoice`] to confirm to the recipient that their corresponding [`Offer`] is ready to
1564+
/// receive async payments.
1565+
#[cfg(async_payments)]
1566+
pub fn static_invoice_persisted(&self, responder: Responder) {
1567+
let mut pending_async_payments_messages =
1568+
self.pending_async_payments_messages.lock().unwrap();
1569+
let message = AsyncPaymentsMessage::StaticInvoicePersisted(StaticInvoicePersisted {});
1570+
pending_async_payments_messages.push((message, responder.respond().into_instructions()));
1571+
}
1572+
15291573
/// Handles an incoming [`StaticInvoicePersisted`] onion message from the static invoice server.
15301574
/// Returns a bool indicating whether the async receive offer cache needs to be re-persisted.
15311575
///

0 commit comments

Comments
 (0)