@@ -30,12 +30,20 @@ use crate::blinded_path::message::AsyncPaymentsContext;
3030enum OfferStatus {
3131 /// This offer has been returned to the user from the cache, so it needs to be stored until it
3232 /// expires and its invoice needs to be kept updated.
33- Used ,
33+ Used {
34+ /// The creation time of the invoice that was last confirmed as persisted by the server. Useful
35+ /// to know when the invoice needs refreshing.
36+ invoice_created_at : Duration ,
37+ } ,
3438 /// This offer has not yet been returned to the user, and is safe to replace to ensure we always
3539 /// have a maximally fresh offer. We always want to have at least 1 offer in this state,
3640 /// preferably a few so we can respond to user requests for new offers without returning the same
3741 /// one multiple times. Returning a new offer each time is better for privacy.
38- Ready ,
42+ Ready {
43+ /// The creation time of the invoice that was last confirmed as persisted by the server. Useful
44+ /// to know when the invoice needs refreshing.
45+ invoice_created_at : Duration ,
46+ } ,
3947 /// This offer's invoice is not yet confirmed as persisted by the static invoice server, so it is
4048 /// not yet ready to receive payments.
4149 Pending ,
@@ -60,8 +68,12 @@ struct AsyncReceiveOffer {
6068}
6169
6270impl_writeable_tlv_based_enum ! ( OfferStatus ,
63- ( 0 , Used ) => { } ,
64- ( 1 , Ready ) => { } ,
71+ ( 0 , Used ) => {
72+ ( 0 , invoice_created_at, required) ,
73+ } ,
74+ ( 1 , Ready ) => {
75+ ( 0 , invoice_created_at, required) ,
76+ } ,
6577 ( 2 , Pending ) => { } ,
6678) ;
6779
@@ -216,16 +228,18 @@ impl AsyncReceiveOfferCache {
216228 // Find the freshest unused offer. See `OfferStatus::Ready`.
217229 let newest_unused_offer_opt = self
218230 . unused_ready_offers ( )
219- . max_by ( |( _, offer_a) , ( _, offer_b) | offer_a. created_at . cmp ( & offer_b. created_at ) )
220- . map ( |( idx, offer) | ( idx, offer. offer . clone ( ) ) ) ;
221- if let Some ( ( idx, newest_ready_offer) ) = newest_unused_offer_opt {
222- self . offers [ idx] . as_mut ( ) . map ( |offer| offer. status = OfferStatus :: Used ) ;
231+ . max_by ( |( _, offer_a, _) , ( _, offer_b, _) | offer_a. created_at . cmp ( & offer_b. created_at ) )
232+ . map ( |( idx, offer, invoice_created_at) | ( idx, offer. offer . clone ( ) , invoice_created_at) ) ;
233+ if let Some ( ( idx, newest_ready_offer, invoice_created_at) ) = newest_unused_offer_opt {
234+ self . offers [ idx]
235+ . as_mut ( )
236+ . map ( |offer| offer. status = OfferStatus :: Used { invoice_created_at } ) ;
223237 return Ok ( ( newest_ready_offer, true ) ) ;
224238 }
225239
226240 // If no unused offers are available, return the used offer with the latest absolute expiry
227241 self . offers_with_idx ( )
228- . filter ( |( _, offer) | matches ! ( offer. status, OfferStatus :: Used ) )
242+ . filter ( |( _, offer) | matches ! ( offer. status, OfferStatus :: Used { .. } ) )
229243 . max_by ( |a, b| {
230244 let abs_expiry_a = a. 1 . offer . absolute_expiry ( ) . unwrap_or ( Duration :: MAX ) ;
231245 let abs_expiry_b = b. 1 . offer . absolute_expiry ( ) . unwrap_or ( Duration :: MAX ) ;
@@ -338,9 +352,9 @@ impl AsyncReceiveOfferCache {
338352 }
339353
340354 // If all of our offers are already used or pending, then none are available to be replaced
341- let no_replaceable_offers = self
342- . offers_with_idx ( )
343- . all ( | ( _ , offer ) | matches ! ( offer . status , OfferStatus :: Used | OfferStatus :: Pending ) ) ;
355+ let no_replaceable_offers = self . offers_with_idx ( ) . all ( | ( _ , offer ) | {
356+ matches ! ( offer . status , OfferStatus :: Used { .. } | OfferStatus :: Pending )
357+ } ) ;
344358 if no_replaceable_offers {
345359 return None ;
346360 }
@@ -350,7 +364,7 @@ impl AsyncReceiveOfferCache {
350364 let num_payable_offers = self
351365 . offers_with_idx ( )
352366 . filter ( |( _, offer) | {
353- matches ! ( offer. status, OfferStatus :: Used | OfferStatus :: Ready { .. } )
367+ matches ! ( offer. status, OfferStatus :: Used { .. } | OfferStatus :: Ready { .. } )
354368 } )
355369 . count ( ) ;
356370 if num_payable_offers <= 1 {
@@ -361,10 +375,10 @@ impl AsyncReceiveOfferCache {
361375 // were last updated, so they are stale enough to warrant replacement.
362376 let awhile_ago = duration_since_epoch. saturating_sub ( OFFER_REFRESH_THRESHOLD ) ;
363377 self . unused_ready_offers ( )
364- . filter ( |( _, offer) | offer. created_at < awhile_ago)
378+ . filter ( |( _, offer, _ ) | offer. created_at < awhile_ago)
365379 // Get the stalest offer and return its index
366- . min_by ( |( _, offer_a) , ( _, offer_b) | offer_a. created_at . cmp ( & offer_b. created_at ) )
367- . map ( |( idx, _) | idx)
380+ . min_by ( |( _, offer_a, _ ) , ( _, offer_b, _ ) | offer_a. created_at . cmp ( & offer_b. created_at ) )
381+ . map ( |( idx, _, _ ) | idx)
368382 }
369383
370384 /// Returns an iterator over (offer_idx, offer)
@@ -378,11 +392,11 @@ impl AsyncReceiveOfferCache {
378392 } )
379393 }
380394
381- /// Returns an iterator over (offer_idx, offer) where all returned offers are
395+ /// Returns an iterator over (offer_idx, offer, invoice_created_at ) where all returned offers are
382396 /// [`OfferStatus::Ready`]
383- fn unused_ready_offers ( & self ) -> impl Iterator < Item = ( usize , & AsyncReceiveOffer ) > {
397+ fn unused_ready_offers ( & self ) -> impl Iterator < Item = ( usize , & AsyncReceiveOffer , Duration ) > {
384398 self . offers_with_idx ( ) . filter_map ( |( idx, offer) | match offer. status {
385- OfferStatus :: Ready => Some ( ( idx, offer) ) ,
399+ OfferStatus :: Ready { invoice_created_at } => Some ( ( idx, offer, invoice_created_at ) ) ,
386400 _ => None ,
387401 } )
388402 }
@@ -408,7 +422,7 @@ impl AsyncReceiveOfferCache {
408422 // them a fresh invoice on each timer tick.
409423 self . offers_with_idx ( ) . filter_map ( |( idx, offer) | {
410424 let needs_invoice_update =
411- offer. status == OfferStatus :: Used || offer . status == OfferStatus :: Pending ;
425+ matches ! ( offer. status, OfferStatus :: Used { .. } | OfferStatus :: Pending ) ;
412426 if needs_invoice_update {
413427 let offer_slot = idx. try_into ( ) . unwrap_or ( u16:: MAX ) ;
414428 Some ( (
@@ -431,29 +445,25 @@ impl AsyncReceiveOfferCache {
431445 /// is needed.
432446 ///
433447 /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted
434- pub ( super ) fn static_invoice_persisted (
435- & mut self , context : AsyncPaymentsContext , duration_since_epoch : Duration ,
436- ) -> bool {
437- let offer_id = match context {
438- AsyncPaymentsContext :: StaticInvoicePersisted { path_absolute_expiry, offer_id } => {
439- if duration_since_epoch > path_absolute_expiry {
440- return false ;
441- }
442- offer_id
448+ pub ( super ) fn static_invoice_persisted ( & mut self , context : AsyncPaymentsContext ) -> bool {
449+ let ( invoice_created_at, offer_id) = match context {
450+ AsyncPaymentsContext :: StaticInvoicePersisted { invoice_created_at, offer_id } => {
451+ ( invoice_created_at, offer_id)
443452 } ,
444453 _ => return false ,
445454 } ;
446455
447456 let mut offers = self . offers . iter_mut ( ) ;
448457 let offer_entry = offers. find ( |o| o. as_ref ( ) . map_or ( false , |o| o. offer . id ( ) == offer_id) ) ;
449458 if let Some ( Some ( ref mut offer) ) = offer_entry {
450- if offer. status == OfferStatus :: Used {
451- // We succeeded in updating the invoice for a used offer, no re-persistence of the cache
452- // needed
453- return false ;
459+ match offer. status {
460+ OfferStatus :: Used { invoice_created_at : ref mut inv_created_at }
461+ | OfferStatus :: Ready { invoice_created_at : ref mut inv_created_at } => {
462+ * inv_created_at = core:: cmp:: min ( invoice_created_at, * inv_created_at) ;
463+ } ,
464+ OfferStatus :: Pending => offer. status = OfferStatus :: Ready { invoice_created_at } ,
454465 }
455466
456- offer. status = OfferStatus :: Ready ;
457467 return true ;
458468 }
459469
@@ -465,7 +475,7 @@ impl AsyncReceiveOfferCache {
465475 self . offers_with_idx ( )
466476 . filter_map ( |( _, offer) | {
467477 if matches ! ( offer. status, OfferStatus :: Ready { .. } )
468- || matches ! ( offer. status, OfferStatus :: Used )
478+ || matches ! ( offer. status, OfferStatus :: Used { .. } )
469479 {
470480 Some ( offer. offer . clone ( ) )
471481 } else {
0 commit comments