@@ -579,3 +579,74 @@ fn pays_static_invoice() {
579579 . handle_onion_message ( nodes[ 2 ] . node . get_our_node_id ( ) , & release_held_htlc_om) ;
580580 assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
581581}
582+
583+ #[ cfg_attr( feature = "std" , ignore) ]
584+ #[ test]
585+ fn expired_static_invoice_fail ( ) {
586+ // Test that if we receive an expired static invoice we'll fail the payment.
587+ let secp_ctx = Secp256k1 :: new ( ) ;
588+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
589+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
590+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
591+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
592+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
593+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
594+
595+ const INVOICE_EXPIRY_SECS : u32 = 10 ;
596+ let relative_expiry = Duration :: from_secs ( INVOICE_EXPIRY_SECS as u64 ) ;
597+ let ( offer, static_invoice) =
598+ create_static_invoice ( & nodes[ 1 ] , & nodes[ 2 ] , Some ( relative_expiry) , & secp_ctx) ;
599+
600+ let amt_msat = 5000 ;
601+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
602+ nodes[ 0 ]
603+ . node
604+ . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None )
605+ . unwrap ( ) ;
606+
607+ let invreq_om = nodes[ 0 ]
608+ . onion_messenger
609+ . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) )
610+ . unwrap ( ) ;
611+ let invreq_reply_path = offers_tests:: extract_invoice_request ( & nodes[ 1 ] , & invreq_om) . 1 ;
612+ // TODO: update to not manually send here when we add support for being the recipient's
613+ // always-online counterparty
614+ nodes[ 1 ]
615+ . onion_messenger
616+ . send_onion_message (
617+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice (
618+ static_invoice,
619+ ) ) ,
620+ MessageSendInstructions :: WithoutReplyPath {
621+ destination : Destination :: BlindedPath ( invreq_reply_path) ,
622+ } ,
623+ )
624+ . unwrap ( ) ;
625+ let static_invoice_om = nodes[ 1 ]
626+ . onion_messenger
627+ . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) )
628+ . unwrap ( ) ;
629+
630+ // Wait until the static invoice expires before providing it to the sender.
631+ let block = create_dummy_block (
632+ nodes[ 0 ] . best_block_hash ( ) ,
633+ nodes[ 0 ] . node . duration_since_epoch ( ) . as_secs ( ) as u32 + INVOICE_EXPIRY_SECS + 1 ,
634+ Vec :: new ( ) ,
635+ ) ;
636+ connect_block ( & nodes[ 0 ] , & block) ;
637+ nodes[ 0 ]
638+ . onion_messenger
639+ . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & static_invoice_om) ;
640+
641+ let events = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
642+ assert_eq ! ( events. len( ) , 1 ) ;
643+ match events[ 0 ] {
644+ Event :: PaymentFailed { payment_id : ev_payment_id, reason, .. } => {
645+ assert_eq ! ( reason. unwrap( ) , PaymentFailureReason :: PaymentExpired ) ;
646+ assert_eq ! ( ev_payment_id, payment_id) ;
647+ } ,
648+ _ => panic ! ( ) ,
649+ }
650+ // The sender doesn't reply with InvoiceError right now because the always-online node doesn't
651+ // currently provide them with a reply path to do so.
652+ }
0 commit comments