Skip to content

Commit 6458c6a

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 a26550b commit 6458c6a

File tree

4 files changed

+117
-2
lines changed

4 files changed

+117
-2
lines changed

lightning/src/events/mod.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ use core::time::Duration;
4646
use core::ops::Deref;
4747
use crate::sync::Arc;
4848

49+
#[cfg(async_payments)] use {
50+
crate::blinded_path::message::BlindedMessagePath,
51+
crate::offers::nonce::Nonce,
52+
crate::offers::static_invoice::StaticInvoice,
53+
};
54+
4955
#[allow(unused_imports)]
5056
use crate::prelude::*;
5157

@@ -1494,7 +1500,32 @@ pub enum Event {
14941500
/// The node id of the peer we just connected to, who advertises support for
14951501
/// onion messages.
14961502
peer_node_id: PublicKey,
1497-
}
1503+
},
1504+
/// We received a [`StaticInvoice`] from an async recipient that wants us to serve the invoice to
1505+
/// payers on their behalf when they are offline. This event will only be generated if we
1506+
/// previously created paths using [`ChannelManager::blinded_paths_for_async_recipient`] and
1507+
/// configured the recipient with them via [`UserConfig::paths_to_static_invoice_server`].
1508+
///
1509+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1510+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
1511+
#[cfg(async_payments)]
1512+
PersistStaticInvoice {
1513+
/// The invoice that should be persisted and later provided to payers when handling a future
1514+
/// `Event::StaticInvoiceRequested`.
1515+
invoice: StaticInvoice,
1516+
/// An identifier for the recipient, originally surfaced in
1517+
/// [`ChannelManager::blinded_paths_for_async_recipient`]. When an
1518+
/// `Event::StaticInvoiceRequested` comes in for this invoice, this id will be surfaced so the
1519+
/// persisted invoice can be retrieved from the database.
1520+
recipient_id_nonce: Nonce,
1521+
/// Once the [`StaticInvoice`] is persisted, [`ChannelManager::static_invoice_persisted`] should
1522+
/// be called with these paths to confirm to the recipient that their [`Offer`] is ready to be used
1523+
/// for async payments.
1524+
///
1525+
/// [`ChannelManager::static_invoice_persisted`]: crate::ln::channelmanager::ChannelManager::static_invoice_persisted
1526+
/// [`Offer`]: crate::offers::offer::Offer
1527+
invoice_persisted_paths: Vec<BlindedMessagePath>,
1528+
},
14981529
}
14991530

15001531
impl Writeable for Event {
@@ -1828,6 +1859,15 @@ impl Writeable for Event {
18281859
(8, former_temporary_channel_id, required),
18291860
});
18301861
},
1862+
#[cfg(async_payments)]
1863+
&Event::PersistStaticInvoice { ref invoice, ref recipient_id_nonce, ref invoice_persisted_paths } => {
1864+
45u8.write(writer)?;
1865+
write_tlv_fields!(writer, {
1866+
(0, invoice, required),
1867+
(2, recipient_id_nonce, required),
1868+
(4, invoice_persisted_paths, required),
1869+
});
1870+
},
18311871
// Note that, going forward, all new events must only write data inside of
18321872
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
18331873
// data via `write_tlv_fields`.
@@ -2341,6 +2381,22 @@ impl MaybeReadable for Event {
23412381
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
23422382
}))
23432383
},
2384+
#[cfg(async_payments)]
2385+
45u8 => {
2386+
let mut f = || {
2387+
_init_and_read_len_prefixed_tlv_fields!(reader, {
2388+
(0, invoice, required),
2389+
(2, recipient_id_nonce, required),
2390+
(4, invoice_persisted_paths, required),
2391+
});
2392+
Ok(Some(Event::PersistStaticInvoice {
2393+
invoice: _init_tlv_based_struct_field!(invoice, required),
2394+
recipient_id_nonce: _init_tlv_based_struct_field!(recipient_id_nonce, required),
2395+
invoice_persisted_paths: _init_tlv_based_struct_field!(invoice_persisted_paths, required),
2396+
}))
2397+
};
2398+
f()
2399+
},
23442400
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
23452401
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
23462402
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4928,6 +4928,22 @@ where
49284928
);
49294929
}
49304930

4931+
///
4932+
#[cfg(async_payments)]
4933+
pub fn static_invoice_persisted(&self, message_paths: Vec<BlindedMessagePath>) -> Result<(), ()> {
4934+
let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
4935+
4936+
let message = AsyncPaymentsMessage::StaticInvoicePersisted(StaticInvoicePersisted {});
4937+
for path in message_paths.into_iter().take(OFFERS_MESSAGE_REQUEST_LIMIT) {
4938+
let instructions = MessageSendInstructions::WithoutReplyPath {
4939+
destination: Destination::BlindedPath(path),
4940+
};
4941+
pending_async_payments_messages.push((message.clone(), instructions));
4942+
}
4943+
4944+
Ok(())
4945+
}
4946+
49314947
#[cfg(async_payments)]
49324948
fn initiate_async_payment(
49334949
&self, invoice: &StaticInvoice, payment_id: PaymentId
@@ -12856,7 +12872,31 @@ where
1285612872

1285712873
fn handle_serve_static_invoice(
1285812874
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
12859-
) {}
12875+
) {
12876+
#[cfg(async_payments)] {
12877+
let expanded_key = &self.inbound_payment_key;
12878+
let recipient_id_nonce = match _context {
12879+
AsyncPaymentsContext::ServeStaticInvoice {
12880+
recipient_id_nonce, nonce, hmac, path_absolute_expiry
12881+
} => {
12882+
if let Err(()) = signer::verify_serve_static_invoice_context(nonce, hmac, expanded_key) {
12883+
return
12884+
}
12885+
if self.duration_since_epoch() > path_absolute_expiry { return }
12886+
recipient_id_nonce
12887+
},
12888+
_ => return
12889+
};
12890+
12891+
PersistenceNotifierGuard::notify_on_drop(self);
12892+
let mut pending_events = self.pending_events.lock().unwrap();
12893+
pending_events.push_back((Event::PersistStaticInvoice {
12894+
invoice: _message.invoice,
12895+
recipient_id_nonce,
12896+
invoice_persisted_paths: _message.invoice_persisted_paths
12897+
}, None));
12898+
}
12899+
}
1286012900

1286112901
fn handle_static_invoice_persisted(
1286212902
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,

lightning/src/offers/signer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,17 @@ pub(crate) fn hmac_for_serve_static_invoice_context(
637637
Hmac::from_engine(hmac)
638638
}
639639

640+
#[cfg(async_payments)]
641+
pub(crate) fn verify_serve_static_invoice_context(
642+
nonce: Nonce, hmac: Hmac<Sha256>, expanded_key: &ExpandedKey,
643+
) -> Result<(), ()> {
644+
if hmac_for_serve_static_invoice_context(nonce, expanded_key) == hmac {
645+
Ok(())
646+
} else {
647+
Err(())
648+
}
649+
}
650+
640651
#[cfg(async_payments)]
641652
pub(crate) fn hmac_for_static_invoice_persisted_context(
642653
nonce: Nonce, expanded_key: &ExpandedKey,

lightning/src/offers/static_invoice.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ impl<'a> StaticInvoiceBuilder<'a> {
177177
invoice_builder_methods_test_common!(self, Self, self.invoice, Self, self, mut);
178178
}
179179

180+
impl PartialEq for StaticInvoice {
181+
fn eq(&self, other: &Self) -> bool {
182+
self.bytes.eq(&other.bytes)
183+
}
184+
}
185+
186+
impl Eq for StaticInvoice {}
187+
180188
/// A semantically valid [`StaticInvoice`] that hasn't been signed.
181189
pub struct UnsignedStaticInvoice {
182190
bytes: Vec<u8>,

0 commit comments

Comments
 (0)