Skip to content

Commit 0384462

Browse files
Add static invoice server messages and boilerplate
Because async recipients are not online to respond to invoice requests, the plan is for another node on the network that is always-online to serve static invoices on their behalf. The protocol is as follows: - Recipient is configured with blinded message paths to reach the static invoice server - On startup, recipient requests blinded message paths for inclusion in their offer from the static invoice server over the configured paths - Server replies with offer paths for the recipient - Recipient builds their offer using these paths and the corresponding static invoice and replies with the invoice - Server persists the invoice and confirms that they've persisted it, causing the recipient to cache the interactively built offer for use At pay-time, the payer sends an invoice request to the static invoice server, who replies with the static invoice after forwarding the invreq to the recipient (to give them a chance to provide a fresh invoice in case they're online). Here we add the requisite trait methods and onion messages to support this protocol.
1 parent ea87d80 commit 0384462

File tree

6 files changed

+293
-6
lines changed

6 files changed

+293
-6
lines changed

fuzz/src/onion_message.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use lightning::ln::peer_handler::IgnoringMessageHandler;
1515
use lightning::ln::script::ShutdownScript;
1616
use lightning::offers::invoice::UnsignedBolt12Invoice;
1717
use lightning::onion_message::async_payments::{
18-
AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
18+
AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc,
19+
ServeStaticInvoice, StaticInvoicePersisted,
1920
};
2021
use lightning::onion_message::messenger::{
2122
CustomOnionMessageHandler, Destination, MessageRouter, MessageSendInstructions,
@@ -124,6 +125,30 @@ impl OffersMessageHandler for TestOffersMessageHandler {
124125
struct TestAsyncPaymentsMessageHandler {}
125126

126127
impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
128+
fn handle_offer_paths_request(
129+
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext,
130+
responder: Option<Responder>,
131+
) -> Option<(OfferPaths, ResponseInstruction)> {
132+
let responder = match responder {
133+
Some(resp) => resp,
134+
None => return None,
135+
};
136+
Some((OfferPaths { paths: Vec::new() }, responder.respond()))
137+
}
138+
fn handle_offer_paths(
139+
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
140+
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
141+
None
142+
}
143+
fn handle_serve_static_invoice(
144+
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
145+
_responder: Option<Responder>,
146+
) {
147+
}
148+
fn handle_static_invoice_persisted(
149+
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
150+
) {
151+
}
127152
fn handle_held_htlc_available(
128153
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
129154
responder: Option<Responder>,

lightning/src/ln/channelmanager.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ use crate::offers::offer::{Offer, OfferBuilder};
7474
use crate::offers::parse::Bolt12SemanticError;
7575
use crate::offers::refund::{Refund, RefundBuilder};
7676
use crate::offers::signer;
77-
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
77+
use crate::onion_message::async_payments::{
78+
AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths,
79+
OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted
80+
};
7881
use crate::onion_message::dns_resolution::HumanReadableName;
7982
use crate::onion_message::messenger::{MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
8083
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
@@ -12405,6 +12408,28 @@ where
1240512408
MR::Target: MessageRouter,
1240612409
L::Target: Logger,
1240712410
{
12411+
fn handle_offer_paths_request(
12412+
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext,
12413+
_responder: Option<Responder>,
12414+
) -> Option<(OfferPaths, ResponseInstruction)> {
12415+
None
12416+
}
12417+
12418+
fn handle_offer_paths(
12419+
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
12420+
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
12421+
None
12422+
}
12423+
12424+
fn handle_serve_static_invoice(
12425+
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
12426+
_responder: Option<Responder>,
12427+
) {}
12428+
12429+
fn handle_static_invoice_persisted(
12430+
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
12431+
) {}
12432+
1240812433
fn handle_held_htlc_available(
1240912434
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
1241012435
_responder: Option<Responder>

lightning/src/ln/peer_handler.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::util::ser::{VecWriter, Writeable, Writer};
2929
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE};
3030
use crate::ln::wire;
3131
use crate::ln::wire::{Encode, Type};
32-
use crate::onion_message::async_payments::{AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc};
32+
use crate::onion_message::async_payments::{AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, ReleaseHeldHtlc, StaticInvoicePersisted};
3333
use crate::onion_message::dns_resolution::{DNSResolverMessageHandler, DNSResolverMessage, DNSSECProof, DNSSECQuery};
3434
use crate::onion_message::messenger::{CustomOnionMessageHandler, Responder, ResponseInstruction, MessageSendInstructions};
3535
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
@@ -148,6 +148,23 @@ impl OffersMessageHandler for IgnoringMessageHandler {
148148
}
149149
}
150150
impl AsyncPaymentsMessageHandler for IgnoringMessageHandler {
151+
fn handle_offer_paths_request(
152+
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext, _responder: Option<Responder>,
153+
) -> Option<(OfferPaths, ResponseInstruction)> {
154+
None
155+
}
156+
fn handle_offer_paths(
157+
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
158+
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
159+
None
160+
}
161+
fn handle_serve_static_invoice(
162+
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
163+
_responder: Option<Responder>,
164+
) {}
165+
fn handle_static_invoice_persisted(
166+
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
167+
) {}
151168
fn handle_held_htlc_available(
152169
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
153170
_responder: Option<Responder>,

lightning/src/onion_message/async_payments.rs

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,67 @@
99

1010
//! Message handling for async payments.
1111
12-
use crate::blinded_path::message::AsyncPaymentsContext;
12+
use crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath};
1313
use crate::io;
1414
use crate::ln::msgs::DecodeError;
15+
use crate::offers::static_invoice::StaticInvoice;
1516
use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
1617
use crate::onion_message::packet::OnionMessageContents;
1718
use crate::prelude::*;
1819
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
1920

21+
use core::time::Duration;
22+
2023
// TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4.
24+
// TODO: document static invoice server onion message payload types in a bLIP.
25+
const OFFER_PATHS_REQ_TLV_TYPE: u64 = 65538;
26+
const OFFER_PATHS_TLV_TYPE: u64 = 65540;
27+
const SERVE_INVOICE_TLV_TYPE: u64 = 65542;
28+
const INVOICE_PERSISTED_TLV_TYPE: u64 = 65544;
2129
const HELD_HTLC_AVAILABLE_TLV_TYPE: u64 = 72;
2230
const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74;
2331

2432
/// A handler for an [`OnionMessage`] containing an async payments message as its payload.
2533
///
2634
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
2735
pub trait AsyncPaymentsMessageHandler {
36+
/// Handle an [`OfferPathsRequest`] message. If the message was sent over paths that we previously
37+
/// provided to an async recipient via [`UserConfig::paths_to_static_invoice_server`], an
38+
/// [`OfferPaths`] message should be returned.
39+
///
40+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
41+
fn handle_offer_paths_request(
42+
&self, message: OfferPathsRequest, context: AsyncPaymentsContext,
43+
responder: Option<Responder>,
44+
) -> Option<(OfferPaths, ResponseInstruction)>;
45+
46+
/// Handle an [`OfferPaths`] message. If this is in response to an [`OfferPathsRequest`] that
47+
/// we previously sent as an async recipient, we should build an [`Offer`] containing the
48+
/// included [`OfferPaths::paths`] and a corresponding [`StaticInvoice`], and reply with
49+
/// [`ServeStaticInvoice`].
50+
///
51+
/// [`Offer`]: crate::offers::offer::Offer
52+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
53+
fn handle_offer_paths(
54+
&self, message: OfferPaths, context: AsyncPaymentsContext, responder: Option<Responder>,
55+
) -> Option<(ServeStaticInvoice, ResponseInstruction)>;
56+
57+
/// Handle a [`ServeStaticInvoice`] message. If this is in response to an [`OfferPaths`] message
58+
/// we previously sent, a [`StaticInvoicePersisted`] message should be sent once the message is
59+
/// handled.
60+
fn handle_serve_static_invoice(
61+
&self, message: ServeStaticInvoice, context: AsyncPaymentsContext,
62+
responder: Option<Responder>,
63+
);
64+
65+
/// Handle a [`StaticInvoicePersisted`] message. If this is in response to a
66+
/// [`ServeStaticInvoice`] message we previously sent as an async recipient, then the offer we
67+
/// generated on receipt of a previous [`OfferPaths`] message is now ready to be used for async
68+
/// payments.
69+
fn handle_static_invoice_persisted(
70+
&self, message: StaticInvoicePersisted, context: AsyncPaymentsContext,
71+
);
72+
2873
/// Handle a [`HeldHtlcAvailable`] message. A [`ReleaseHeldHtlc`] should be returned to release
2974
/// the held funds.
3075
fn handle_held_htlc_available(
@@ -50,6 +95,25 @@ pub trait AsyncPaymentsMessageHandler {
5095
/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
5196
#[derive(Clone, Debug)]
5297
pub enum AsyncPaymentsMessage {
98+
/// A request for [`BlindedMessagePath`]s from a static invoice server.
99+
OfferPathsRequest(OfferPathsRequest),
100+
101+
/// [`BlindedMessagePath`]s to be included in an async recipient's [`Offer::paths`], sent in
102+
/// response to an [`OfferPathsRequest`].
103+
///
104+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
105+
OfferPaths(OfferPaths),
106+
107+
/// A request to serve a [`StaticInvoice`] on behalf of an async recipient.
108+
ServeStaticInvoice(ServeStaticInvoice),
109+
110+
/// Confirms that a [`StaticInvoice`] was persisted by a static invoice server and the
111+
/// corresponding [`Offer`] is ready to be used to receive async payments. Sent in response to a
112+
/// [`ServeStaticInvoice`] message.
113+
///
114+
/// [`Offer`]: crate::offers::offer::Offer
115+
StaticInvoicePersisted(StaticInvoicePersisted),
116+
53117
/// An HTLC is being held upstream for the often-offline recipient, to be released via
54118
/// [`ReleaseHeldHtlc`].
55119
HeldHtlcAvailable(HeldHtlcAvailable),
@@ -58,6 +122,51 @@ pub enum AsyncPaymentsMessage {
58122
ReleaseHeldHtlc(ReleaseHeldHtlc),
59123
}
60124

125+
/// A request for [`BlindedMessagePath`]s from a static invoice server. These paths will be used
126+
/// in the async recipient's [`Offer::paths`], so payers can request [`StaticInvoice`]s from the
127+
/// static invoice server.
128+
///
129+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
130+
#[derive(Clone, Debug)]
131+
pub struct OfferPathsRequest {}
132+
133+
/// [`BlindedMessagePath`]s to be included in an async recipient's [`Offer::paths`], sent in
134+
/// response to an [`OfferPathsRequest`].
135+
///
136+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
137+
#[derive(Clone, Debug)]
138+
pub struct OfferPaths {
139+
/// The paths that should be included in the async recipient's [`Offer::paths`].
140+
///
141+
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
142+
pub paths: Vec<BlindedMessagePath>,
143+
/// The time as duration since the Unix epoch at which the [`Self::paths`] expire.
144+
pub paths_absolute_expiry: Option<Duration>,
145+
}
146+
147+
/// Indicates that a [`StaticInvoice`] should be provided by a static invoice server in response to
148+
/// [`InvoiceRequest`]s from payers behalf of an async recipient.
149+
///
150+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
151+
#[derive(Clone, Debug)]
152+
pub struct ServeStaticInvoice {
153+
/// The invoice that should be served by the static invoice server. Once this invoice has been
154+
/// persisted, the [`Responder`] accompanying this message should be used to send
155+
/// [`StaticInvoicePersisted`] to the recipient to confirm that the offer corresponding to the
156+
/// invoice is ready to receive async payments.
157+
pub invoice: StaticInvoice,
158+
// TODO: include blinded paths to forward the invreq to the async recipient
159+
// pub invoice_request_paths: Vec<BlindedMessagePath>,
160+
}
161+
162+
/// Confirms that a [`StaticInvoice`] was persisted by a static invoice server and the
163+
/// corresponding [`Offer`] is ready to be used to receive async payments. Sent in response to a
164+
/// [`ServeStaticInvoice`] message.
165+
///
166+
/// [`Offer`]: crate::offers::offer::Offer
167+
#[derive(Clone, Debug)]
168+
pub struct StaticInvoicePersisted {}
169+
61170
/// An HTLC destined for the recipient of this message is being held upstream. The reply path
62171
/// accompanying this onion message should be used to send a [`ReleaseHeldHtlc`] response, which
63172
/// will cause the upstream HTLC to be released.
@@ -68,6 +177,34 @@ pub struct HeldHtlcAvailable {}
68177
#[derive(Clone, Debug)]
69178
pub struct ReleaseHeldHtlc {}
70179

180+
impl OnionMessageContents for OfferPaths {
181+
fn tlv_type(&self) -> u64 {
182+
OFFER_PATHS_TLV_TYPE
183+
}
184+
#[cfg(c_bindings)]
185+
fn msg_type(&self) -> String {
186+
"Offer Paths".to_string()
187+
}
188+
#[cfg(not(c_bindings))]
189+
fn msg_type(&self) -> &'static str {
190+
"Offer Paths"
191+
}
192+
}
193+
194+
impl OnionMessageContents for ServeStaticInvoice {
195+
fn tlv_type(&self) -> u64 {
196+
SERVE_INVOICE_TLV_TYPE
197+
}
198+
#[cfg(c_bindings)]
199+
fn msg_type(&self) -> String {
200+
"Serve Static Invoice".to_string()
201+
}
202+
#[cfg(not(c_bindings))]
203+
fn msg_type(&self) -> &'static str {
204+
"Serve Static Invoice"
205+
}
206+
}
207+
71208
impl OnionMessageContents for ReleaseHeldHtlc {
72209
fn tlv_type(&self) -> u64 {
73210
RELEASE_HELD_HTLC_TLV_TYPE
@@ -82,6 +219,19 @@ impl OnionMessageContents for ReleaseHeldHtlc {
82219
}
83220
}
84221

222+
impl_writeable_tlv_based!(OfferPathsRequest, {});
223+
224+
impl_writeable_tlv_based!(OfferPaths, {
225+
(0, paths, required_vec),
226+
(2, paths_absolute_expiry, option),
227+
});
228+
229+
impl_writeable_tlv_based!(ServeStaticInvoice, {
230+
(0, invoice, required),
231+
});
232+
233+
impl_writeable_tlv_based!(StaticInvoicePersisted, {});
234+
85235
impl_writeable_tlv_based!(HeldHtlcAvailable, {});
86236

87237
impl_writeable_tlv_based!(ReleaseHeldHtlc, {});
@@ -90,7 +240,12 @@ impl AsyncPaymentsMessage {
90240
/// Returns whether `tlv_type` corresponds to a TLV record for async payment messages.
91241
pub fn is_known_type(tlv_type: u64) -> bool {
92242
match tlv_type {
93-
HELD_HTLC_AVAILABLE_TLV_TYPE | RELEASE_HELD_HTLC_TLV_TYPE => true,
243+
OFFER_PATHS_REQ_TLV_TYPE
244+
| OFFER_PATHS_TLV_TYPE
245+
| SERVE_INVOICE_TLV_TYPE
246+
| INVOICE_PERSISTED_TLV_TYPE
247+
| HELD_HTLC_AVAILABLE_TLV_TYPE
248+
| RELEASE_HELD_HTLC_TLV_TYPE => true,
94249
_ => false,
95250
}
96251
}
@@ -99,20 +254,32 @@ impl AsyncPaymentsMessage {
99254
impl OnionMessageContents for AsyncPaymentsMessage {
100255
fn tlv_type(&self) -> u64 {
101256
match self {
257+
Self::OfferPathsRequest(_) => OFFER_PATHS_REQ_TLV_TYPE,
258+
Self::OfferPaths(msg) => msg.tlv_type(),
259+
Self::ServeStaticInvoice(msg) => msg.tlv_type(),
260+
Self::StaticInvoicePersisted(_) => INVOICE_PERSISTED_TLV_TYPE,
102261
Self::HeldHtlcAvailable(_) => HELD_HTLC_AVAILABLE_TLV_TYPE,
103262
Self::ReleaseHeldHtlc(msg) => msg.tlv_type(),
104263
}
105264
}
106265
#[cfg(c_bindings)]
107266
fn msg_type(&self) -> String {
108267
match &self {
268+
Self::OfferPathsRequest(_) => "Offer Paths Request".to_string(),
269+
Self::OfferPaths(msg) => msg.msg_type(),
270+
Self::ServeStaticInvoice(msg) => msg.msg_type(),
271+
Self::StaticInvoicePersisted(_) => "Static Invoice Persisted".to_string(),
109272
Self::HeldHtlcAvailable(_) => "Held HTLC Available".to_string(),
110273
Self::ReleaseHeldHtlc(msg) => msg.msg_type(),
111274
}
112275
}
113276
#[cfg(not(c_bindings))]
114277
fn msg_type(&self) -> &'static str {
115278
match &self {
279+
Self::OfferPathsRequest(_) => "Offer Paths Request",
280+
Self::OfferPaths(msg) => msg.msg_type(),
281+
Self::ServeStaticInvoice(msg) => msg.msg_type(),
282+
Self::StaticInvoicePersisted(_) => "Static Invoice Persisted",
116283
Self::HeldHtlcAvailable(_) => "Held HTLC Available",
117284
Self::ReleaseHeldHtlc(msg) => msg.msg_type(),
118285
}
@@ -122,6 +289,10 @@ impl OnionMessageContents for AsyncPaymentsMessage {
122289
impl Writeable for AsyncPaymentsMessage {
123290
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
124291
match self {
292+
Self::OfferPathsRequest(message) => message.write(w),
293+
Self::OfferPaths(message) => message.write(w),
294+
Self::ServeStaticInvoice(message) => message.write(w),
295+
Self::StaticInvoicePersisted(message) => message.write(w),
125296
Self::HeldHtlcAvailable(message) => message.write(w),
126297
Self::ReleaseHeldHtlc(message) => message.write(w),
127298
}
@@ -131,6 +302,10 @@ impl Writeable for AsyncPaymentsMessage {
131302
impl ReadableArgs<u64> for AsyncPaymentsMessage {
132303
fn read<R: io::Read>(r: &mut R, tlv_type: u64) -> Result<Self, DecodeError> {
133304
match tlv_type {
305+
OFFER_PATHS_REQ_TLV_TYPE => Ok(Self::OfferPathsRequest(Readable::read(r)?)),
306+
OFFER_PATHS_TLV_TYPE => Ok(Self::OfferPaths(Readable::read(r)?)),
307+
SERVE_INVOICE_TLV_TYPE => Ok(Self::ServeStaticInvoice(Readable::read(r)?)),
308+
INVOICE_PERSISTED_TLV_TYPE => Ok(Self::StaticInvoicePersisted(Readable::read(r)?)),
134309
HELD_HTLC_AVAILABLE_TLV_TYPE => Ok(Self::HeldHtlcAvailable(Readable::read(r)?)),
135310
RELEASE_HELD_HTLC_TLV_TYPE => Ok(Self::ReleaseHeldHtlc(Readable::read(r)?)),
136311
_ => Err(DecodeError::InvalidValue),

0 commit comments

Comments
 (0)