Skip to content

Commit cdf2908

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 1ae3151 commit cdf2908

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 random identifier for the invoice. When an `Event::StaticInvoiceRequested` comes in for
1611+
/// A random 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
///
@@ -1623,6 +1623,37 @@ pub enum Event {
16231623
/// [`Offer`]: crate::offers::offer::Offer
16241624
invoice_persisted_path: Responder,
16251625
},
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+
},
16261657
}
16271658

16281659
impl Writeable for Event {
@@ -2059,6 +2090,11 @@ impl Writeable for Event {
20592090
// No need to write these events because we can just restart the static invoice negotiation
20602091
// on startup.
20612092
},
2093+
#[cfg(async_payments)]
2094+
&Event::StaticInvoiceRequested { .. } => {
2095+
47u8.write(writer)?;
2096+
// Never write StaticInvoiceRequested events as buffered onion messages aren't serialized.
2097+
},
20622098
// Note that, going forward, all new events must only write data inside of
20632099
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20642100
// data via `write_tlv_fields`.
@@ -2633,6 +2669,9 @@ impl MaybeReadable for Event {
26332669
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
26342670
#[cfg(async_payments)]
26352671
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),
26362675
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
26372676
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
26382677
// 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
@@ -426,6 +426,26 @@ fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
426426
});
427427
}
428428

429+
/// Instructions for how to respond to an `InvoiceRequest`.
430+
pub enum InvreqResponseInstructions {
431+
/// We are the recipient of this payment, and a [`Bolt12Invoice`] should be sent in response to
432+
/// the invoice request since it is now verified.
433+
SendInvoice(VerifiedInvoiceRequest),
434+
/// We are a static invoice server and should respond to this invoice request by retrieving the
435+
/// [`StaticInvoice`] corresponding to the `recipient_id` and `invoice_id` and calling
436+
/// `OffersMessageFlow::enqueue_static_invoice`.
437+
///
438+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
439+
SendStaticInvoice {
440+
/// An identifier for the async recipient for whom we are serving [`StaticInvoice`]s.
441+
///
442+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
443+
recipient_id: Vec<u8>,
444+
/// An identifier for the specific invoice being requested by the payer.
445+
invoice_id: u128,
446+
},
447+
}
448+
429449
impl<MR: Deref> OffersMessageFlow<MR>
430450
where
431451
MR::Target: MessageRouter,
@@ -443,13 +463,28 @@ where
443463
/// - The verification process (via recipient context data or metadata) fails.
444464
pub fn verify_invoice_request(
445465
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
446-
) -> Result<VerifiedInvoiceRequest, ()> {
466+
) -> Result<InvreqResponseInstructions, ()> {
447467
let secp_ctx = &self.secp_ctx;
448468
let expanded_key = &self.inbound_payment_key;
449469

450470
let nonce = match context {
451471
None if invoice_request.metadata().is_some() => None,
452472
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
473+
#[cfg(async_payments)]
474+
Some(OffersContext::StaticInvoiceRequested {
475+
recipient_id,
476+
invoice_id,
477+
path_absolute_expiry,
478+
}) => {
479+
if path_absolute_expiry < self.duration_since_epoch() {
480+
return Err(());
481+
}
482+
483+
return Ok(InvreqResponseInstructions::SendStaticInvoice {
484+
recipient_id,
485+
invoice_id,
486+
});
487+
},
453488
_ => return Err(()),
454489
};
455490

@@ -460,7 +495,7 @@ where
460495
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
461496
}?;
462497

463-
Ok(invoice_request)
498+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
464499
}
465500

466501
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1070,6 +1105,26 @@ where
10701105
Ok(())
10711106
}
10721107

1108+
/// Forwards a [`StaticInvoice`] over the provided `responder`.
1109+
#[cfg(async_payments)]
1110+
pub(crate) fn enqueue_static_invoice(
1111+
&self, invoice: StaticInvoice, responder: Responder,
1112+
) -> Result<(), Bolt12SemanticError> {
1113+
let duration_since_epoch = self.duration_since_epoch();
1114+
if invoice.is_expired_no_std(duration_since_epoch) {
1115+
return Err(Bolt12SemanticError::AlreadyExpired);
1116+
}
1117+
if invoice.is_offer_expired_no_std(duration_since_epoch) {
1118+
return Err(Bolt12SemanticError::AlreadyExpired);
1119+
}
1120+
1121+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
1122+
let message = OffersMessage::StaticInvoice(invoice);
1123+
pending_offers_messages.push((message, responder.respond().into_instructions()));
1124+
1125+
Ok(())
1126+
}
1127+
10731128
/// Enqueues `held_htlc_available` onion messages to be sent to the payee via the reply paths
10741129
/// contained within the provided [`StaticInvoice`].
10751130
///

0 commit comments

Comments
 (0)