Skip to content

Commit 16c9a37

Browse files
Track cached async receive offers in offers::Flow
In future commits, as part of being an async recipient, we will interactively build offers and static invoices with an always-online node that will serve static invoices on our behalf. Once an offer is built and the static invoice is confirmed as persisted by the server, we will use the new offer cache added here to save the invoice metadata and the offer in ChannelManager, though the OffersMessageFlow is responsible for keeping the cache updated. We want to cache and persist these offers so we always have them at the ready, we don't want to begin the process of interactively building an offer the moment it is needed. The offers are likely to be long-lived so caching them avoids having to keep interactively rebuilding them after every restart.
1 parent e5079a6 commit 16c9a37

File tree

4 files changed

+140
-0
lines changed

4 files changed

+140
-0
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ use crate::ln::outbound_payment::{
8686
SendAlongPathArgs, StaleExpiration,
8787
};
8888
use crate::ln::types::ChannelId;
89+
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
8990
use crate::offers::flow::OffersMessageFlow;
9091
use crate::offers::invoice::{
9192
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
@@ -13881,6 +13882,7 @@ where
1388113882
(15, self.inbound_payment_id_secret, required),
1388213883
(17, in_flight_monitor_updates, option),
1388313884
(19, peer_storage_dir, optional_vec),
13885+
(21, self.flow.writeable_async_receive_offer_cache(), required),
1388413886
});
1388513887

1388613888
Ok(())
@@ -14454,6 +14456,7 @@ where
1445414456
let mut decode_update_add_htlcs: Option<HashMap<u64, Vec<msgs::UpdateAddHTLC>>> = None;
1445514457
let mut inbound_payment_id_secret = None;
1445614458
let mut peer_storage_dir: Option<Vec<(PublicKey, Vec<u8>)>> = None;
14459+
let mut async_receive_offer_cache: AsyncReceiveOfferCache = AsyncReceiveOfferCache::new();
1445714460
read_tlv_fields!(reader, {
1445814461
(1, pending_outbound_payments_no_retry, option),
1445914462
(2, pending_intercepted_htlcs, option),
@@ -14471,6 +14474,7 @@ where
1447114474
(15, inbound_payment_id_secret, option),
1447214475
(17, in_flight_monitor_updates, option),
1447314476
(19, peer_storage_dir, optional_vec),
14477+
(21, async_receive_offer_cache, (default_value, async_receive_offer_cache)),
1447414478
});
1447514479
let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map());
1447614480
let peer_storage_dir: Vec<(PublicKey, Vec<u8>)> = peer_storage_dir.unwrap_or_else(Vec::new);
@@ -15157,6 +15161,8 @@ where
1515715161
chain_hash, best_block, our_network_pubkey,
1515815162
highest_seen_timestamp, expanded_inbound_key,
1515915163
secp_ctx.clone(), args.message_router
15164+
).with_async_payments_offers_cache(
15165+
async_receive_offer_cache, &args.default_config.paths_to_static_invoice_server[..]
1516015166
);
1516115167

1516215168
let channel_manager = ChannelManager {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
//! Data structures and methods for caching offers that we interactively build with a static invoice
11+
//! server as an async recipient. The static invoice server will serve the resulting invoices to
12+
//! payers on our behalf when we're offline.
13+
14+
use crate::io;
15+
use crate::io::Read;
16+
use crate::ln::msgs::DecodeError;
17+
use crate::offers::nonce::Nonce;
18+
use crate::offers::offer::Offer;
19+
use crate::onion_message::messenger::Responder;
20+
use crate::prelude::*;
21+
use crate::util::ser::{Readable, Writeable, Writer};
22+
use core::time::Duration;
23+
24+
struct AsyncReceiveOffer {
25+
offer: Offer,
26+
/// We determine whether an offer is expiring "soon" based on how far the offer is into its total
27+
/// lifespan, using this field.
28+
offer_created_at: Duration,
29+
30+
/// The below fields are used to generate and persist a new static invoice with the invoice
31+
/// server, if the invoice is expiring prior to the corresponding offer. We support automatically
32+
/// rotating the invoice for long-lived offers so users don't have to update the offer they've
33+
/// posted on e.g. their website if fees change or the invoices' payment paths become otherwise
34+
/// outdated.
35+
offer_nonce: Nonce,
36+
update_static_invoice_path: Responder,
37+
static_invoice_absolute_expiry: Duration,
38+
invoice_update_attempts: u8,
39+
}
40+
41+
impl_writeable_tlv_based!(AsyncReceiveOffer, {
42+
(0, offer, required),
43+
(2, offer_nonce, required),
44+
(4, offer_created_at, required),
45+
(6, update_static_invoice_path, required),
46+
(8, static_invoice_absolute_expiry, required),
47+
(10, invoice_update_attempts, (static_value, 0)),
48+
});
49+
50+
/// If we are an often-offline recipient, we'll want to interactively build offers and static
51+
/// invoices with an always-online node that will serve those static invoices to payers on our
52+
/// behalf when we are offline.
53+
///
54+
/// This struct is used to cache those interactively built offers, and should be passed into
55+
/// [`OffersMessageFlow`] on startup as well as persisted whenever an offer or invoice is updated
56+
/// with the static invoice server.
57+
///
58+
/// [`OffersMessageFlow`]: crate::offers::flow::OffersMessageFlow
59+
pub struct AsyncReceiveOfferCache {
60+
offers: Vec<AsyncReceiveOffer>,
61+
/// Used to limit the number of times we request paths for our offer from the static invoice
62+
/// server.
63+
#[allow(unused)] // TODO: remove when we get rid of async payments cfg flag
64+
offer_paths_request_attempts: u8,
65+
/// Used to determine whether enough time has passed since our last request for offer paths that
66+
/// more requests should be allowed to go out.
67+
#[allow(unused)] // TODO: remove when we get rid of async payments cfg flag
68+
last_offer_paths_request_timestamp: Duration,
69+
}
70+
71+
impl AsyncReceiveOfferCache {
72+
/// Creates an empty [`AsyncReceiveOfferCache`] to be passed into [`OffersMessageFlow`].
73+
///
74+
/// [`OffersMessageFlow`]: crate::offers::flow::OffersMessageFlow
75+
pub fn new() -> Self {
76+
Self {
77+
offers: Vec::new(),
78+
offer_paths_request_attempts: 0,
79+
last_offer_paths_request_timestamp: Duration::from_secs(0),
80+
}
81+
}
82+
}
83+
84+
impl Writeable for AsyncReceiveOfferCache {
85+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
86+
write_tlv_fields!(w, {
87+
(0, self.offers, required_vec),
88+
// offer paths request retry info always resets on restart
89+
});
90+
Ok(())
91+
}
92+
}
93+
94+
impl Readable for AsyncReceiveOfferCache {
95+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
96+
_init_and_read_len_prefixed_tlv_fields!(r, {
97+
(0, offers, required_vec),
98+
});
99+
let offers: Vec<AsyncReceiveOffer> = offers;
100+
Ok(Self {
101+
offers,
102+
offer_paths_request_attempts: 0,
103+
last_offer_paths_request_timestamp: Duration::from_secs(0),
104+
})
105+
}
106+
}

lightning/src/offers/flow.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use crate::ln::channelmanager::{
3636
Verification, {PaymentId, CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY},
3737
};
3838
use crate::ln::inbound_payment;
39+
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
3940
use crate::offers::invoice::{
4041
Bolt12Invoice, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder,
4142
UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY,
@@ -56,6 +57,7 @@ use crate::routing::router::Router;
5657
use crate::sign::{EntropySource, NodeSigner};
5758
use crate::sync::{Mutex, RwLock};
5859
use crate::types::payment::{PaymentHash, PaymentSecret};
60+
use crate::util::ser::Writeable;
5961

6062
#[cfg(async_payments)]
6163
use {
@@ -98,6 +100,10 @@ where
98100
pub(crate) pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
99101

100102
pending_async_payments_messages: Mutex<Vec<(AsyncPaymentsMessage, MessageSendInstructions)>>,
103+
async_receive_offer_cache: Mutex<AsyncReceiveOfferCache>,
104+
/// Blinded paths used to request offer paths from the static invoice server, if we are an async
105+
/// recipient.
106+
paths_to_static_invoice_server: Vec<BlindedMessagePath>,
101107

102108
#[cfg(feature = "dnssec")]
103109
pub(crate) hrn_resolver: OMNameResolver,
@@ -133,9 +139,25 @@ where
133139
hrn_resolver: OMNameResolver::new(current_timestamp, best_block.height),
134140
#[cfg(feature = "dnssec")]
135141
pending_dns_onion_messages: Mutex::new(Vec::new()),
142+
143+
async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()),
144+
paths_to_static_invoice_server: Vec::new(),
136145
}
137146
}
138147

148+
/// If we are an async recipient, on startup we'll interactively build offers and static invoices
149+
/// with an always-online node that will serve static invoices on our behalf. Once the offer is
150+
/// built and the static invoice is confirmed as persisted by the server, the underlying
151+
/// [`AsyncReceiveOfferCache`] should be persisted so we remember the offers we've built.
152+
pub(crate) fn with_async_payments_offers_cache(
153+
mut self, async_receive_offer_cache: AsyncReceiveOfferCache,
154+
paths_to_static_invoice_server: &[BlindedMessagePath],
155+
) -> Self {
156+
self.async_receive_offer_cache = Mutex::new(async_receive_offer_cache);
157+
self.paths_to_static_invoice_server = paths_to_static_invoice_server.to_vec();
158+
self
159+
}
160+
139161
/// Gets the node_id held by this [`OffersMessageFlow`]`
140162
fn get_our_node_id(&self) -> PublicKey {
141163
self.our_network_pubkey
@@ -1082,4 +1104,9 @@ where
10821104
) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
10831105
core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap())
10841106
}
1107+
1108+
/// Get the `AsyncReceiveOfferCache` for persistence.
1109+
pub(crate) fn writeable_async_receive_offer_cache(&self) -> impl Writeable + '_ {
1110+
&self.async_receive_offer_cache
1111+
}
10851112
}

lightning/src/offers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
pub mod offer;
1717
pub mod flow;
1818

19+
pub(crate) mod async_receive_offer_cache;
1920
pub mod invoice;
2021
pub mod invoice_error;
2122
mod invoice_macros;

0 commit comments

Comments
 (0)