@@ -1516,6 +1516,11 @@ struct AsyncReceiveOffer {
1516
1516
}
1517
1517
1518
1518
impl AsyncReceiveOffer {
1519
+ // If we have more than three hours before our offer expires, don't bother requesting new
1520
+ // paths.
1521
+ #[cfg(async_payments)]
1522
+ const OFFER_RELATIVE_EXPIRY_BUFFER: Duration = Duration::from_secs(60 * 60 * 3);
1523
+
1519
1524
/// Removes the offer from our cache if it's expired.
1520
1525
#[cfg(async_payments)]
1521
1526
fn check_expire_offer(&mut self, duration_since_epoch: Duration) {
@@ -1526,6 +1531,17 @@ impl AsyncReceiveOffer {
1526
1531
}
1527
1532
}
1528
1533
}
1534
+
1535
+ #[cfg(async_payments)]
1536
+ fn should_refresh_offer(&self, duration_since_epoch: Duration) -> bool {
1537
+ if let Some(ref offer) = self.offer {
1538
+ let offer_expiry = offer.absolute_expiry().unwrap_or(Duration::MAX);
1539
+ if offer_expiry > duration_since_epoch.saturating_add(Self::OFFER_RELATIVE_EXPIRY_BUFFER) {
1540
+ return false
1541
+ }
1542
+ }
1543
+ return true
1544
+ }
1529
1545
}
1530
1546
1531
1547
impl_writeable_tlv_based!(AsyncReceiveOffer, {
@@ -4875,15 +4891,8 @@ where
4875
4891
{
4876
4892
let mut offer_cache = self.async_receive_offer_cache.lock().unwrap();
4877
4893
offer_cache.check_expire_offer(duration_since_epoch);
4878
-
4879
- if let Some(ref offer) = offer_cache.offer {
4880
- // If we have more than three hours before our offer expires, don't bother requesting new
4881
- // paths.
4882
- const PATHS_EXPIRY_BUFFER: Duration = Duration::from_secs(60 * 60 * 3);
4883
- let offer_expiry = offer.absolute_expiry().unwrap_or(Duration::MAX);
4884
- if offer_expiry > duration_since_epoch.saturating_add(PATHS_EXPIRY_BUFFER) {
4885
- return
4886
- }
4894
+ if !offer_cache.should_refresh_offer(duration_since_epoch) {
4895
+ return
4887
4896
}
4888
4897
4889
4898
const MAX_ATTEMPTS: u8 = 3;
@@ -12647,6 +12656,101 @@ where
12647
12656
fn handle_offer_paths(
12648
12657
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
12649
12658
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
12659
+ #[cfg(async_payments)] {
12660
+ let expanded_key = &self.inbound_payment_key;
12661
+ let entropy = &*self.entropy_source;
12662
+ let secp_ctx = &self.secp_ctx;
12663
+ let duration_since_epoch = self.duration_since_epoch();
12664
+
12665
+ match _context {
12666
+ AsyncPaymentsContext::OfferPaths { nonce, hmac, path_absolute_expiry } => {
12667
+ if let Err(()) = signer::verify_offer_paths_context(nonce, hmac, expanded_key) {
12668
+ return None
12669
+ }
12670
+ if duration_since_epoch > path_absolute_expiry { return None }
12671
+ },
12672
+ _ => return None
12673
+ }
12674
+
12675
+ if !self.async_receive_offer_cache.lock().unwrap().should_refresh_offer(duration_since_epoch) {
12676
+ return None
12677
+ }
12678
+
12679
+ // Require at least two hours before we'll need to start the process of creating a new offer.
12680
+ const MIN_OFFER_PATHS_RELATIVE_EXPIRY: Duration =
12681
+ Duration::from_secs(2 * 60 * 60).saturating_add(AsyncReceiveOffer::OFFER_RELATIVE_EXPIRY_BUFFER);
12682
+ let min_offer_paths_absolute_expiry =
12683
+ duration_since_epoch.saturating_add(MIN_OFFER_PATHS_RELATIVE_EXPIRY);
12684
+ let offer_paths_absolute_expiry =
12685
+ _message.paths_absolute_expiry.unwrap_or(Duration::from_secs(u64::MAX));
12686
+ if offer_paths_absolute_expiry < min_offer_paths_absolute_expiry {
12687
+ log_error!(self.logger, "Received offer paths with too-soon absolute Unix epoch expiry: {}", offer_paths_absolute_expiry.as_secs());
12688
+ return None
12689
+ }
12690
+
12691
+ // Expire the offer at the same time as the static invoice so we automatically refresh both
12692
+ // at the same time.
12693
+ let offer_and_invoice_absolute_expiry = Duration::from_secs(core::cmp::min(
12694
+ offer_paths_absolute_expiry.as_secs(),
12695
+ duration_since_epoch.saturating_add(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY).as_secs()
12696
+ ));
12697
+
12698
+ let (offer, offer_nonce) = {
12699
+ let (offer_builder, offer_nonce) =
12700
+ match self.create_async_receive_offer_builder(_message.paths) {
12701
+ Ok((builder, nonce)) => (builder, nonce),
12702
+ Err(e) => {
12703
+ log_error!(self.logger, "Failed to create offer builder when replying to OfferPaths message: {:?}", e);
12704
+ return None
12705
+ },
12706
+ };
12707
+ match offer_builder.absolute_expiry(offer_and_invoice_absolute_expiry).build() {
12708
+ Ok(offer) => (offer, offer_nonce),
12709
+ Err(e) => {
12710
+ log_error!(self.logger, "Failed to build offer when replying to OfferPaths message: {:?}", e);
12711
+ return None
12712
+ },
12713
+ }
12714
+ };
12715
+
12716
+ let static_invoice = {
12717
+ let invoice_res = self.create_static_invoice_builder(
12718
+ &offer, offer_nonce, Some(offer_and_invoice_absolute_expiry)
12719
+ ).and_then(|builder| builder.build_and_sign(secp_ctx));
12720
+ match invoice_res {
12721
+ Ok(invoice) => invoice,
12722
+ Err(e) => {
12723
+ log_error!(self.logger, "Failed to create static invoice when replying to OfferPaths message: {:?}", e);
12724
+ return None
12725
+ },
12726
+ }
12727
+ };
12728
+
12729
+ let invoice_persisted_paths = {
12730
+ // We expect the static invoice server to respond quickly, but add some buffer for no-std
12731
+ // users that rely on block timestamps.
12732
+ const PATH_RELATIVE_EXPIRY: Duration = Duration::from_secs(2 * 60 * 60);
12733
+
12734
+ let nonce = Nonce::from_entropy_source(entropy);
12735
+ let hmac = signer::hmac_for_static_invoice_persisted_context(nonce, expanded_key);
12736
+ let context = MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted {
12737
+ offer, nonce, hmac,
12738
+ path_absolute_expiry: duration_since_epoch.saturating_add(PATH_RELATIVE_EXPIRY)
12739
+ });
12740
+ match self.create_blinded_paths(context) {
12741
+ Ok(paths) => paths,
12742
+ Err(()) => {
12743
+ log_error!(self.logger, "Failed to create blinded paths when replying to OfferPaths message");
12744
+ return None
12745
+ },
12746
+ }
12747
+ };
12748
+
12749
+ let reply = ServeStaticInvoice { invoice: static_invoice, invoice_persisted_paths };
12750
+ return _responder.map(|responder| (reply, responder.respond()))
12751
+ }
12752
+
12753
+ #[cfg(not(async_payments))]
12650
12754
None
12651
12755
}
12652
12756
0 commit comments