Skip to content

Commit 32394c0

Browse files
committed
lsps2: Test restored BOLT12 leases
Rebuild the receiver from its persisted store and pay the same long-lived offer again. Verify the pending offer survives and the exact cached lease is consumed before its replacement is negotiated. Co-Authored-By: HAL 9000
1 parent d9acd74 commit 32394c0

1 file changed

Lines changed: 93 additions & 4 deletions

File tree

tests/integration_tests_rust.rs

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,10 +3181,14 @@ async fn bolt12_lsps2_client_service_integration() {
31813181
let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone();
31823182

31833183
let client_config = random_config();
3184+
let client_store =
3185+
TestSyncStore::new(client_config.node_config.storage_dir_path.clone().into());
31843186
setup_builder!(client_builder, client_config.node_config);
31853187
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
3186-
client_builder.add_liquidity_source(service_node_id, service_addr, None, true);
3187-
let client_node = client_builder.build(client_config.node_entropy.into()).unwrap();
3188+
client_builder.add_liquidity_source(service_node_id, service_addr.clone(), None, true);
3189+
let client_node = client_builder
3190+
.build_with_store(client_config.node_entropy.into(), client_store.clone())
3191+
.unwrap();
31883192
client_node.start().unwrap();
31893193

31903194
let payer_config = random_config();
@@ -3276,8 +3280,93 @@ async fn bolt12_lsps2_client_service_integration() {
32763280
assert_eq!(client_node.list_channels().len(), client_channel_count);
32773281
assert_eq!(service_node.list_channels().len(), service_channel_count);
32783282

3279-
// The first JIT channel cannot carry this larger variable payment. The variable offer disables
3280-
// MPP, so the payer must use the fresh JIT path rather than splitting across both paths.
3283+
let persisted_lease_key =
3284+
tokio::time::timeout(std::time::Duration::from_secs(common::INTEROP_TIMEOUT_SECS), async {
3285+
loop {
3286+
let keys = KVStore::list(&client_store, "lsps2_leases", "").await.unwrap();
3287+
if let Some(key) = keys.first() {
3288+
return key.clone();
3289+
}
3290+
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
3291+
}
3292+
})
3293+
.await
3294+
.expect("replacement lease should be persisted");
3295+
assert!(KVStore::read(&client_store, "lsps2_leases", "", &persisted_lease_key).await.is_ok());
3296+
3297+
let client_node_id = client_node.node_id();
3298+
client_node.stop().unwrap();
3299+
drop(client_node);
3300+
tokio::time::timeout(std::time::Duration::from_secs(common::INTEROP_TIMEOUT_SECS), async {
3301+
while payer_node.list_peers().iter().any(|peer| peer.node_id == client_node_id)
3302+
|| service_node.list_peers().iter().any(|peer| peer.node_id == client_node_id)
3303+
{
3304+
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
3305+
}
3306+
})
3307+
.await
3308+
.expect("peers should observe the stopped client");
3309+
3310+
setup_builder!(restarted_client_builder, client_config.node_config);
3311+
restarted_client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
3312+
restarted_client_builder.add_liquidity_source(
3313+
service_node_id,
3314+
service_addr.clone(),
3315+
None,
3316+
true,
3317+
);
3318+
let client_node = restarted_client_builder
3319+
.build_with_store(client_config.node_entropy.into(), client_store.clone())
3320+
.unwrap();
3321+
assert_eq!(client_node.node_id(), client_node_id);
3322+
assert!(KVStore::read(&client_store, "lsps2_leases", "", &persisted_lease_key).await.is_ok());
3323+
client_node.start().unwrap();
3324+
let client_addr = client_node.listening_addresses().unwrap().first().unwrap().clone();
3325+
payer_node.connect(client_node.node_id(), client_addr, false).unwrap();
3326+
3327+
let restored_payment_id =
3328+
payer_node.bolt12_payment().send(&fixed_offer, None, None, None).unwrap();
3329+
expect_channel_pending_event!(service_node, client_node.node_id());
3330+
expect_channel_ready_event!(service_node, client_node.node_id());
3331+
expect_event!(service_node, PaymentForwarded);
3332+
loop {
3333+
let event = tokio::time::timeout(
3334+
std::time::Duration::from_secs(common::INTEROP_TIMEOUT_SECS),
3335+
client_node.next_event_async(),
3336+
)
3337+
.await
3338+
.expect("restored client should receive a payment event");
3339+
match event {
3340+
Event::ChannelPending { counterparty_node_id, .. } => {
3341+
assert_eq!(counterparty_node_id, service_node.node_id());
3342+
client_node.event_handled().unwrap();
3343+
break;
3344+
},
3345+
Event::PaymentReceived { payment_id, amount_msat, .. } => {
3346+
assert_eq!(payment_id, Some(ordinary_receiver_payment_id));
3347+
assert_eq!(amount_msat, ordinary_amount_msat);
3348+
client_node.event_handled().unwrap();
3349+
},
3350+
unexpected => panic!("Unexpected event after restoring client: {unexpected:?}"),
3351+
}
3352+
}
3353+
expect_channel_ready_event!(client_node, service_node.node_id());
3354+
expect_payment_successful_event!(payer_node, Some(restored_payment_id), None);
3355+
let restored_receiver_payment_id =
3356+
expect_payment_received_event!(client_node, fixed_received_msat).unwrap();
3357+
match client_node.payment(&restored_receiver_payment_id).unwrap().kind {
3358+
PaymentKind::Bolt12Offer { counterparty_skimmed_fee_msat, .. } => {
3359+
assert_eq!(counterparty_skimmed_fee_msat, Some(fixed_fee_msat));
3360+
},
3361+
_ => panic!("Unexpected payment kind"),
3362+
}
3363+
assert!(
3364+
KVStore::read(&client_store, "lsps2_leases", "", &persisted_lease_key).await.is_err(),
3365+
"the restored lease must be consumed instead of renegotiating"
3366+
);
3367+
3368+
// The fixed JIT channels cannot carry this larger variable payment. The variable offer disables
3369+
// MPP, so the payer must use the fresh JIT path rather than splitting across the existing paths.
32813370
let variable_amount_msat = 200_000_000;
32823371
let variable_offer =
32833372
client_node.bolt12_payment().receive_variable_amount("variable", None).unwrap();

0 commit comments

Comments
 (0)