@@ -7,13 +7,15 @@ use common::{
77 get_lsps_message, LSPSNodes , LSPSNodesWithPayer , LiquidityNode ,
88} ;
99
10+ use lightning:: blinded_path:: IntroductionNode ;
1011use lightning:: events:: { ClosureReason , Event } ;
1112use lightning:: get_event_msg;
1213use lightning:: ln:: channelmanager:: { OptionalBolt11PaymentParams , PaymentId } ;
1314use lightning:: ln:: functional_test_utils:: * ;
1415use lightning:: ln:: msgs:: BaseMessageHandler ;
1516use lightning:: ln:: msgs:: ChannelMessageHandler ;
1617use lightning:: ln:: msgs:: MessageSendEvent ;
18+ use lightning:: ln:: msgs:: OnionMessageHandler ;
1719use lightning:: ln:: types:: ChannelId ;
1820
1921use lightning_liquidity:: events:: LiquidityEvent ;
@@ -2000,6 +2002,304 @@ fn htlc_timeout_before_client_claim_results_in_handling_failed() {
20002002 payer_node. chain_monitor . added_monitors . lock ( ) . unwrap ( ) . clear ( ) ;
20012003}
20022004
2005+ #[ test]
2006+ fn bolt12_lsps2_end_to_end_test ( ) {
2007+ // End-to-end test of the BOLT12 + LSPS2 JIT channel flow. Three nodes: payer, service, client.
2008+ // client_trusts_lsp=true; funding transaction broadcast happens after client claims the HTLC.
2009+ //
2010+ // 1. Create a channel between payer and service
2011+ // 2. Do the LSPS2 ceremony between client and service to get an intercept SCID
2012+ // 3. Client creates a BOLT12 offer (with manually_handle_bolt12_invoice_requests = true)
2013+ // 4. Payer pays for the offer (sends InvoiceRequest via onion message)
2014+ // 5. Client receives InvoiceRequestReceived event and creates a BOLT12 invoice with blinded
2015+ // payment paths through the service's intercept SCID
2016+ // 6. Payer receives the invoice and sends payment through the blinded path
2017+ // 7. Service intercepts the HTLC and creates a JIT channel to the client
2018+ // 8. Service forwards the HTLC to the client via the JIT channel
2019+ // 9. Client claims the payment, service broadcasts the funding tx
2020+ let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
2021+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
2022+
2023+ let mut service_node_config = test_default_channel_config ( ) ;
2024+ service_node_config. htlc_interception_flags = HTLCInterceptionFlags :: ToInterceptSCIDs as u8 ;
2025+
2026+ let mut client_node_config = test_default_channel_config ( ) ;
2027+ client_node_config. manually_accept_inbound_channels = true ;
2028+ client_node_config. channel_config . accept_underpaying_htlcs = true ;
2029+ client_node_config. manually_handle_bolt12_invoice_requests = true ;
2030+
2031+ let node_chanmgrs = create_node_chanmgrs (
2032+ 3 ,
2033+ & node_cfgs,
2034+ & [ Some ( service_node_config) , Some ( client_node_config) , None ] ,
2035+ ) ;
2036+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
2037+ let ( lsps_nodes, promise_secret) = setup_test_lsps2_nodes_with_payer ( nodes) ;
2038+ let LSPSNodesWithPayer { ref service_node, ref client_node, ref payer_node } = lsps_nodes;
2039+
2040+ let payer_node_id = payer_node. node . get_our_node_id ( ) ;
2041+ let service_node_id = service_node. inner . node . get_our_node_id ( ) ;
2042+ let client_node_id = client_node. inner . node . get_our_node_id ( ) ;
2043+
2044+ let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
2045+
2046+ // Create channel between payer and service
2047+ create_chan_between_nodes_with_value ( & payer_node, & service_node. inner , 2_000_000 , 100_000 ) ;
2048+
2049+ // LSPS2 ceremony: client negotiates JIT channel parameters with service
2050+ let intercept_scid = service_node. node . get_intercept_scid ( ) ;
2051+ let user_channel_id = 42 ;
2052+ let cltv_expiry_delta: u32 = 144 ;
2053+ let payment_size_msat = Some ( 1_000_000 ) ;
2054+ let fee_base_msat = 1000 ;
2055+
2056+ execute_lsps2_dance (
2057+ & lsps_nodes,
2058+ intercept_scid,
2059+ user_channel_id,
2060+ cltv_expiry_delta,
2061+ promise_secret,
2062+ payment_size_msat,
2063+ fee_base_msat,
2064+ ) ;
2065+
2066+ // Disconnect payer from client to ensure deterministic onion message routing through service.
2067+ // This guarantees that both the offer's blinded message paths and the payer's reply paths
2068+ // route through the service node.
2069+ payer_node. node . peer_disconnected ( client_node_id) ;
2070+ client_node. node . peer_disconnected ( payer_node_id) ;
2071+ payer_node. onion_messenger . peer_disconnected ( client_node_id) ;
2072+ client_node. onion_messenger . peer_disconnected ( payer_node_id) ;
2073+
2074+ // Client creates a BOLT12 offer. Since client's only remaining peer is service,
2075+ // the blinded message paths will use service as the introduction node.
2076+ let offer = client_node
2077+ . node
2078+ . create_offer_builder ( )
2079+ . unwrap ( )
2080+ . amount_msats ( payment_size_msat. unwrap ( ) )
2081+ . build ( )
2082+ . unwrap ( ) ;
2083+
2084+ // Payer initiates payment for the offer
2085+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
2086+ payer_node. node . pay_for_offer ( & offer, None , payment_id, Default :: default ( ) ) . unwrap ( ) ;
2087+
2088+ // Route InvoiceRequest: payer -> service -> client
2089+ let onion_msg = payer_node
2090+ . onion_messenger
2091+ . next_onion_message_for_peer ( service_node_id)
2092+ . expect ( "Payer should send InvoiceRequest toward service" ) ;
2093+ service_node. onion_messenger . handle_onion_message ( payer_node_id, & onion_msg) ;
2094+
2095+ let fwd_msg = service_node
2096+ . onion_messenger
2097+ . next_onion_message_for_peer ( client_node_id)
2098+ . expect ( "Service should forward InvoiceRequest to client" ) ;
2099+ client_node. onion_messenger . handle_onion_message ( service_node_id, & fwd_msg) ;
2100+
2101+ // Client should NOT auto-create an invoice (manually_handle_bolt12_invoice_requests = true)
2102+ assert ! ( client_node. onion_messenger. next_onion_message_for_peer( service_node_id) . is_none( ) ) ;
2103+
2104+ // Client receives InvoiceRequestReceived event
2105+ let mut events = client_node. node . get_and_clear_pending_events ( ) ;
2106+ assert_eq ! ( events. len( ) , 1 ) ;
2107+
2108+ let ( invoice_request, context, responder) = match events. pop ( ) . unwrap ( ) {
2109+ Event :: InvoiceRequestReceived { invoice_request, context, responder } => {
2110+ ( invoice_request, context, responder)
2111+ } ,
2112+ other => panic ! ( "Expected Event::InvoiceRequestReceived, got: {:?}" , other) ,
2113+ } ;
2114+
2115+ // Client creates a BOLT12 invoice with blinded payment paths through the service's
2116+ // intercept SCID, simulating the LSPS2 JIT channel flow.
2117+ let invoice = client_node
2118+ . node
2119+ . send_bolt12_invoice_for_intercept_scid (
2120+ invoice_request,
2121+ context,
2122+ responder,
2123+ service_node_id,
2124+ intercept_scid,
2125+ cltv_expiry_delta as u16 ,
2126+ 3600 ,
2127+ )
2128+ . unwrap ( ) ;
2129+
2130+ // Verify the invoice has the correct structure: blinded payment paths with service
2131+ // as the introduction node (the LSP that will open the JIT channel).
2132+ assert_eq ! ( invoice. amount_msats( ) , payment_size_msat. unwrap( ) ) ;
2133+ assert ! ( !invoice. payment_paths( ) . is_empty( ) ) ;
2134+ for path in invoice. payment_paths ( ) {
2135+ assert_eq ! ( path. introduction_node( ) , & IntroductionNode :: NodeId ( service_node_id) ) ;
2136+ }
2137+
2138+ // Route Invoice: client -> service -> payer
2139+ let onion_msg = client_node
2140+ . onion_messenger
2141+ . next_onion_message_for_peer ( service_node_id)
2142+ . expect ( "Client should send Invoice toward service" ) ;
2143+ service_node. onion_messenger . handle_onion_message ( client_node_id, & onion_msg) ;
2144+
2145+ let fwd_msg = service_node
2146+ . onion_messenger
2147+ . next_onion_message_for_peer ( payer_node_id)
2148+ . expect ( "Service should forward Invoice to payer" ) ;
2149+ payer_node. onion_messenger . handle_onion_message ( service_node_id, & fwd_msg) ;
2150+
2151+ // Payer processes the invoice and starts the payment automatically
2152+ check_added_monitors ( & payer_node, 1 ) ;
2153+ let events = payer_node. node . get_and_clear_pending_msg_events ( ) ;
2154+ assert_eq ! ( events. len( ) , 1 ) ;
2155+ let ev = SendEvent :: from_event ( events[ 0 ] . clone ( ) ) ;
2156+
2157+ // Payment goes to service via the blinded payment path (through intercept SCID)
2158+ service_node. inner . node . handle_update_add_htlc ( payer_node_id, & ev. msgs [ 0 ] ) ;
2159+ do_commitment_signed_dance ( & service_node. inner , & payer_node, & ev. commitment_msg , false , true ) ;
2160+ service_node. inner . node . process_pending_htlc_forwards ( ) ;
2161+
2162+ // Service intercepts the HTLC (destined for intercept SCID)
2163+ let events = service_node. inner . node . get_and_clear_pending_events ( ) ;
2164+ assert_eq ! ( events. len( ) , 1 ) ;
2165+ let ( payment_hash, expected_outbound_amount_msat) = match & events[ 0 ] {
2166+ Event :: HTLCIntercepted {
2167+ intercept_id,
2168+ requested_next_hop_scid,
2169+ payment_hash,
2170+ expected_outbound_amount_msat,
2171+ ..
2172+ } => {
2173+ assert_eq ! ( * requested_next_hop_scid, intercept_scid) ;
2174+
2175+ service_handler
2176+ . htlc_intercepted (
2177+ * requested_next_hop_scid,
2178+ * intercept_id,
2179+ * expected_outbound_amount_msat,
2180+ * payment_hash,
2181+ )
2182+ . unwrap ( ) ;
2183+ ( * payment_hash, expected_outbound_amount_msat)
2184+ } ,
2185+ other => panic ! ( "Expected HTLCIntercepted event, got: {:?}" , other) ,
2186+ } ;
2187+
2188+ // Service emits OpenChannel event with the correct fee deduction
2189+ let open_channel_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
2190+ match open_channel_event {
2191+ LiquidityEvent :: LSPS2Service ( LSPS2ServiceEvent :: OpenChannel {
2192+ their_network_key,
2193+ amt_to_forward_msat,
2194+ opening_fee_msat,
2195+ user_channel_id : uc_id,
2196+ intercept_scid : iscd,
2197+ } ) => {
2198+ assert_eq ! ( their_network_key, client_node_id) ;
2199+ assert_eq ! ( amt_to_forward_msat, payment_size_msat. unwrap( ) - fee_base_msat) ;
2200+ assert_eq ! ( opening_fee_msat, fee_base_msat) ;
2201+ assert_eq ! ( uc_id, user_channel_id) ;
2202+ assert_eq ! ( iscd, intercept_scid) ;
2203+ } ,
2204+ other => panic ! ( "Expected OpenChannel event, got: {:?}" , other) ,
2205+ } ;
2206+
2207+ // Create JIT channel with manual broadcast (client_trusts_lsp = true)
2208+ let result =
2209+ service_handler. channel_needs_manual_broadcast ( user_channel_id, & client_node_id) . unwrap ( ) ;
2210+ assert ! ( result, "Channel should require manual broadcast" ) ;
2211+
2212+ let ( channel_id, funding_tx) = create_channel_with_manual_broadcast (
2213+ & service_node_id,
2214+ & client_node_id,
2215+ & service_node,
2216+ & client_node,
2217+ user_channel_id,
2218+ expected_outbound_amount_msat,
2219+ true ,
2220+ ) ;
2221+
2222+ // Service marks channel as ready and forwards the intercepted HTLC
2223+ service_handler. channel_ready ( user_channel_id, & channel_id, & client_node_id) . unwrap ( ) ;
2224+ service_node. inner . node . process_pending_htlc_forwards ( ) ;
2225+
2226+ // Forward the HTLC to client on the JIT channel
2227+ let pay_event = {
2228+ {
2229+ let mut added_monitors =
2230+ service_node. inner . chain_monitor . added_monitors . lock ( ) . unwrap ( ) ;
2231+ assert_eq ! ( added_monitors. len( ) , 1 ) ;
2232+ added_monitors. clear ( ) ;
2233+ }
2234+ let mut events = service_node. inner . node . get_and_clear_pending_msg_events ( ) ;
2235+ assert_eq ! ( events. len( ) , 1 ) ;
2236+ SendEvent :: from_event ( events. remove ( 0 ) )
2237+ } ;
2238+
2239+ client_node. inner . node . handle_update_add_htlc ( service_node_id, & pay_event. msgs [ 0 ] ) ;
2240+ do_commitment_signed_dance (
2241+ & client_node. inner ,
2242+ & service_node. inner ,
2243+ & pay_event. commitment_msg ,
2244+ false ,
2245+ true ,
2246+ ) ;
2247+ client_node. inner . node . process_pending_htlc_forwards ( ) ;
2248+
2249+ // Client sees PaymentClaimable
2250+ let client_events = client_node. inner . node . get_and_clear_pending_events ( ) ;
2251+ assert_eq ! ( client_events. len( ) , 1 ) ;
2252+ let preimage = match & client_events[ 0 ] {
2253+ Event :: PaymentClaimable { payment_hash : ph, purpose, .. } => {
2254+ assert_eq ! ( * ph, payment_hash) ;
2255+ purpose. preimage ( )
2256+ } ,
2257+ other => panic ! ( "Expected PaymentClaimable event on client, got: {:?}" , other) ,
2258+ } ;
2259+
2260+ // Before client claims, service should not have broadcasted the funding tx
2261+ let broadcasted = service_node. inner . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
2262+ assert ! ( broadcasted. is_empty( ) , "There should be no broadcasted txs yet" ) ;
2263+ drop ( broadcasted) ;
2264+
2265+ // Client claims the payment
2266+ client_node. inner . node . claim_funds ( preimage. unwrap ( ) ) ;
2267+
2268+ claim_and_assert_forwarded_only (
2269+ & payer_node,
2270+ & service_node. inner ,
2271+ & client_node. inner ,
2272+ preimage. unwrap ( ) ,
2273+ ) ;
2274+
2275+ // Service should emit PaymentForwarded
2276+ let service_events = service_node. node . get_and_clear_pending_events ( ) ;
2277+ assert_eq ! ( service_events. len( ) , 1 ) ;
2278+
2279+ let total_fee_msat = match service_events[ 0 ] . clone ( ) {
2280+ Event :: PaymentForwarded {
2281+ prev_node_id,
2282+ next_node_id,
2283+ skimmed_fee_msat,
2284+ total_fee_earned_msat,
2285+ ..
2286+ } => {
2287+ assert_eq ! ( prev_node_id, Some ( payer_node_id) ) ;
2288+ assert_eq ! ( next_node_id, Some ( client_node_id) ) ;
2289+ service_handler. payment_forwarded ( channel_id, skimmed_fee_msat. unwrap_or ( 0 ) ) . unwrap ( ) ;
2290+ Some ( total_fee_earned_msat. unwrap ( ) - skimmed_fee_msat. unwrap ( ) )
2291+ } ,
2292+ _ => panic ! ( "Expected PaymentForwarded event, got: {:?}" , service_events[ 0 ] ) ,
2293+ } ;
2294+
2295+ // Service should have broadcasted the funding tx after client claimed
2296+ let broadcasted = service_node. inner . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
2297+ assert ! ( broadcasted. iter( ) . any( |b| b. compute_txid( ) == funding_tx. compute_txid( ) ) ) ;
2298+
2299+ // Payer should have the PaymentSent event
2300+ expect_payment_sent ( & payer_node, preimage. unwrap ( ) , Some ( total_fee_msat) , true , true ) ;
2301+ }
2302+
20032303fn claim_and_assert_forwarded_only < ' a , ' b , ' c > (
20042304 payer_node : & lightning:: ln:: functional_test_utils:: Node < ' a , ' b , ' c > ,
20052305 service_node : & lightning:: ln:: functional_test_utils:: Node < ' a , ' b , ' c > ,
0 commit comments