Skip to content

Commit 96b4070

Browse files
Static invoice server: forward static invoices to payers
Here we implement serving static invoices to payers on behalf of often-offline recipients. These recipients previously encoded blinded paths terminating at our node in their offer, so we receive invoice requests on their behalf. Handle those inbound invreqs by retrieving a static invoice we previously persisted on behalf of the payee, and forward it to the payer as a reply to their invreq.
1 parent f8859f7 commit 96b4070

File tree

3 files changed

+119
-7
lines changed

3 files changed

+119
-7
lines changed

lightning/src/events/mod.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,7 @@ pub enum Event {
15931593
#[cfg(async_payments)]
15941594
PersistStaticInvoice {
15951595
/// The invoice that should be persisted and later provided to payers when handling a future
1596-
/// `Event::StaticInvoiceRequested`.
1596+
/// [`Event::StaticInvoiceRequested`].
15971597
invoice: StaticInvoice,
15981598
/// Useful for the recipient to replace a specific invoice stored by us as the static invoice
15991599
/// server.
@@ -1605,10 +1605,10 @@ pub enum Event {
16051605
/// An identifier for the recipient, originally provided to
16061606
/// [`ChannelManager::blinded_paths_for_async_recipient`].
16071607
///
1608-
/// When an `Event::StaticInvoiceRequested` comes in for the invoice, this id will be surfaced
1608+
/// When an [`Event::StaticInvoiceRequested`] comes in for the invoice, this id will be surfaced
16091609
/// and can be used alongside the `invoice_id` to retrieve the invoice from the database.
16101610
recipient_id: Vec<u8>,
1611-
/// A unique identifier for the invoice. When an `Event::StaticInvoiceRequested` comes in for
1611+
/// A unique identifier for the invoice. When an [`Event::StaticInvoiceRequested`] comes in for
16121612
/// the invoice, this id will be surfaced and can be used alongside the `recipient_id` to
16131613
/// retrieve the invoice from the database.
16141614
invoice_id: u128,
@@ -1620,6 +1620,37 @@ pub enum Event {
16201620
/// [`Offer`]: crate::offers::offer::Offer
16211621
invoice_persisted_path: Responder,
16221622
},
1623+
/// As a static invoice server, we received an [`InvoiceRequest`] on behalf of an often-offline
1624+
/// recipient for whom we are serving [`StaticInvoice`]s.
1625+
///
1626+
/// This event will only be generated if we previously created paths using
1627+
/// [`ChannelManager::blinded_paths_for_async_recipient`] and the recipient was configured with
1628+
/// them via [`ChannelManager::set_paths_to_static_invoice_server`].
1629+
///
1630+
/// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that
1631+
/// matches the below `recipient_id` and `invoice_id`, that invoice should be retrieved now
1632+
/// and forwarded to the payer via [`ChannelManager::send_static_invoice`].
1633+
///
1634+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1635+
/// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server
1636+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1637+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1638+
#[cfg(async_payments)]
1639+
StaticInvoiceRequested {
1640+
/// An identifier for the recipient previously surfaced in
1641+
/// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_id` to
1642+
/// retrieve the [`StaticInvoice`] requested by the payer.
1643+
recipient_id: Vec<u8>,
1644+
/// A random unique identifier for the invoice being requested, previously surfaced in
1645+
/// [`Event::PersistStaticInvoice::invoice_id`]. Useful when paired with the `recipient_id` to
1646+
/// retrieve the [`StaticInvoice`] requested by the payer.
1647+
invoice_id: u128,
1648+
/// The path over which the [`StaticInvoice`] will be sent to the payer, which should be
1649+
/// provided to [`ChannelManager::send_static_invoice`] along with the invoice.
1650+
///
1651+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1652+
reply_path: Responder,
1653+
},
16231654
}
16241655

16251656
impl Writeable for Event {
@@ -2056,6 +2087,11 @@ impl Writeable for Event {
20562087
// No need to write these events because we can just restart the static invoice negotiation
20572088
// on startup.
20582089
},
2090+
#[cfg(async_payments)]
2091+
&Event::StaticInvoiceRequested { .. } => {
2092+
47u8.write(writer)?;
2093+
// Never write StaticInvoiceRequested events as buffered onion messages aren't serialized.
2094+
},
20592095
// Note that, going forward, all new events must only write data inside of
20602096
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20612097
// data via `write_tlv_fields`.
@@ -2630,6 +2666,9 @@ impl MaybeReadable for Event {
26302666
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
26312667
#[cfg(async_payments)]
26322668
45u8 => Ok(None),
2669+
// Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events.
2670+
#[cfg(async_payments)]
2671+
47u8 => Ok(None),
26332672
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
26342673
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
26352674
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ use crate::ln::outbound_payment::{
8787
};
8888
use crate::ln::types::ChannelId;
8989
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
90-
use crate::offers::flow::OffersMessageFlow;
90+
use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
9191
use crate::offers::invoice::{
9292
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
9393
};
@@ -5279,6 +5279,14 @@ where
52795279
self.flow.static_invoice_persisted(invoice_persisted_path);
52805280
}
52815281

5282+
/// Forwards a [`StaticInvoice`] in response to an [`Event::StaticInvoiceRequested`].
5283+
#[cfg(async_payments)]
5284+
pub fn send_static_invoice(
5285+
&self, invoice: StaticInvoice, responder: Responder,
5286+
) -> Result<(), Bolt12SemanticError> {
5287+
self.flow.enqueue_static_invoice(invoice, responder)
5288+
}
5289+
52825290
#[cfg(async_payments)]
52835291
fn initiate_async_payment(
52845292
&self, invoice: &StaticInvoice, payment_id: PaymentId,
@@ -13355,7 +13363,17 @@ where
1335513363
};
1335613364

1335713365
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
13358-
Ok(invoice_request) => invoice_request,
13366+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
13367+
Ok(InvreqResponseInstructions::SendStaticInvoice {
13368+
recipient_id: _recipient_id, invoice_id: _invoice_id
13369+
}) => {
13370+
#[cfg(async_payments)]
13371+
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
13372+
recipient_id: _recipient_id, invoice_id: _invoice_id, reply_path: responder
13373+
}, None));
13374+
13375+
return None
13376+
},
1335913377
Err(_) => return None,
1336013378
};
1336113379

lightning/src/offers/flow.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,26 @@ fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
415415
});
416416
}
417417

418+
/// Instructions for how to respond to an `InvoiceRequest`.
419+
pub enum InvreqResponseInstructions {
420+
/// We are the recipient of this payment, and a [`Bolt12Invoice`] should be sent in response to
421+
/// the invoice request since it is now verified.
422+
SendInvoice(VerifiedInvoiceRequest),
423+
/// We are a static invoice server and should respond to this invoice request by retrieving the
424+
/// [`StaticInvoice`] corresponding to the `recipient_id` and `invoice_id` and calling
425+
/// `OffersMessageFlow::enqueue_static_invoice`.
426+
///
427+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
428+
SendStaticInvoice {
429+
/// An identifier for the async recipient for whom we are serving [`StaticInvoice`]s.
430+
///
431+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
432+
recipient_id: Vec<u8>,
433+
/// An identifier for the specific invoice being requested by the payer.
434+
invoice_id: u128,
435+
},
436+
}
437+
418438
impl<MR: Deref> OffersMessageFlow<MR>
419439
where
420440
MR::Target: MessageRouter,
@@ -432,13 +452,28 @@ where
432452
/// - The verification process (via recipient context data or metadata) fails.
433453
pub fn verify_invoice_request(
434454
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
435-
) -> Result<VerifiedInvoiceRequest, ()> {
455+
) -> Result<InvreqResponseInstructions, ()> {
436456
let secp_ctx = &self.secp_ctx;
437457
let expanded_key = &self.inbound_payment_key;
438458

439459
let nonce = match context {
440460
None if invoice_request.metadata().is_some() => None,
441461
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
462+
#[cfg(async_payments)]
463+
Some(OffersContext::StaticInvoiceRequested {
464+
recipient_id,
465+
invoice_id,
466+
path_absolute_expiry,
467+
}) => {
468+
if path_absolute_expiry < self.duration_since_epoch() {
469+
return Err(());
470+
}
471+
472+
return Ok(InvreqResponseInstructions::SendStaticInvoice {
473+
recipient_id,
474+
invoice_id,
475+
});
476+
},
442477
_ => return Err(()),
443478
};
444479

@@ -449,7 +484,7 @@ where
449484
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
450485
}?;
451486

452-
Ok(invoice_request)
487+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
453488
}
454489

455490
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1059,6 +1094,26 @@ where
10591094
Ok(())
10601095
}
10611096

1097+
/// Forwards a [`StaticInvoice`] over the provided `responder`.
1098+
#[cfg(async_payments)]
1099+
pub(crate) fn enqueue_static_invoice(
1100+
&self, invoice: StaticInvoice, responder: Responder,
1101+
) -> Result<(), Bolt12SemanticError> {
1102+
let duration_since_epoch = self.duration_since_epoch();
1103+
if invoice.is_expired_no_std(duration_since_epoch) {
1104+
return Err(Bolt12SemanticError::AlreadyExpired);
1105+
}
1106+
if invoice.is_offer_expired_no_std(duration_since_epoch) {
1107+
return Err(Bolt12SemanticError::AlreadyExpired);
1108+
}
1109+
1110+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
1111+
let message = OffersMessage::StaticInvoice(invoice);
1112+
pending_offers_messages.push((message, responder.respond().into_instructions()));
1113+
1114+
Ok(())
1115+
}
1116+
10621117
/// Enqueues `held_htlc_available` onion messages to be sent to the payee via the reply paths
10631118
/// contained within the provided [`StaticInvoice`].
10641119
///

0 commit comments

Comments
 (0)