@@ -78,6 +78,105 @@ impl AsyncReceiveOfferCache {
7878 }
7979}
8080
81+ // The target number of offers we want to have cached at any given time, to mitigate too much
82+ // reuse of the same offer.
83+ #[ cfg( async_payments) ]
84+ const NUM_CACHED_OFFERS_TARGET : usize = 3 ;
85+
86+ // The max number of times we'll attempt to request offer paths or attempt to refresh a static
87+ // invoice before giving up.
88+ #[ cfg( async_payments) ]
89+ const MAX_UPDATE_ATTEMPTS : u8 = 3 ;
90+
91+ // If we run out of attempts to request offer paths from the static invoice server, we'll stop
92+ // sending requests for some time. After this amount of time has passed, more requests are allowed
93+ // to be sent out.
94+ #[ cfg( async_payments) ]
95+ const PATHS_REQUESTS_BUFFER : Duration = Duration :: from_secs ( 3 * 60 * 60 ) ;
96+
97+ // If an offer is 90% of the way through its lifespan, it's expiring soon. This allows us to be
98+ // flexible for various offer lifespans, i.e. an offer that lasts 10 days expires soon after 9 days
99+ // and an offer that lasts 10 years expires soon after 9 years.
100+ const OFFER_EXPIRES_SOON_THRESHOLD_PERCENT : u64 = 90 ;
101+
102+ #[ cfg( async_payments) ]
103+ impl AsyncReceiveOfferCache {
104+ /// Remove expired offers from the cache.
105+ pub ( super ) fn prune_expired_offers ( & mut self , duration_since_epoch : Duration ) {
106+ // Remove expired offers from the cache.
107+ let mut offer_was_removed = false ;
108+ self . offers . retain ( |offer| {
109+ if offer. offer . is_expired_no_std ( duration_since_epoch) {
110+ offer_was_removed = true ;
111+ return false ;
112+ }
113+ true
114+ } ) ;
115+
116+ // If we just removed a newly expired offer, force allowing more paths request attempts.
117+ if offer_was_removed {
118+ self . reset_offer_paths_request_attempts ( ) ;
119+ }
120+
121+ // If we haven't attempted to request new paths in a long time, allow more requests to go out
122+ // if/when needed.
123+ self . check_reset_offer_paths_request_attempts ( duration_since_epoch) ;
124+ }
125+
126+ /// Checks whether we should request new offer paths from the always-online static invoice server.
127+ pub ( super ) fn should_request_offer_paths ( & self , duration_since_epoch : Duration ) -> bool {
128+ self . needs_new_offers ( duration_since_epoch)
129+ && self . offer_paths_request_attempts < MAX_UPDATE_ATTEMPTS
130+ }
131+
132+ /// Returns a bool indicating whether new offers are needed in the cache.
133+ fn needs_new_offers ( & self , duration_since_epoch : Duration ) -> bool {
134+ // If we have fewer than NUM_CACHED_OFFERS_TARGET offers that aren't expiring soon, indicate
135+ // that new offers should be interactively built.
136+ let num_unexpiring_offers = self
137+ . offers
138+ . iter ( )
139+ . filter ( |offer| {
140+ let offer_absolute_expiry = offer. offer . absolute_expiry ( ) . unwrap_or ( Duration :: MAX ) ;
141+ let offer_created_at = offer. offer_created_at ;
142+ let offer_lifespan =
143+ offer_absolute_expiry. saturating_sub ( offer_created_at) . as_secs ( ) ;
144+ let elapsed = duration_since_epoch. saturating_sub ( offer_created_at) . as_secs ( ) ;
145+
146+ // If an offer is in the last 10% of its lifespan, it's expiring soon.
147+ elapsed. saturating_mul ( 100 )
148+ < offer_lifespan. saturating_mul ( OFFER_EXPIRES_SOON_THRESHOLD_PERCENT )
149+ } )
150+ . count ( ) ;
151+
152+ num_unexpiring_offers < NUM_CACHED_OFFERS_TARGET
153+ }
154+
155+ // Indicates that onion messages requesting new offer paths have been sent to the static invoice
156+ // server. Calling this method allows the cache to self-limit how many requests are sent, in case
157+ // the server goes unresponsive.
158+ pub ( super ) fn new_offers_requested ( & mut self , duration_since_epoch : Duration ) {
159+ self . offer_paths_request_attempts += 1 ;
160+ self . last_offer_paths_request_timestamp = duration_since_epoch;
161+ }
162+
163+ /// If we haven't sent an offer paths request in a long time, reset the limit to allow more
164+ /// requests to be sent out if/when needed.
165+ fn check_reset_offer_paths_request_attempts ( & mut self , duration_since_epoch : Duration ) {
166+ let should_reset =
167+ self . last_offer_paths_request_timestamp . saturating_add ( PATHS_REQUESTS_BUFFER )
168+ < duration_since_epoch;
169+ if should_reset {
170+ self . reset_offer_paths_request_attempts ( ) ;
171+ }
172+ }
173+
174+ fn reset_offer_paths_request_attempts ( & mut self ) {
175+ self . offer_paths_request_attempts = 0 ;
176+ self . last_offer_paths_request_timestamp = Duration :: from_secs ( 0 ) ;
177+ }
178+ }
179+
81180impl Writeable for AsyncReceiveOfferCache {
82181 fn write < W : Writer > ( & self , w : & mut W ) -> Result < ( ) , io:: Error > {
83182 write_tlv_fields ! ( w, {
0 commit comments