@@ -2127,6 +2127,312 @@ async fn drop_in_async_context() {
21272127 node. stop ( ) . unwrap ( ) ;
21282128}
21292129
2130+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2131+ async fn lsps2_bolt12_payment_succeeds_after_lsp_restart ( ) {
2132+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2133+
2134+ let esplora_url = format ! ( "http://{}" , electrsd. esplora_url. as_ref( ) . unwrap( ) ) ;
2135+
2136+ let mut sync_config = EsploraSyncConfig :: default ( ) ;
2137+ sync_config. background_sync_config = None ;
2138+
2139+ let lsps2_service_config = LSPS2ServiceConfig {
2140+ require_token : None ,
2141+ advertise_service : false ,
2142+ channel_opening_fee_ppm : 10_000 ,
2143+ channel_over_provisioning_ppm : 100_000 ,
2144+ max_payment_size_msat : 1_000_000_000 ,
2145+ min_payment_size_msat : 0 ,
2146+ min_channel_lifetime : 100 ,
2147+ min_channel_opening_fee_msat : 0 ,
2148+ max_client_to_self_delay : 1024 ,
2149+ client_trusts_lsp : true ,
2150+ disable_client_reserve : false ,
2151+ } ;
2152+
2153+ let service_config = random_config ( true ) ;
2154+ setup_builder ! ( service_builder, service_config. node_config) ;
2155+ service_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2156+ service_builder. enable_liquidity_provider ( lsps2_service_config) ;
2157+ let service_node = service_builder. build ( service_config. node_entropy . into ( ) ) . unwrap ( ) ;
2158+ service_node. start ( ) . unwrap ( ) ;
2159+ let service_node_id = service_node. node_id ( ) ;
2160+ let service_addr = service_node. listening_addresses ( ) . unwrap ( ) . first ( ) . unwrap ( ) . clone ( ) ;
2161+
2162+ let client_config = random_config ( true ) ;
2163+ setup_builder ! ( client_builder, client_config. node_config) ;
2164+ client_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2165+ client_builder. add_liquidity_source ( service_node_id, service_addr. clone ( ) , None , true ) ;
2166+ let client_node = client_builder. build ( client_config. node_entropy . into ( ) ) . unwrap ( ) ;
2167+ client_node. start ( ) . unwrap ( ) ;
2168+
2169+ let payer_config = random_config ( true ) ;
2170+ setup_builder ! ( payer_builder, payer_config. node_config) ;
2171+ payer_builder. set_chain_source_esplora ( esplora_url, Some ( sync_config) ) ;
2172+ let payer_node = payer_builder. build ( payer_config. node_entropy . into ( ) ) . unwrap ( ) ;
2173+ payer_node. start ( ) . unwrap ( ) ;
2174+
2175+ let service_onchain_addr = service_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2176+ let client_onchain_addr = client_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2177+ let payer_onchain_addr = payer_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2178+
2179+ premine_and_distribute_funds (
2180+ & bitcoind. client ,
2181+ & electrsd. client ,
2182+ vec ! [ service_onchain_addr, client_onchain_addr, payer_onchain_addr] ,
2183+ Amount :: from_sat ( 10_000_000 ) ,
2184+ )
2185+ . await ;
2186+ service_node. sync_wallets ( ) . unwrap ( ) ;
2187+ client_node. sync_wallets ( ) . unwrap ( ) ;
2188+ payer_node. sync_wallets ( ) . unwrap ( ) ;
2189+
2190+ open_channel ( & payer_node, & service_node, 5_000_000 , false , & electrsd) . await ;
2191+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2192+ service_node. sync_wallets ( ) . unwrap ( ) ;
2193+ payer_node. sync_wallets ( ) . unwrap ( ) ;
2194+ expect_channel_ready_event ! ( payer_node, service_node. node_id( ) ) ;
2195+ expect_channel_ready_event ! ( service_node, payer_node. node_id( ) ) ;
2196+
2197+ let jit_amount_msat = 100_000_000 ;
2198+ let offer = client_node
2199+ . bolt12_payment ( )
2200+ . receive ( jit_amount_msat, "lsps2-bolt12-after-restart" , None , Some ( 1 ) )
2201+ . unwrap ( ) ;
2202+
2203+ service_node. stop ( ) . unwrap ( ) ;
2204+ service_node. start ( ) . unwrap ( ) ;
2205+
2206+ // Ensure peers are connected after the restart before paying the offer.
2207+ let _ = payer_node. connect ( service_node_id, service_addr. clone ( ) , false ) ;
2208+ let _ = client_node. connect ( service_node_id, service_addr, false ) ;
2209+
2210+ let payment_id = payer_node
2211+ . bolt12_payment ( )
2212+ . send ( & offer, Some ( 1 ) , Some ( "restart" . to_string ( ) ) , None )
2213+ . unwrap ( ) ;
2214+
2215+ expect_channel_pending_event ! ( service_node, client_node. node_id( ) ) ;
2216+ expect_channel_ready_event ! ( service_node, client_node. node_id( ) ) ;
2217+ expect_channel_pending_event ! ( client_node, service_node. node_id( ) ) ;
2218+ expect_channel_ready_event ! ( client_node, service_node. node_id( ) ) ;
2219+
2220+ let service_fee_msat = ( jit_amount_msat * 10_000 ) / 1_000_000 ;
2221+ let expected_received_amount_msat = jit_amount_msat - service_fee_msat;
2222+
2223+ expect_payment_successful_event ! ( payer_node, Some ( payment_id) , None ) ;
2224+ expect_payment_received_event ! ( client_node, expected_received_amount_msat) ;
2225+ }
2226+
2227+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2228+ async fn lsps2_bolt12_jit_channel_opens_successfully ( ) {
2229+ // Verify the full BOLT12 + LSPS2 JIT channel flow: a client with no pre-existing channels
2230+ // creates a plain BOLT12 offer, a payer pays it, and the LSP opens a channel just-in-time.
2231+ // The LSPS2 parameters negotiated at startup are injected into the offer's invoices as
2232+ // JIT-channel blinded payment paths by the client's router.
2233+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2234+
2235+ let esplora_url = format ! ( "http://{}" , electrsd. esplora_url. as_ref( ) . unwrap( ) ) ;
2236+
2237+ let mut sync_config = EsploraSyncConfig :: default ( ) ;
2238+ sync_config. background_sync_config = None ;
2239+
2240+ let lsps2_service_config = LSPS2ServiceConfig {
2241+ require_token : None ,
2242+ advertise_service : false ,
2243+ channel_opening_fee_ppm : 10_000 ,
2244+ channel_over_provisioning_ppm : 100_000 ,
2245+ max_payment_size_msat : 1_000_000_000 ,
2246+ min_payment_size_msat : 0 ,
2247+ min_channel_lifetime : 100 ,
2248+ min_channel_opening_fee_msat : 0 ,
2249+ max_client_to_self_delay : 1024 ,
2250+ client_trusts_lsp : true ,
2251+ disable_client_reserve : false ,
2252+ } ;
2253+
2254+ let service_config = random_config ( true ) ;
2255+ setup_builder ! ( service_builder, service_config. node_config) ;
2256+ service_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2257+ service_builder. enable_liquidity_provider ( lsps2_service_config) ;
2258+ let service_node = service_builder. build ( service_config. node_entropy . into ( ) ) . unwrap ( ) ;
2259+ service_node. start ( ) . unwrap ( ) ;
2260+ let service_node_id = service_node. node_id ( ) ;
2261+ let service_addr = service_node. listening_addresses ( ) . unwrap ( ) . first ( ) . unwrap ( ) . clone ( ) ;
2262+
2263+ let client_config = random_config ( true ) ;
2264+ setup_builder ! ( client_builder, client_config. node_config) ;
2265+ client_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2266+ client_builder. add_liquidity_source ( service_node_id, service_addr. clone ( ) , None , true ) ;
2267+ let client_node = client_builder. build ( client_config. node_entropy . into ( ) ) . unwrap ( ) ;
2268+ client_node. start ( ) . unwrap ( ) ;
2269+
2270+ let payer_config = random_config ( true ) ;
2271+ setup_builder ! ( payer_builder, payer_config. node_config) ;
2272+ payer_builder. set_chain_source_esplora ( esplora_url, Some ( sync_config) ) ;
2273+ let payer_node = payer_builder. build ( payer_config. node_entropy . into ( ) ) . unwrap ( ) ;
2274+ payer_node. start ( ) . unwrap ( ) ;
2275+
2276+ let service_onchain_addr = service_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2277+ let client_onchain_addr = client_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2278+ let payer_onchain_addr = payer_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2279+
2280+ premine_and_distribute_funds (
2281+ & bitcoind. client ,
2282+ & electrsd. client ,
2283+ vec ! [ service_onchain_addr, client_onchain_addr, payer_onchain_addr] ,
2284+ Amount :: from_sat ( 10_000_000 ) ,
2285+ )
2286+ . await ;
2287+ service_node. sync_wallets ( ) . unwrap ( ) ;
2288+ client_node. sync_wallets ( ) . unwrap ( ) ;
2289+ payer_node. sync_wallets ( ) . unwrap ( ) ;
2290+
2291+ open_channel ( & payer_node, & service_node, 5_000_000 , false , & electrsd) . await ;
2292+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2293+ service_node. sync_wallets ( ) . unwrap ( ) ;
2294+ payer_node. sync_wallets ( ) . unwrap ( ) ;
2295+ expect_channel_ready_event ! ( payer_node, service_node. node_id( ) ) ;
2296+ expect_channel_ready_event ! ( service_node, payer_node. node_id( ) ) ;
2297+
2298+ assert_eq ! (
2299+ service_node. list_channels( ) . len( ) ,
2300+ 1 ,
2301+ "Only payer-service channel should exist before JIT flow"
2302+ ) ;
2303+
2304+ let jit_amount_msat = 100_000_000 ;
2305+ let offer = client_node
2306+ . bolt12_payment ( )
2307+ . receive ( jit_amount_msat, "jit-payment" , None , Some ( 1 ) )
2308+ . unwrap ( ) ;
2309+
2310+ let payment_id =
2311+ payer_node. bolt12_payment ( ) . send ( & offer, Some ( 1 ) , Some ( "pay" . to_string ( ) ) , None ) . unwrap ( ) ;
2312+
2313+ expect_channel_pending_event ! ( service_node, client_node. node_id( ) ) ;
2314+ expect_channel_ready_event ! ( service_node, client_node. node_id( ) ) ;
2315+ expect_channel_pending_event ! ( client_node, service_node. node_id( ) ) ;
2316+ expect_channel_ready_event ! ( client_node, service_node. node_id( ) ) ;
2317+
2318+ let service_fee_msat = ( jit_amount_msat * 10_000 ) / 1_000_000 ;
2319+ let expected_received = jit_amount_msat - service_fee_msat;
2320+ expect_payment_successful_event ! ( payer_node, Some ( payment_id) , None ) ;
2321+ expect_payment_received_event ! ( client_node, expected_received) ;
2322+
2323+ // The LSP should now have two channels: payer<->service and service<->client.
2324+ assert_eq ! (
2325+ service_node. list_channels( ) . len( ) ,
2326+ 2 ,
2327+ "JIT channel should have been opened alongside the payer channel"
2328+ ) ;
2329+ }
2330+
2331+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2332+ async fn lsps2_bolt12_params_wiped_for_unconfigured_lsp ( ) {
2333+ // Verify that the LSPS2 invoice parameters negotiated at startup are persisted, and wiped
2334+ // early on restart if the LSP is no longer configured as a liquidity source.
2335+ let ( _bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2336+ let esplora_url = format ! ( "http://{}" , electrsd. esplora_url. as_ref( ) . unwrap( ) ) ;
2337+
2338+ let mut sync_config = EsploraSyncConfig :: default ( ) ;
2339+ sync_config. background_sync_config = None ;
2340+
2341+ let lsps2_service_config = LSPS2ServiceConfig {
2342+ require_token : None ,
2343+ advertise_service : false ,
2344+ channel_opening_fee_ppm : 10_000 ,
2345+ channel_over_provisioning_ppm : 100_000 ,
2346+ max_payment_size_msat : 1_000_000_000 ,
2347+ min_payment_size_msat : 0 ,
2348+ min_channel_lifetime : 100 ,
2349+ min_channel_opening_fee_msat : 0 ,
2350+ max_client_to_self_delay : 1024 ,
2351+ client_trusts_lsp : true ,
2352+ disable_client_reserve : false ,
2353+ } ;
2354+
2355+ let service_config = random_config ( true ) ;
2356+ setup_builder ! ( service_builder, service_config. node_config) ;
2357+ service_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2358+ service_builder. enable_liquidity_provider ( lsps2_service_config) ;
2359+ let service_node = service_builder. build ( service_config. node_entropy . into ( ) ) . unwrap ( ) ;
2360+ service_node. start ( ) . unwrap ( ) ;
2361+ let service_node_id = service_node. node_id ( ) ;
2362+ let service_addr = service_node. listening_addresses ( ) . unwrap ( ) . first ( ) . unwrap ( ) . clone ( ) ;
2363+
2364+ let client_config = random_config ( true ) ;
2365+ let client_store =
2366+ TestSyncStore :: new ( client_config. node_config . storage_dir_path . clone ( ) . into ( ) ) ;
2367+ setup_builder ! ( client_builder, client_config. node_config) ;
2368+ client_builder. set_chain_source_esplora ( esplora_url. clone ( ) , Some ( sync_config) ) ;
2369+ client_builder. add_liquidity_source ( service_node_id, service_addr. clone ( ) , None , true ) ;
2370+ let client_node = client_builder
2371+ . build_with_store ( client_config. node_entropy . into ( ) , client_store. clone ( ) )
2372+ . unwrap ( ) ;
2373+ client_node. start ( ) . unwrap ( ) ;
2374+
2375+ let params_key = service_node_id. to_string ( ) ;
2376+ let read_params_blob = || async {
2377+ use ldk_node:: lightning:: util:: persist:: KVStore ;
2378+ use ldk_node:: lightning_liquidity:: persist:: {
2379+ LIQUIDITY_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE ,
2380+ LSPS2_CLIENT_PERSISTENCE_SECONDARY_NAMESPACE ,
2381+ } ;
2382+ client_store
2383+ . read (
2384+ LIQUIDITY_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE ,
2385+ LSPS2_CLIENT_PERSISTENCE_SECONDARY_NAMESPACE ,
2386+ & params_key,
2387+ )
2388+ . await
2389+ . ok ( )
2390+ } ;
2391+
2392+ // Wait until the parameters negotiated at startup were persisted.
2393+ let mut negotiated_params_blob = None ;
2394+ for _ in 0 ..100 {
2395+ if let Some ( blob) = read_params_blob ( ) . await {
2396+ negotiated_params_blob = Some ( blob) ;
2397+ break ;
2398+ }
2399+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) . await ;
2400+ }
2401+ let negotiated_params_blob =
2402+ negotiated_params_blob. expect ( "LSPS2 params should be negotiated and persisted at startup" ) ;
2403+ assert ! ( !negotiated_params_blob. is_empty( ) ) ;
2404+
2405+ client_node. stop ( ) . unwrap ( ) ;
2406+ drop ( client_node) ;
2407+
2408+ // Restart the client without the LSP configured as a liquidity source. The previously
2409+ // negotiated parameters must be wiped during build, before the node starts up.
2410+ setup_builder ! ( client_builder, client_config. node_config) ;
2411+ client_builder. set_chain_source_esplora ( esplora_url, Some ( sync_config) ) ;
2412+ let client_node = client_builder
2413+ . build_with_store ( client_config. node_entropy . into ( ) , client_store. clone ( ) )
2414+ . unwrap ( ) ;
2415+
2416+ // The stored blob is rewritten with the parameters removed (the now-empty entry itself is
2417+ // garbage collected later). As the parameters make up virtually all of the encoded peer
2418+ // state, check the remaining blob is just a few bytes of empty TLV stream. Note the wipe
2419+ // happens in a background task, so we might need to wait for it briefly.
2420+ let mut wiped = false ;
2421+ for _ in 0 ..100 {
2422+ let wiped_params_blob = read_params_blob ( ) . await . unwrap ( ) ;
2423+ if wiped_params_blob. len ( ) < 10 {
2424+ assert ! ( wiped_params_blob. len( ) < negotiated_params_blob. len( ) ) ;
2425+ wiped = true ;
2426+ break ;
2427+ }
2428+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) . await ;
2429+ }
2430+ assert ! ( wiped, "expected stale LSPS2 params to be wiped during node building" ) ;
2431+
2432+ client_node. start ( ) . unwrap ( ) ;
2433+ client_node. stop ( ) . unwrap ( ) ;
2434+ }
2435+
21302436#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
21312437async fn lsps2_client_trusts_lsp ( ) {
21322438 let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
0 commit comments