Skip to content

Commit 5f79078

Browse files
Matt Coralloclaude
andcommitted
Cover LSPS2 BOLT12 JIT payments
Exercise the BOLT12 receive flow with LSPS2 JIT channels via plain BOLT12 offers relying on the parameters negotiated at startup: the initial channel open, paying an offer after LSP restart, and wiping of stale parameters when restarting without the LSP configured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5672a5c commit 5f79078

1 file changed

Lines changed: 306 additions & 0 deletions

File tree

tests/integration_tests_rust.rs

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
21312437
async fn lsps2_client_trusts_lsp() {
21322438
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)