@@ -30,12 +30,20 @@ use crate::blinded_path::message::AsyncPaymentsContext;
30
30
enum OfferStatus {
31
31
/// This offer has been returned to the user from the cache, so it needs to be stored until it
32
32
/// 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
+ } ,
34
38
/// This offer has not yet been returned to the user, and is safe to replace to ensure we always
35
39
/// have a maximally fresh offer. We always want to have at least 1 offer in this state,
36
40
/// preferably a few so we can respond to user requests for new offers without returning the same
37
41
/// 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
+ } ,
39
47
/// This offer's invoice is not yet confirmed as persisted by the static invoice server, so it is
40
48
/// not yet ready to receive payments.
41
49
Pending ,
@@ -60,8 +68,12 @@ struct AsyncReceiveOffer {
60
68
}
61
69
62
70
impl_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
+ } ,
65
77
( 2 , Pending ) => { } ,
66
78
) ;
67
79
@@ -216,16 +228,18 @@ impl AsyncReceiveOfferCache {
216
228
// Find the freshest unused offer. See `OfferStatus::Ready`.
217
229
let newest_unused_offer_opt = self
218
230
. 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 } ) ;
223
237
return Ok ( ( newest_ready_offer, true ) ) ;
224
238
}
225
239
226
240
// If no unused offers are available, return the used offer with the latest absolute expiry
227
241
self . offers_with_idx ( )
228
- . filter ( |( _, offer) | matches ! ( offer. status, OfferStatus :: Used ) )
242
+ . filter ( |( _, offer) | matches ! ( offer. status, OfferStatus :: Used { .. } ) )
229
243
. max_by ( |a, b| {
230
244
let abs_expiry_a = a. 1 . offer . absolute_expiry ( ) . unwrap_or ( Duration :: MAX ) ;
231
245
let abs_expiry_b = b. 1 . offer . absolute_expiry ( ) . unwrap_or ( Duration :: MAX ) ;
@@ -338,9 +352,9 @@ impl AsyncReceiveOfferCache {
338
352
}
339
353
340
354
// 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
+ } ) ;
344
358
if no_replaceable_offers {
345
359
return None ;
346
360
}
@@ -350,7 +364,7 @@ impl AsyncReceiveOfferCache {
350
364
let num_payable_offers = self
351
365
. offers_with_idx ( )
352
366
. filter ( |( _, offer) | {
353
- matches ! ( offer. status, OfferStatus :: Used | OfferStatus :: Ready { .. } )
367
+ matches ! ( offer. status, OfferStatus :: Used { .. } | OfferStatus :: Ready { .. } )
354
368
} )
355
369
. count ( ) ;
356
370
if num_payable_offers <= 1 {
@@ -361,10 +375,10 @@ impl AsyncReceiveOfferCache {
361
375
// were last updated, so they are stale enough to warrant replacement.
362
376
let awhile_ago = duration_since_epoch. saturating_sub ( OFFER_REFRESH_THRESHOLD ) ;
363
377
self . unused_ready_offers ( )
364
- . filter ( |( _, offer) | offer. created_at < awhile_ago)
378
+ . filter ( |( _, offer, _ ) | offer. created_at < awhile_ago)
365
379
// 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)
368
382
}
369
383
370
384
/// Returns an iterator over (offer_idx, offer)
@@ -378,11 +392,11 @@ impl AsyncReceiveOfferCache {
378
392
} )
379
393
}
380
394
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
382
396
/// [`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 ) > {
384
398
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 ) ) ,
386
400
_ => None ,
387
401
} )
388
402
}
@@ -408,7 +422,7 @@ impl AsyncReceiveOfferCache {
408
422
// them a fresh invoice on each timer tick.
409
423
self . offers_with_idx ( ) . filter_map ( |( idx, offer) | {
410
424
let needs_invoice_update =
411
- offer. status == OfferStatus :: Used || offer . status == OfferStatus :: Pending ;
425
+ matches ! ( offer. status, OfferStatus :: Used { .. } | OfferStatus :: Pending ) ;
412
426
if needs_invoice_update {
413
427
let offer_slot = idx. try_into ( ) . unwrap_or ( u16:: MAX ) ;
414
428
Some ( (
@@ -431,29 +445,25 @@ impl AsyncReceiveOfferCache {
431
445
/// is needed.
432
446
///
433
447
/// [`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)
443
452
} ,
444
453
_ => return false ,
445
454
} ;
446
455
447
456
let mut offers = self . offers . iter_mut ( ) ;
448
457
let offer_entry = offers. find ( |o| o. as_ref ( ) . map_or ( false , |o| o. offer . id ( ) == offer_id) ) ;
449
458
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 } ,
454
465
}
455
466
456
- offer. status = OfferStatus :: Ready ;
457
467
return true ;
458
468
}
459
469
@@ -465,7 +475,7 @@ impl AsyncReceiveOfferCache {
465
475
self . offers_with_idx ( )
466
476
. filter_map ( |( _, offer) | {
467
477
if matches ! ( offer. status, OfferStatus :: Ready { .. } )
468
- || matches ! ( offer. status, OfferStatus :: Used )
478
+ || matches ! ( offer. status, OfferStatus :: Used { .. } )
469
479
{
470
480
Some ( offer. offer . clone ( ) )
471
481
} else {
0 commit comments