1111//! server as an async recipient. The static invoice server will serve the resulting invoices to
1212//! payers on our behalf when we're offline.
1313
14+ use alloc:: collections:: BTreeMap ;
15+
1416use crate :: blinded_path:: message:: { AsyncPaymentsContext , BlindedMessagePath } ;
1517use crate :: io;
1618use crate :: io:: Read ;
@@ -19,7 +21,7 @@ use crate::offers::nonce::Nonce;
1921use crate :: offers:: offer:: Offer ;
2022use crate :: onion_message:: messenger:: Responder ;
2123use crate :: prelude:: * ;
22- use crate :: util:: ser:: { Readable , Writeable , Writer } ;
24+ use crate :: util:: ser:: { BigSizeKeyedMap , Readable , Writeable , Writer } ;
2325use core:: time:: Duration ;
2426
2527/// The status of this offer in the cache.
@@ -62,6 +64,7 @@ struct AsyncReceiveOffer {
6264 /// payment paths become otherwise outdated.
6365 offer_nonce : Nonce ,
6466 update_static_invoice_path : Responder ,
67+ payment_metadata : Option < BTreeMap < u64 , Vec < u8 > > > ,
6568}
6669
6770impl AsyncReceiveOffer {
@@ -92,6 +95,7 @@ impl_ser_tlv_based!(AsyncReceiveOffer, {
9295 ( 4 , status, required) ,
9396 ( 6 , update_static_invoice_path, required) ,
9497 ( 8 , created_at, required) ,
98+ ( 10 , payment_metadata, ( option, encoding: ( BTreeMap <u64 , Vec <u8 >>, BigSizeKeyedMap ) ) ) ,
9599} ) ;
96100
97101/// If we are an often-offline recipient, we'll want to interactively build offers and static
@@ -147,6 +151,8 @@ pub struct AsyncReceiveOfferCache {
147151 /// Blinded paths used to request offer paths from the static invoice server.
148152 #[ allow( unused) ] // TODO: remove when we get rid of async payments cfg flag
149153 paths_to_static_invoice_server : Vec < BlindedMessagePath > ,
154+ /// Payment metadata associated with offer-path requests in flight.
155+ pending_offer_payment_metadata : BTreeMap < u16 , Option < BTreeMap < u64 , Vec < u8 > > > > ,
150156}
151157
152158impl AsyncReceiveOfferCache {
@@ -158,6 +164,7 @@ impl AsyncReceiveOfferCache {
158164 offers : Vec :: new ( ) ,
159165 offer_paths_request_attempts : 0 ,
160166 paths_to_static_invoice_server : Vec :: new ( ) ,
167+ pending_offer_payment_metadata : BTreeMap :: new ( ) ,
161168 }
162169 }
163170
@@ -320,6 +327,7 @@ impl AsyncReceiveOfferCache {
320327 pub ( super ) fn cache_pending_offer (
321328 & mut self , offer : Offer , offer_paths_absolute_expiry_secs : Option < u64 > , offer_nonce : Nonce ,
322329 update_static_invoice_path : Responder , duration_since_epoch : Duration , slot : u16 ,
330+ payment_metadata : Option < BTreeMap < u64 , Vec < u8 > > > ,
323331 ) -> Result < ( ) , ( ) > {
324332 self . prune_expired_offers ( duration_since_epoch, false ) ;
325333
@@ -340,13 +348,15 @@ impl AsyncReceiveOfferCache {
340348 offer_nonce,
341349 status : OfferStatus :: Pending ,
342350 update_static_invoice_path,
351+ payment_metadata,
343352 } )
344353 } ,
345354 None => {
346355 debug_assert ! ( false , "Slot in cache was invalid but should'be been checked above" ) ;
347356 return Err ( ( ) ) ;
348357 } ,
349358 }
359+ self . pending_offer_payment_metadata . remove ( & slot) ;
350360
351361 Ok ( ( ) )
352362 }
@@ -433,8 +443,11 @@ impl AsyncReceiveOfferCache {
433443
434444 // Indicates that onion messages requesting new offer paths have been sent to the static invoice
435445 // server. Calling this method allows the cache to self-limit how many requests are sent.
436- pub ( super ) fn new_offers_requested ( & mut self ) {
446+ pub ( super ) fn new_offers_requested (
447+ & mut self , slot : u16 , payment_metadata : Option < BTreeMap < u64 , Vec < u8 > > > ,
448+ ) {
437449 self . offer_paths_request_attempts += 1 ;
450+ self . pending_offer_payment_metadata . insert ( slot, payment_metadata) ;
438451 }
439452
440453 /// Called on timer tick (roughly once per minute) to allow another [`MAX_UPDATE_ATTEMPTS`] offer
@@ -447,7 +460,7 @@ impl AsyncReceiveOfferCache {
447460 /// the static invoice server.
448461 pub ( super ) fn offers_needing_invoice_refresh (
449462 & self , duration_since_epoch : Duration ,
450- ) -> impl Iterator < Item = ( & Offer , Nonce , & Responder ) > {
463+ ) -> impl Iterator < Item = ( & Offer , Nonce , & Responder , Option < BTreeMap < u64 , Vec < u8 > > > ) > {
451464 // For any offers which are either in use or pending confirmation by the server, we should send
452465 // them a fresh invoice on each timer tick.
453466 self . offers_with_idx ( ) . filter_map ( move |( _, offer) | {
@@ -462,13 +475,44 @@ impl AsyncReceiveOfferCache {
462475 OfferStatus :: Ready { .. } => false ,
463476 } ;
464477 if needs_invoice_update {
465- Some ( ( & offer. offer , offer. offer_nonce , & offer. update_static_invoice_path ) )
478+ Some ( (
479+ & offer. offer ,
480+ offer. offer_nonce ,
481+ & offer. update_static_invoice_path ,
482+ offer. payment_metadata . clone ( ) ,
483+ ) )
466484 } else {
467485 None
468486 }
469487 } )
470488 }
471489
490+ /// Returns the payment metadata that should be attached to a replacement offer in `slot`.
491+ pub ( super ) fn payment_metadata_for_new_offer (
492+ & self , slot : u16 , payment_metadata : Option < BTreeMap < u64 , Vec < u8 > > > ,
493+ ) -> Option < BTreeMap < u64 , Vec < u8 > > > {
494+ payment_metadata
495+ . or_else ( || self . pending_offer_payment_metadata . get ( & slot) . cloned ( ) . flatten ( ) )
496+ . or_else ( || {
497+ self . offers
498+ . get ( slot as usize )
499+ . and_then ( |offer| offer. as_ref ( ) )
500+ . and_then ( |offer| offer. payment_metadata . clone ( ) )
501+ } )
502+ }
503+
504+ /// Returns the payment metadata associated with an incoming offer-path response for `slot`.
505+ pub ( super ) fn payment_metadata_for_offer_slot (
506+ & self , slot : u16 ,
507+ ) -> Option < BTreeMap < u64 , Vec < u8 > > > {
508+ self . pending_offer_payment_metadata . get ( & slot) . cloned ( ) . flatten ( ) . or_else ( || {
509+ self . offers
510+ . get ( slot as usize )
511+ . and_then ( |offer| offer. as_ref ( ) )
512+ . and_then ( |offer| offer. payment_metadata . clone ( ) )
513+ } )
514+ }
515+
472516 /// Should be called when we receive a [`StaticInvoicePersisted`] message from the static invoice
473517 /// server, which indicates that a new offer was persisted by the server and they are ready to
474518 /// serve the corresponding static invoice to payers on our behalf.
@@ -536,6 +580,11 @@ impl Readable for AsyncReceiveOfferCache {
536580 ( 2 , paths_to_static_invoice_server, required_vec) ,
537581 } ) ;
538582 let offers: Vec < Option < AsyncReceiveOffer > > = offers;
539- Ok ( Self { offers, offer_paths_request_attempts : 0 , paths_to_static_invoice_server } )
583+ Ok ( Self {
584+ offers,
585+ offer_paths_request_attempts : 0 ,
586+ paths_to_static_invoice_server,
587+ pending_offer_payment_metadata : BTreeMap :: new ( ) ,
588+ } )
540589 }
541590}
0 commit comments