@@ -35,8 +35,18 @@ use crate::util::ser::WithoutLength;
35
35
use crate :: util:: test_utils;
36
36
use lightning_invoice:: RawBolt11Invoice ;
37
37
#[ cfg( async_payments) ] use {
38
+ crate :: blinded_path:: message:: { BlindedMessagePath , MessageContext , OffersContext } ,
38
39
crate :: ln:: inbound_payment,
40
+ crate :: ln:: msgs:: OnionMessageHandler ,
41
+ crate :: offers:: nonce:: Nonce ,
42
+ crate :: onion_message:: async_payments:: { AsyncPaymentsMessage , AsyncPaymentsMessageHandler , ReleaseHeldHtlc } ,
43
+ crate :: onion_message:: messenger:: { Destination , MessageSendInstructions , MessageRouter , PeeledOnion } ,
44
+ crate :: onion_message:: offers:: OffersMessage ,
45
+ crate :: onion_message:: packet:: ParsedOnionMessageContents ,
46
+ crate :: types:: features:: Bolt12InvoiceFeatures ,
39
47
crate :: types:: payment:: PaymentPreimage ,
48
+
49
+ core:: convert:: Infallible ,
40
50
} ;
41
51
42
52
fn blinded_payment_path (
@@ -101,6 +111,25 @@ pub fn get_blinded_route_parameters(
101
111
)
102
112
}
103
113
114
+ #[ cfg( async_payments) ]
115
+ fn extract_invoice_request_reply_path < ' a , ' b , ' c > (
116
+ invreq_recipient : & Node < ' a , ' b , ' c > , message : & msgs:: OnionMessage
117
+ ) -> BlindedMessagePath {
118
+ match invreq_recipient. onion_messenger . peel_onion_message ( message) {
119
+ Ok ( PeeledOnion :: Receive ( invreq, context, reply_path) ) => {
120
+ assert ! (
121
+ matches!( invreq, ParsedOnionMessageContents :: Offers ( OffersMessage :: InvoiceRequest ( _) ) )
122
+ ) ;
123
+ assert ! (
124
+ matches!( context, Some ( MessageContext :: Offers ( OffersContext :: InvoiceRequest { .. } ) ) )
125
+ ) ;
126
+ reply_path. unwrap ( )
127
+ } ,
128
+ Ok ( PeeledOnion :: Forward ( _, _) ) => panic ! ( "Unexpected onion message forward" ) ,
129
+ Err ( e) => panic ! ( "Failed to process onion message {:?}" , e) ,
130
+ }
131
+ }
132
+
104
133
#[ test]
105
134
fn one_hop_blinded_path ( ) {
106
135
do_one_hop_blinded_path ( true ) ;
@@ -1416,6 +1445,210 @@ fn custom_tlvs_to_blinded_path() {
1416
1445
) ;
1417
1446
}
1418
1447
1448
+ #[ test]
1449
+ #[ cfg( async_payments) ]
1450
+ fn static_invoice_unknown_required_features ( ) {
1451
+ // Test that we will fail to pay a static invoice with unsupported required features.
1452
+ let secp_ctx = Secp256k1 :: new ( ) ;
1453
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1454
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1455
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1456
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1457
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1458
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1459
+
1460
+ let blinded_paths_to_always_online_node = nodes[ 1 ] . message_router . create_blinded_paths (
1461
+ nodes[ 1 ] . node . get_our_node_id ( ) ,
1462
+ MessageContext :: Offers ( OffersContext :: InvoiceRequest { nonce : Nonce ( [ 42 ; 16 ] ) } ) ,
1463
+ Vec :: new ( ) , & secp_ctx
1464
+ ) . unwrap ( ) ;
1465
+ let ( offer_builder, nonce) =
1466
+ nodes[ 2 ] . node . create_async_receive_offer_builder ( blinded_paths_to_always_online_node) . unwrap ( ) ;
1467
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1468
+ let static_invoice_unknown_req_features = nodes[ 2 ] . node . create_static_invoice_builder (
1469
+ & offer, nonce, None
1470
+ )
1471
+ . unwrap ( )
1472
+ . features_unchecked ( Bolt12InvoiceFeatures :: unknown ( ) )
1473
+ . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1474
+
1475
+ let amt_msat = 5000 ;
1476
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1477
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1478
+
1479
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1480
+ // recipient's LSP yet, instead manually construct the response.
1481
+ let invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1482
+ let invreq_reply_path = extract_invoice_request_reply_path ( & nodes[ 1 ] , & invreq_om) ;
1483
+ nodes[ 1 ] . onion_messenger . send_onion_message (
1484
+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice ( static_invoice_unknown_req_features) ) ,
1485
+ MessageSendInstructions :: WithoutReplyPath { destination : Destination :: BlindedPath ( invreq_reply_path) }
1486
+ ) . unwrap ( ) ;
1487
+
1488
+ let static_invoice_om = nodes[ 1 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1489
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & static_invoice_om) ;
1490
+ let events = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
1491
+ assert_eq ! ( events. len( ) , 1 ) ;
1492
+ match events[ 0 ] {
1493
+ Event :: PaymentFailed { payment_hash, payment_id : ev_payment_id, reason } => {
1494
+ assert_eq ! ( payment_hash, None ) ;
1495
+ assert_eq ! ( payment_id, ev_payment_id) ;
1496
+ assert_eq ! ( reason, Some ( PaymentFailureReason :: UnknownRequiredFeatures ) ) ;
1497
+ } ,
1498
+ _ => panic ! ( )
1499
+ }
1500
+ }
1501
+
1502
+ #[ test]
1503
+ #[ cfg( async_payments) ]
1504
+ fn ignore_unexpected_static_invoice ( ) {
1505
+ // Test that we'll ignore unexpected static invoices, invoices that don't match our invoice
1506
+ // request, and duplicate invoices.
1507
+ let secp_ctx = Secp256k1 :: new ( ) ;
1508
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1509
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1510
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1511
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1512
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1513
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1514
+
1515
+ // Initiate payment to the sender's intended offer.
1516
+ let blinded_paths_to_always_online_node = nodes[ 1 ] . message_router . create_blinded_paths (
1517
+ nodes[ 1 ] . node . get_our_node_id ( ) ,
1518
+ MessageContext :: Offers ( OffersContext :: InvoiceRequest { nonce : Nonce ( [ 42 ; 16 ] ) } ) ,
1519
+ Vec :: new ( ) , & secp_ctx
1520
+ ) . unwrap ( ) ;
1521
+ let ( offer_builder, offer_nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( blinded_paths_to_always_online_node. clone ( ) ) . unwrap ( ) ;
1522
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1523
+ let amt_msat = 5000 ;
1524
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1525
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1526
+
1527
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1528
+ // recipient's LSP yet, instead manually construct the responses below.
1529
+ let invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1530
+ let invreq_reply_path = extract_invoice_request_reply_path ( & nodes[ 1 ] , & invreq_om) ;
1531
+
1532
+ // Create a static invoice to be sent over the reply path containing the original payment_id, but
1533
+ // the static invoice corresponds to a different offer than was originally paid.
1534
+ let unexpected_static_invoice = {
1535
+ let ( offer_builder, nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( blinded_paths_to_always_online_node) . unwrap ( ) ;
1536
+ let sender_unintended_offer = offer_builder. build ( ) . unwrap ( ) ;
1537
+
1538
+ nodes[ 2 ] . node . create_static_invoice_builder (
1539
+ & sender_unintended_offer, nonce, None
1540
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( )
1541
+ } ;
1542
+
1543
+ // Check that we'll ignore the unexpected static invoice.
1544
+ nodes[ 1 ] . onion_messenger . send_onion_message (
1545
+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice ( unexpected_static_invoice) ) ,
1546
+ MessageSendInstructions :: WithoutReplyPath { destination : Destination :: BlindedPath ( invreq_reply_path. clone ( ) ) }
1547
+ ) . unwrap ( ) ;
1548
+ let unexpected_static_invoice_om = nodes[ 1 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1549
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & unexpected_static_invoice_om) ;
1550
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1551
+ assert ! ( async_pmts_msgs. is_empty( ) ) ;
1552
+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
1553
+
1554
+ // A valid static invoice corresponding to the correct offer will succeed and cause us to send a
1555
+ // held_htlc_available onion message.
1556
+ let valid_static_invoice = nodes[ 2 ] . node . create_static_invoice_builder (
1557
+ & offer, offer_nonce, None
1558
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1559
+
1560
+ nodes[ 1 ] . onion_messenger . send_onion_message (
1561
+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice ( valid_static_invoice. clone ( ) ) ) ,
1562
+ MessageSendInstructions :: WithoutReplyPath { destination : Destination :: BlindedPath ( invreq_reply_path. clone ( ) ) }
1563
+ ) . unwrap ( ) ;
1564
+ let static_invoice_om = nodes[ 1 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1565
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & static_invoice_om) ;
1566
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1567
+ assert ! ( !async_pmts_msgs. is_empty( ) ) ;
1568
+ assert ! ( async_pmts_msgs. into_iter( ) . all( |( msg, _) | matches!( msg, AsyncPaymentsMessage :: HeldHtlcAvailable ( _) ) ) ) ;
1569
+
1570
+ // Receiving a duplicate invoice will have no effect.
1571
+ nodes[ 1 ] . onion_messenger . send_onion_message (
1572
+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice ( valid_static_invoice) ) ,
1573
+ MessageSendInstructions :: WithoutReplyPath { destination : Destination :: BlindedPath ( invreq_reply_path) }
1574
+ ) . unwrap ( ) ;
1575
+ let dup_static_invoice_om = nodes[ 1 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1576
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & dup_static_invoice_om) ;
1577
+ let async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1578
+ assert ! ( async_pmts_msgs. is_empty( ) ) ;
1579
+ }
1580
+
1581
+ #[ test]
1582
+ #[ cfg( async_payments) ]
1583
+ fn pays_static_invoice ( ) {
1584
+ // Test that we support the async payments flow up to and including sending the actual payment.
1585
+ // Async receive is not yet supported so we don't complete the payment yet.
1586
+ let secp_ctx = Secp256k1 :: new ( ) ;
1587
+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1588
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1589
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1590
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1591
+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
1592
+ create_unannounced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
1593
+
1594
+ let blinded_paths_to_always_online_node = nodes[ 1 ] . message_router . create_blinded_paths (
1595
+ nodes[ 1 ] . node . get_our_node_id ( ) ,
1596
+ MessageContext :: Offers ( OffersContext :: InvoiceRequest { nonce : Nonce ( [ 42 ; 16 ] ) } ) ,
1597
+ Vec :: new ( ) , & secp_ctx
1598
+ ) . unwrap ( ) ;
1599
+ let ( offer_builder, offer_nonce) = nodes[ 2 ] . node . create_async_receive_offer_builder ( blinded_paths_to_always_online_node) . unwrap ( ) ;
1600
+ let offer = offer_builder. build ( ) . unwrap ( ) ;
1601
+ let amt_msat = 5000 ;
1602
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
1603
+ let static_invoice = nodes[ 2 ] . node . create_static_invoice_builder (
1604
+ & offer, offer_nonce, None
1605
+ ) . unwrap ( ) . build_and_sign ( & secp_ctx) . unwrap ( ) ;
1606
+
1607
+ nodes[ 0 ] . node . pay_for_offer ( & offer, None , Some ( amt_msat) , None , payment_id, Retry :: Attempts ( 0 ) , None ) . unwrap ( ) ;
1608
+
1609
+ // Don't forward the invreq since we don't support retrieving the static invoice from the
1610
+ // recipient's LSP yet, instead manually construct the response.
1611
+ let invreq_om = nodes[ 0 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 1 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1612
+ let invreq_reply_path = extract_invoice_request_reply_path ( & nodes[ 1 ] , & invreq_om) ;
1613
+
1614
+ nodes[ 1 ] . onion_messenger . send_onion_message (
1615
+ ParsedOnionMessageContents :: < Infallible > :: Offers ( OffersMessage :: StaticInvoice ( static_invoice) ) ,
1616
+ MessageSendInstructions :: WithoutReplyPath { destination : Destination :: BlindedPath ( invreq_reply_path) }
1617
+ ) . unwrap ( ) ;
1618
+ let static_invoice_om = nodes[ 1 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1619
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 1 ] . node . get_our_node_id ( ) , & static_invoice_om) ;
1620
+ let mut async_pmts_msgs = AsyncPaymentsMessageHandler :: release_pending_messages ( nodes[ 0 ] . node ) ;
1621
+ assert ! ( !async_pmts_msgs. is_empty( ) ) ;
1622
+ assert ! ( async_pmts_msgs. iter( ) . all( |( msg, _) | matches!( msg, AsyncPaymentsMessage :: HeldHtlcAvailable ( _) ) ) ) ;
1623
+
1624
+ // Manually send the message and context releasing the HTLC since the recipient doesn't support
1625
+ // responding themselves yet.
1626
+ let held_htlc_avail_reply_path = match async_pmts_msgs. pop ( ) . unwrap ( ) . 1 {
1627
+ MessageSendInstructions :: WithSpecifiedReplyPath { reply_path, .. } => reply_path,
1628
+ _ => panic ! ( )
1629
+ } ;
1630
+ nodes[ 2 ] . onion_messenger . send_onion_message (
1631
+ ParsedOnionMessageContents :: < Infallible > :: AsyncPayments (
1632
+ AsyncPaymentsMessage :: ReleaseHeldHtlc ( ReleaseHeldHtlc { } ) ,
1633
+ ) ,
1634
+ MessageSendInstructions :: WithoutReplyPath {
1635
+ destination : Destination :: BlindedPath ( held_htlc_avail_reply_path)
1636
+ }
1637
+ ) . unwrap ( ) ;
1638
+
1639
+ let release_held_htlc_om = nodes[ 2 ] . onion_messenger . next_onion_message_for_peer ( nodes[ 0 ] . node . get_our_node_id ( ) ) . unwrap ( ) ;
1640
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 2 ] . node . get_our_node_id ( ) , & release_held_htlc_om) ;
1641
+
1642
+ // Check that we've queued the HTLCs of the async keysend payment.
1643
+ let htlc_updates = get_htlc_update_msgs ! ( nodes[ 0 ] , nodes[ 1 ] . node. get_our_node_id( ) ) ;
1644
+ assert_eq ! ( htlc_updates. update_add_htlcs. len( ) , 1 ) ;
1645
+ check_added_monitors ! ( nodes[ 0 ] , 1 ) ;
1646
+
1647
+ // Receiving a duplicate release_htlc message doesn't result in duplicate payment.
1648
+ nodes[ 0 ] . onion_messenger . handle_onion_message ( nodes[ 2 ] . node . get_our_node_id ( ) , & release_held_htlc_om) ;
1649
+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
1650
+ }
1651
+
1419
1652
fn secret_from_hex ( hex : & str ) -> SecretKey {
1420
1653
SecretKey :: from_slice ( & <Vec < u8 > >:: from_hex ( hex) . unwrap ( ) ) . unwrap ( )
1421
1654
}
0 commit comments