Skip to content

Commit e5dca8d

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 87b98a6 commit e5dca8d

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdat
6868
#[cfg(test)]
6969
use crate::ln::outbound_payment;
7070
use crate::ln::outbound_payment::{Bolt11PaymentError, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
71+
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
7172
use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY};
7273
use crate::offers::invoice_error::InvoiceError;
7374
use crate::offers::invoice_request::InvoiceRequest;
@@ -13758,6 +13759,7 @@ where
1375813759
let mut decode_update_add_htlcs: Option<HashMap<u64, Vec<msgs::UpdateAddHTLC>>> = None;
1375913760
let mut inbound_payment_id_secret = None;
1376013761
let mut peer_storage_dir: Option<Vec<(PublicKey, Vec<u8>)>> = None;
13762+
let mut async_receive_offer_cache: AsyncReceiveOfferCache = AsyncReceiveOfferCache::new();
1376113763
read_tlv_fields!(reader, {
1376213764
(1, pending_outbound_payments_no_retry, option),
1376313765
(2, pending_intercepted_htlcs, option),
@@ -13775,6 +13777,7 @@ where
1377513777
(15, inbound_payment_id_secret, option),
1377613778
(17, in_flight_monitor_updates, option),
1377713779
(19, peer_storage_dir, optional_vec),
13780+
(21, async_receive_offer_cache, (default_value, async_receive_offer_cache)),
1377813781
});
1377913782
let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map());
1378013783
let peer_storage_dir: Vec<(PublicKey, Vec<u8>)> = peer_storage_dir.unwrap_or_else(Vec::new);
@@ -14461,6 +14464,8 @@ where
1446114464
chain_hash, best_block, our_network_pubkey,
1446214465
highest_seen_timestamp, expanded_inbound_key,
1446314466
secp_ctx.clone(), args.message_router
14467+
).with_async_payments_offers_cache(
14468+
async_receive_offer_cache, &args.default_config.paths_to_static_invoice_server[..]
1446414469
);
1446514470

1446614471
let channel_manager = ChannelManager {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.
32+
offer_nonce: Nonce,
33+
update_static_invoice_path: Responder,
34+
static_invoice_absolute_expiry: Duration,
35+
invoice_update_attempts: u8,
36+
}
37+
38+
impl_writeable_tlv_based!(AsyncReceiveOffer, {
39+
(0, offer, required),
40+
(2, offer_nonce, required),
41+
(4, offer_created_at, required),
42+
(6, update_static_invoice_path, required),
43+
(8, static_invoice_absolute_expiry, required),
44+
(10, invoice_update_attempts, (static_value, 0)),
45+
});
46+
47+
/// If we are an often-offline recipient, we'll want to interactively build offers and static
48+
/// invoices with an always-online node that will serve those static invoices to payers on our
49+
/// behalf when we are offline.
50+
///
51+
/// This struct is used to cache those interactively built offers, and should be passed into
52+
/// [`OffersMessageFlow`] on startup as well as persisted whenever an offer or invoice is updated
53+
/// with the static invoice server.
54+
///
55+
/// [`OffersMessageFlow`]: crate::offers::flow::OffersMessageFlow
56+
pub struct AsyncReceiveOfferCache {
57+
offers: Vec<AsyncReceiveOffer>,
58+
/// Used to limit the number of times we request paths for our offer from the static invoice
59+
/// server.
60+
#[allow(unused)] // TODO: remove when we get rid of async payments cfg flag
61+
offer_paths_request_attempts: u8,
62+
/// Used to determine whether enough time has passed since our last request for offer paths that
63+
/// more requests should be allowed to go out.
64+
#[allow(unused)] // TODO: remove when we get rid of async payments cfg flag
65+
last_offer_paths_request_timestamp: Duration,
66+
}
67+
68+
impl AsyncReceiveOfferCache {
69+
/// Creates an empty [`AsyncReceiveOfferCache`] to be passed into [`OffersMessageFlow`].
70+
///
71+
/// [`OffersMessageFlow`]: crate::offers::flow::OffersMessageFlow
72+
pub fn new() -> Self {
73+
Self {
74+
offers: Vec::new(),
75+
offer_paths_request_attempts: 0,
76+
last_offer_paths_request_timestamp: Duration::from_secs(0),
77+
}
78+
}
79+
}
80+
81+
impl Writeable for AsyncReceiveOfferCache {
82+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
83+
write_tlv_fields!(w, {
84+
(0, self.offers, required_vec),
85+
// offer paths request retry info always resets on restart
86+
});
87+
Ok(())
88+
}
89+
}
90+
91+
impl Readable for AsyncReceiveOfferCache {
92+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
93+
_init_and_read_len_prefixed_tlv_fields!(r, {
94+
(0, offers, required_vec),
95+
});
96+
let offers: Vec<AsyncReceiveOffer> = offers;
97+
Ok(Self {
98+
offers,
99+
offer_paths_request_attempts: 0,
100+
last_offer_paths_request_timestamp: Duration::from_secs(0),
101+
})
102+
}
103+
}

lightning/src/offers/flow.rs

Lines changed: 21 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,
@@ -98,6 +99,10 @@ where
9899
pub(crate) pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
99100

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

102107
#[cfg(feature = "dnssec")]
103108
pub(crate) hrn_resolver: OMNameResolver,
@@ -133,9 +138,25 @@ where
133138
hrn_resolver: OMNameResolver::new(current_timestamp, best_block.height),
134139
#[cfg(feature = "dnssec")]
135140
pending_dns_onion_messages: Mutex::new(Vec::new()),
141+
142+
async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()),
143+
paths_to_static_invoice_server: Vec::new(),
136144
}
137145
}
138146

147+
/// If we are an async recipient, on startup we'll interactively build offers and static invoices
148+
/// with an always-online node that will serve static invoices on our behalf. Once the offer is
149+
/// built and the static invoice is confirmed as persisted by the server, the underlying
150+
/// [`AsyncReceiveOfferCache`] should be persisted so we remember the offers we've built.
151+
pub(crate) fn with_async_payments_offers_cache(
152+
mut self, async_receive_offer_cache: AsyncReceiveOfferCache,
153+
paths_to_static_invoice_server: &[BlindedMessagePath],
154+
) -> Self {
155+
self.async_receive_offer_cache = Mutex::new(async_receive_offer_cache);
156+
self.paths_to_static_invoice_server = paths_to_static_invoice_server.to_vec();
157+
self
158+
}
159+
139160
/// Gets the node_id held by this [`OffersMessageFlow`]`
140161
fn get_our_node_id(&self) -> PublicKey {
141162
self.our_network_pubkey

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)