@@ -114,6 +114,7 @@ use secp256k1::key::PublicKey;
114
114
use std:: collections:: hash_map:: { self , HashMap } ;
115
115
use std:: ops:: Deref ;
116
116
use std:: sync:: Mutex ;
117
+ use std:: time:: { Duration , SystemTime } ;
117
118
118
119
/// A utility for paying [`Invoice]`s.
119
120
pub struct InvoicePayer < P : Deref , R , L : Deref , E >
@@ -226,6 +227,7 @@ where
226
227
hash_map:: Entry :: Vacant ( entry) => {
227
228
let payer = self . payer . node_id ( ) ;
228
229
let mut payee = Payee :: new ( invoice. recover_payee_pub_key ( ) )
230
+ . with_expiry_time ( expiry_time_from_unix_epoch ( & invoice) . as_secs ( ) )
229
231
. with_route_hints ( invoice. route_hints ( ) ) ;
230
232
if let Some ( features) = invoice. features ( ) {
231
233
payee = payee. with_features ( features. clone ( ) ) ;
@@ -273,6 +275,15 @@ where
273
275
}
274
276
}
275
277
278
+ fn expiry_time_from_unix_epoch ( invoice : & Invoice ) -> Duration {
279
+ invoice. timestamp ( ) . duration_since ( SystemTime :: UNIX_EPOCH ) . unwrap ( ) + invoice. expiry_time ( )
280
+ }
281
+
282
+ fn has_expired ( params : & RouteParameters ) -> bool {
283
+ let expiry_time = Duration :: from_secs ( params. payee . expiry_time . unwrap ( ) ) ;
284
+ Invoice :: is_expired_from_epoch ( & SystemTime :: UNIX_EPOCH , expiry_time)
285
+ }
286
+
276
287
impl < P : Deref , R , L : Deref , E > EventHandler for InvoicePayer < P , R , L , E >
277
288
where
278
289
P :: Target : Payer ,
@@ -304,6 +315,8 @@ where
304
315
log_trace ! ( self . logger, "Payment {} exceeded maximum attempts; not retrying (attempts: {})" , log_bytes!( payment_hash. 0 ) , attempts) ;
305
316
} else if retry. is_none ( ) {
306
317
log_trace ! ( self . logger, "Payment {} missing retry params; not retrying (attempts: {})" , log_bytes!( payment_hash. 0 ) , attempts) ;
318
+ } else if has_expired ( retry. as_ref ( ) . unwrap ( ) ) {
319
+ log_trace ! ( self . logger, "Invoice expired for payment {}; not retrying (attempts: {})" , log_bytes!( payment_hash. 0 ) , attempts) ;
307
320
} else if self . retry_payment ( * payment_id. as_ref ( ) . unwrap ( ) , retry. as_ref ( ) . unwrap ( ) ) . is_err ( ) {
308
321
log_trace ! ( self . logger, "Error retrying payment {}; not retrying (attempts: {})" , log_bytes!( payment_hash. 0 ) , attempts) ;
309
322
} else {
@@ -336,7 +349,7 @@ where
336
349
#[ cfg( test) ]
337
350
mod tests {
338
351
use super :: * ;
339
- use crate :: { InvoiceBuilder , Currency } ;
352
+ use crate :: { DEFAULT_EXPIRY_TIME , InvoiceBuilder , Currency } ;
340
353
use bitcoin_hashes:: sha256:: Hash as Sha256 ;
341
354
use lightning:: ln:: PaymentPreimage ;
342
355
use lightning:: ln:: features:: { ChannelFeatures , NodeFeatures } ;
@@ -346,6 +359,7 @@ mod tests {
346
359
use lightning:: util:: errors:: APIError ;
347
360
use lightning:: util:: events:: Event ;
348
361
use secp256k1:: { SecretKey , PublicKey , Secp256k1 } ;
362
+ use std:: time:: { SystemTime , Duration } ;
349
363
350
364
fn invoice ( payment_preimage : PaymentPreimage ) -> Invoice {
351
365
let payment_hash = Sha256 :: hash ( & payment_preimage. 0 ) ;
@@ -378,6 +392,25 @@ mod tests {
378
392
. unwrap ( )
379
393
}
380
394
395
+ fn expired_invoice ( payment_preimage : PaymentPreimage ) -> Invoice {
396
+ let payment_hash = Sha256 :: hash ( & payment_preimage. 0 ) ;
397
+ let private_key = SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ;
398
+ let timestamp = SystemTime :: now ( )
399
+ . checked_sub ( Duration :: from_secs ( DEFAULT_EXPIRY_TIME * 2 ) )
400
+ . unwrap ( ) ;
401
+ InvoiceBuilder :: new ( Currency :: Bitcoin )
402
+ . description ( "test" . into ( ) )
403
+ . payment_hash ( payment_hash)
404
+ . payment_secret ( PaymentSecret ( [ 0 ; 32 ] ) )
405
+ . timestamp ( timestamp)
406
+ . min_final_cltv_expiry ( 144 )
407
+ . amount_milli_satoshis ( 128 )
408
+ . build_signed ( |hash| {
409
+ Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key)
410
+ } )
411
+ . unwrap ( )
412
+ }
413
+
381
414
#[ test]
382
415
fn pays_invoice_on_first_attempt ( ) {
383
416
let event_handled = core:: cell:: RefCell :: new ( false ) ;
@@ -574,6 +607,37 @@ mod tests {
574
607
assert_eq ! ( * payer. attempts. borrow( ) , 1 ) ;
575
608
}
576
609
610
+ #[ test]
611
+ fn fails_paying_invoice_after_expiration ( ) {
612
+ let event_handled = core:: cell:: RefCell :: new ( false ) ;
613
+ let event_handler = |_: & _ | { * event_handled. borrow_mut ( ) = true ; } ;
614
+
615
+ let payer = TestPayer :: new ( ) ;
616
+ let router = TestRouter { } ;
617
+ let logger = TestLogger :: new ( ) ;
618
+ let invoice_payer =
619
+ InvoicePayer :: new ( & payer, router, & logger, event_handler, RetryAttempts ( 2 ) ) ;
620
+
621
+ let payment_preimage = PaymentPreimage ( [ 1 ; 32 ] ) ;
622
+ let invoice = expired_invoice ( payment_preimage) ;
623
+ let payment_id = Some ( invoice_payer. pay_invoice ( & invoice) . unwrap ( ) ) ;
624
+ assert_eq ! ( * payer. attempts. borrow( ) , 1 ) ;
625
+
626
+ let event = Event :: PaymentPathFailed {
627
+ payment_id,
628
+ payment_hash : PaymentHash ( invoice. payment_hash ( ) . clone ( ) . into_inner ( ) ) ,
629
+ network_update : None ,
630
+ rejected_by_dest : false ,
631
+ all_paths_failed : false ,
632
+ path : vec ! [ ] ,
633
+ short_channel_id : None ,
634
+ retry : Some ( TestRouter :: retry_for_invoice ( & invoice) ) ,
635
+ } ;
636
+ invoice_payer. handle_event ( & event) ;
637
+ assert_eq ! ( * event_handled. borrow( ) , true ) ;
638
+ assert_eq ! ( * payer. attempts. borrow( ) , 1 ) ;
639
+ }
640
+
577
641
#[ test]
578
642
fn fails_paying_invoice_after_retry_error ( ) {
579
643
let event_handled = core:: cell:: RefCell :: new ( false ) ;
@@ -795,6 +859,7 @@ mod tests {
795
859
796
860
fn retry_for_invoice ( invoice : & Invoice ) -> RouteParameters {
797
861
let mut payee = Payee :: new ( invoice. recover_payee_pub_key ( ) )
862
+ . with_expiry_time ( expiry_time_from_unix_epoch ( invoice) . as_secs ( ) )
798
863
. with_route_hints ( invoice. route_hints ( ) ) ;
799
864
if let Some ( features) = invoice. features ( ) {
800
865
payee = payee. with_features ( features. clone ( ) ) ;
0 commit comments