lightning/onion_message/async_payments.rs
1// This file is Copyright its original authors, visible in version control
2// history.
3//
4// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7// You may not use this file except in accordance with one or both of these
8// licenses.
9
10//! Message handling for async payments.
11
12use crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath};
13use crate::io;
14use crate::ln::msgs::DecodeError;
15use crate::offers::static_invoice::StaticInvoice;
16use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
17use crate::onion_message::packet::OnionMessageContents;
18use crate::prelude::*;
19use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
20
21// TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4.
22const OFFER_PATHS_REQ_TLV_TYPE: u64 = 75540;
23const OFFER_PATHS_TLV_TYPE: u64 = 75542;
24const SERVE_INVOICE_TLV_TYPE: u64 = 75544;
25const INVOICE_PERSISTED_TLV_TYPE: u64 = 75546;
26const HELD_HTLC_AVAILABLE_TLV_TYPE: u64 = 72;
27const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74;
28
29/// A handler for an [`OnionMessage`] containing an async payments message as its payload.
30///
31/// The [`AsyncPaymentsContext`]s provided to each method was authenticated by the
32/// [`OnionMessenger`] as coming from a blinded path that we created.
33///
34/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
35/// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger
36pub trait AsyncPaymentsMessageHandler {
37 /// Handle an [`OfferPathsRequest`] message. If we are a static invoice server and the message was
38 /// sent over paths that we previously provided to an async recipient, an [`OfferPaths`] message
39 /// should be returned.
40 fn handle_offer_paths_request(
41 &self, message: OfferPathsRequest, context: AsyncPaymentsContext,
42 responder: Option<Responder>,
43 ) -> Option<(OfferPaths, ResponseInstruction)>;
44
45 /// Handle an [`OfferPaths`] message. If this is in response to an [`OfferPathsRequest`] that
46 /// we previously sent as an async recipient, we should build an [`Offer`] containing the
47 /// included [`OfferPaths::paths`] and a corresponding [`StaticInvoice`], and reply with
48 /// [`ServeStaticInvoice`].
49 ///
50 /// [`Offer`]: crate::offers::offer::Offer
51 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
52 fn handle_offer_paths(
53 &self, message: OfferPaths, context: AsyncPaymentsContext, responder: Option<Responder>,
54 ) -> Option<(ServeStaticInvoice, ResponseInstruction)>;
55
56 /// Handle a [`ServeStaticInvoice`] message. If this is in response to an [`OfferPaths`] message
57 /// we previously sent as a static invoice server, a [`StaticInvoicePersisted`] message should be
58 /// sent once the message is handled.
59 fn handle_serve_static_invoice(
60 &self, message: ServeStaticInvoice, context: AsyncPaymentsContext,
61 responder: Option<Responder>,
62 );
63
64 /// Handle a [`StaticInvoicePersisted`] message. If this is in response to a
65 /// [`ServeStaticInvoice`] message we previously sent as an async recipient, then the offer we
66 /// generated on receipt of a previous [`OfferPaths`] message is now ready to be used for async
67 /// payments.
68 fn handle_static_invoice_persisted(
69 &self, message: StaticInvoicePersisted, context: AsyncPaymentsContext,
70 );
71
72 /// Handle a [`HeldHtlcAvailable`] message. A [`ReleaseHeldHtlc`] should be returned to release
73 /// the held funds.
74 fn handle_held_htlc_available(
75 &self, message: HeldHtlcAvailable, context: AsyncPaymentsContext,
76 responder: Option<Responder>,
77 ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)>;
78
79 /// Handle a [`ReleaseHeldHtlc`] message. If authentication of the message succeeds, an HTLC
80 /// should be released to the corresponding payee.
81 fn handle_release_held_htlc(&self, message: ReleaseHeldHtlc, context: AsyncPaymentsContext);
82
83 /// Release any [`AsyncPaymentsMessage`]s that need to be sent.
84 ///
85 /// Typically, this is used for messages initiating an async payment flow rather than in response
86 /// to another message.
87 fn release_pending_messages(&self) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> {
88 vec![]
89 }
90}
91
92/// Possible async payment messages sent and received via an [`OnionMessage`].
93///
94/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
95#[derive(Clone, Debug)]
96pub enum AsyncPaymentsMessage {
97 /// A request from an async recipient for [`BlindedMessagePath`]s, sent to a static invoice
98 /// server.
99 OfferPathsRequest(OfferPathsRequest),
100
101 /// [`BlindedMessagePath`]s to be included in an async recipient's [`Offer::paths`], sent by a
102 /// static invoice server in response to an [`OfferPathsRequest`].
103 ///
104 /// [`Offer::paths`]: crate::offers::offer::Offer::paths
105 OfferPaths(OfferPaths),
106
107 /// A request from an async recipient to a static invoice server that a [`StaticInvoice`] be
108 /// provided in response to [`InvoiceRequest`]s from payers.
109 ///
110 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
111 ServeStaticInvoice(ServeStaticInvoice),
112
113 /// Confirmation from a static invoice server that a [`StaticInvoice`] was persisted and the
114 /// corresponding [`Offer`] is ready to be used to receive async payments. Sent to an async
115 /// recipient in response to a [`ServeStaticInvoice`] message.
116 ///
117 /// [`Offer`]: crate::offers::offer::Offer
118 StaticInvoicePersisted(StaticInvoicePersisted),
119
120 /// An HTLC is being held upstream for the often-offline recipient, to be released via
121 /// [`ReleaseHeldHtlc`].
122 HeldHtlcAvailable(HeldHtlcAvailable),
123
124 /// Releases the HTLC corresponding to an inbound [`HeldHtlcAvailable`] message.
125 ReleaseHeldHtlc(ReleaseHeldHtlc),
126}
127
128/// A request from an async recipient for [`BlindedMessagePath`]s from a static invoice server.
129/// These paths will be used in the async recipient's [`Offer::paths`], so payers can request
130/// [`StaticInvoice`]s from the static invoice server.
131///
132/// [`Offer::paths`]: crate::offers::offer::Offer::paths
133#[derive(Clone, Debug)]
134pub struct OfferPathsRequest {
135 /// The "slot" in the static invoice server's database that this invoice should go into. This
136 /// allows us as the recipient to replace a specific invoice that is stored by the server, which
137 /// is useful for limiting the number of invoices stored by the server while also keeping all the
138 /// invoices persisted with the server fresh.
139 pub invoice_slot: u16,
140}
141
142/// [`BlindedMessagePath`]s to be included in an async recipient's [`Offer::paths`], sent by a
143/// static invoice server in response to an [`OfferPathsRequest`].
144///
145/// [`Offer::paths`]: crate::offers::offer::Offer::paths
146#[derive(Clone, Debug)]
147pub struct OfferPaths {
148 /// The paths that should be included in the async recipient's [`Offer::paths`].
149 ///
150 /// [`Offer::paths`]: crate::offers::offer::Offer::paths
151 pub paths: Vec<BlindedMessagePath>,
152 /// The time as seconds since the Unix epoch at which the [`Self::paths`] expire.
153 pub paths_absolute_expiry: Option<u64>,
154}
155
156/// A request from an async recipient to a static invoice server that a [`StaticInvoice`] be
157/// provided in response to [`InvoiceRequest`]s from payers.
158///
159/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
160#[derive(Clone, Debug)]
161pub struct ServeStaticInvoice {
162 /// The invoice that should be served by the static invoice server. Once this invoice has been
163 /// persisted, the [`Responder`] accompanying this message should be used to send
164 /// [`StaticInvoicePersisted`] to the recipient to confirm that the offer corresponding to the
165 /// invoice is ready to receive async payments.
166 pub invoice: StaticInvoice,
167 /// If a static invoice server receives an [`InvoiceRequest`] for a [`StaticInvoice`], they should
168 /// also forward the [`InvoiceRequest`] to the async recipient so they can respond with a fresh
169 /// [`Bolt12Invoice`] if the recipient is online at the time. Use this path to forward the
170 /// [`InvoiceRequest`] to the async recipient.
171 ///
172 /// This path's [`BlindedMessagePath::introduction_node`] MUST be set to the static invoice server
173 /// node or one of its peers. This is because, for DoS protection, invoice requests forwarded over
174 /// this path are treated by the server node like any other onion message forward and the server
175 /// will not directly connect to the introduction node if they are not already peers.
176 ///
177 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
178 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
179 pub forward_invoice_request_path: BlindedMessagePath,
180}
181
182/// Confirmation from a static invoice server that a [`StaticInvoice`] was persisted and the
183/// corresponding [`Offer`] is ready to be used to receive async payments. Sent to an async
184/// recipient in response to a [`ServeStaticInvoice`] message.
185///
186/// [`Offer`]: crate::offers::offer::Offer
187#[derive(Clone, Debug)]
188pub struct StaticInvoicePersisted {}
189
190/// An HTLC destined for the recipient of this message is being held upstream. The reply path
191/// accompanying this onion message should be used to send a [`ReleaseHeldHtlc`] response, which
192/// will cause the upstream HTLC to be released.
193#[derive(Clone, Debug)]
194pub struct HeldHtlcAvailable {}
195
196/// Releases the HTLC corresponding to an inbound [`HeldHtlcAvailable`] message.
197#[derive(Clone, Debug)]
198pub struct ReleaseHeldHtlc {}
199
200impl OnionMessageContents for OfferPaths {
201 fn tlv_type(&self) -> u64 {
202 OFFER_PATHS_TLV_TYPE
203 }
204 #[cfg(c_bindings)]
205 fn msg_type(&self) -> String {
206 "Offer Paths".to_string()
207 }
208 #[cfg(not(c_bindings))]
209 fn msg_type(&self) -> &'static str {
210 "Offer Paths"
211 }
212}
213
214impl OnionMessageContents for ServeStaticInvoice {
215 fn tlv_type(&self) -> u64 {
216 SERVE_INVOICE_TLV_TYPE
217 }
218 #[cfg(c_bindings)]
219 fn msg_type(&self) -> String {
220 "Serve Static Invoice".to_string()
221 }
222 #[cfg(not(c_bindings))]
223 fn msg_type(&self) -> &'static str {
224 "Serve Static Invoice"
225 }
226}
227
228impl OnionMessageContents for ReleaseHeldHtlc {
229 fn tlv_type(&self) -> u64 {
230 RELEASE_HELD_HTLC_TLV_TYPE
231 }
232 #[cfg(c_bindings)]
233 fn msg_type(&self) -> String {
234 "Release Held HTLC".to_string()
235 }
236 #[cfg(not(c_bindings))]
237 fn msg_type(&self) -> &'static str {
238 "Release Held HTLC"
239 }
240}
241
242impl_writeable_tlv_based!(OfferPathsRequest, {
243 (0, invoice_slot, required),
244});
245
246impl_writeable_tlv_based!(OfferPaths, {
247 (0, paths, required_vec),
248 (2, paths_absolute_expiry, option),
249});
250
251impl_writeable_tlv_based!(ServeStaticInvoice, {
252 (0, invoice, required),
253 (2, forward_invoice_request_path, required),
254});
255
256impl_writeable_tlv_based!(StaticInvoicePersisted, {});
257
258impl_writeable_tlv_based!(HeldHtlcAvailable, {});
259
260impl_writeable_tlv_based!(ReleaseHeldHtlc, {});
261
262impl AsyncPaymentsMessage {
263 /// Returns whether `tlv_type` corresponds to a TLV record for async payment messages.
264 pub fn is_known_type(tlv_type: u64) -> bool {
265 match tlv_type {
266 OFFER_PATHS_REQ_TLV_TYPE
267 | OFFER_PATHS_TLV_TYPE
268 | SERVE_INVOICE_TLV_TYPE
269 | INVOICE_PERSISTED_TLV_TYPE
270 | HELD_HTLC_AVAILABLE_TLV_TYPE
271 | RELEASE_HELD_HTLC_TLV_TYPE => true,
272 _ => false,
273 }
274 }
275}
276
277impl OnionMessageContents for AsyncPaymentsMessage {
278 fn tlv_type(&self) -> u64 {
279 match self {
280 Self::OfferPathsRequest(_) => OFFER_PATHS_REQ_TLV_TYPE,
281 Self::OfferPaths(msg) => msg.tlv_type(),
282 Self::ServeStaticInvoice(msg) => msg.tlv_type(),
283 Self::StaticInvoicePersisted(_) => INVOICE_PERSISTED_TLV_TYPE,
284 Self::HeldHtlcAvailable(_) => HELD_HTLC_AVAILABLE_TLV_TYPE,
285 Self::ReleaseHeldHtlc(msg) => msg.tlv_type(),
286 }
287 }
288 #[cfg(c_bindings)]
289 fn msg_type(&self) -> String {
290 match &self {
291 Self::OfferPathsRequest(_) => "Offer Paths Request".to_string(),
292 Self::OfferPaths(msg) => msg.msg_type(),
293 Self::ServeStaticInvoice(msg) => msg.msg_type(),
294 Self::StaticInvoicePersisted(_) => "Static Invoice Persisted".to_string(),
295 Self::HeldHtlcAvailable(_) => "Held HTLC Available".to_string(),
296 Self::ReleaseHeldHtlc(msg) => msg.msg_type(),
297 }
298 }
299 #[cfg(not(c_bindings))]
300 fn msg_type(&self) -> &'static str {
301 match &self {
302 Self::OfferPathsRequest(_) => "Offer Paths Request",
303 Self::OfferPaths(msg) => msg.msg_type(),
304 Self::ServeStaticInvoice(msg) => msg.msg_type(),
305 Self::StaticInvoicePersisted(_) => "Static Invoice Persisted",
306 Self::HeldHtlcAvailable(_) => "Held HTLC Available",
307 Self::ReleaseHeldHtlc(msg) => msg.msg_type(),
308 }
309 }
310}
311
312impl Writeable for AsyncPaymentsMessage {
313 fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
314 match self {
315 Self::OfferPathsRequest(message) => message.write(w),
316 Self::OfferPaths(message) => message.write(w),
317 Self::ServeStaticInvoice(message) => message.write(w),
318 Self::StaticInvoicePersisted(message) => message.write(w),
319 Self::HeldHtlcAvailable(message) => message.write(w),
320 Self::ReleaseHeldHtlc(message) => message.write(w),
321 }
322 }
323}
324
325impl ReadableArgs<u64> for AsyncPaymentsMessage {
326 fn read<R: io::Read>(r: &mut R, tlv_type: u64) -> Result<Self, DecodeError> {
327 match tlv_type {
328 OFFER_PATHS_REQ_TLV_TYPE => Ok(Self::OfferPathsRequest(Readable::read(r)?)),
329 OFFER_PATHS_TLV_TYPE => Ok(Self::OfferPaths(Readable::read(r)?)),
330 SERVE_INVOICE_TLV_TYPE => Ok(Self::ServeStaticInvoice(Readable::read(r)?)),
331 INVOICE_PERSISTED_TLV_TYPE => Ok(Self::StaticInvoicePersisted(Readable::read(r)?)),
332 HELD_HTLC_AVAILABLE_TLV_TYPE => Ok(Self::HeldHtlcAvailable(Readable::read(r)?)),
333 RELEASE_HELD_HTLC_TLV_TYPE => Ok(Self::ReleaseHeldHtlc(Readable::read(r)?)),
334 _ => Err(DecodeError::InvalidValue),
335 }
336 }
337}