Skip to content

Commit 2a6c744

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 4c35a16 commit 2a6c744

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
@@ -85,6 +85,7 @@ use crate::ln::outbound_payment::{
8585
SendAlongPathArgs, StaleExpiration,
8686
};
8787
use crate::ln::types::ChannelId;
88+
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
8889
use crate::offers::flow::OffersMessageFlow;
8990
use crate::offers::invoice::{
9091
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
@@ -13683,6 +13684,7 @@ where
1368313684
(15, self.inbound_payment_id_secret, required),
1368413685
(17, in_flight_monitor_updates, option),
1368513686
(19, peer_storage_dir, optional_vec),
13687+
(21, self.flow.writeable_async_receive_offer_cache(), required),
1368613688
});
1368713689

1368813690
Ok(())
@@ -14247,6 +14249,7 @@ where
1424714249
let mut decode_update_add_htlcs: Option<HashMap<u64, Vec<msgs::UpdateAddHTLC>>> = None;
1424814250
let mut inbound_payment_id_secret = None;
1424914251
let mut peer_storage_dir: Option<Vec<(PublicKey, Vec<u8>)>> = None;
14252+
let mut async_receive_offer_cache: AsyncReceiveOfferCache = AsyncReceiveOfferCache::new();
1425014253
read_tlv_fields!(reader, {
1425114254
(1, pending_outbound_payments_no_retry, option),
1425214255
(2, pending_intercepted_htlcs, option),
@@ -14264,6 +14267,7 @@ where
1426414267
(15, inbound_payment_id_secret, option),
1426514268
(17, in_flight_monitor_updates, option),
1426614269
(19, peer_storage_dir, optional_vec),
14270+
(21, async_receive_offer_cache, (default_value, async_receive_offer_cache)),
1426714271
});
1426814272
let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map());
1426914273
let peer_storage_dir: Vec<(PublicKey, Vec<u8>)> = peer_storage_dir.unwrap_or_else(Vec::new);
@@ -14950,6 +14954,8 @@ where
1495014954
chain_hash, best_block, our_network_pubkey,
1495114955
highest_seen_timestamp, expanded_inbound_key,
1495214956
secp_ctx.clone(), args.message_router
14957+
).with_async_payments_offers_cache(
14958+
async_receive_offer_cache, &args.default_config.paths_to_static_invoice_server[..]
1495314959
);
1495414960

1495514961
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)