Skip to content

Commit 74b4712

Browse files
test: add int tests for failed & abandoned open
Add integration tests to verify channel open reset and pruning handlers. Tests cover: - channel_open_failed resetting state to allow retry. - channel_open_failed error on invalid state. - channel_open_abandoned pruning all open state. - error handling for nonexistent channels.
1 parent cf49410 commit 74b4712

File tree

1 file changed

+289
-24
lines changed

1 file changed

+289
-24
lines changed

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 289 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,54 @@ use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
1111
use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams;
1212
use lightning_liquidity::lsps2::service::LSPS2ServiceConfig;
1313
use lightning_liquidity::lsps2::utils::is_valid_opening_fee_params;
14-
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
1514

16-
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
15+
use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA};
1716
use lightning::ln::peer_handler::CustomMessageHandler;
1817
use lightning::log_error;
1918
use lightning::routing::router::{RouteHint, RouteHintHop};
19+
use lightning::util::errors::APIError;
2020
use lightning::util::logger::Logger;
2121

2222
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
2323

24+
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
25+
use lightning_types::payment::PaymentHash;
26+
2427
use bitcoin::hashes::{sha256, Hash};
25-
use bitcoin::secp256k1::{PublicKey, Secp256k1};
28+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
2629
use bitcoin::Network;
2730

2831
use std::str::FromStr;
2932
use std::time::Duration;
3033

34+
fn setup_test_lsps2(
35+
) -> (bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey, Node, Node, [u8; 32]) {
36+
let promise_secret = [42; 32];
37+
let signing_key = SecretKey::from_slice(&promise_secret).unwrap();
38+
let lsps2_service_config = LSPS2ServiceConfig { promise_secret };
39+
let service_config = LiquidityServiceConfig {
40+
#[cfg(lsps1_service)]
41+
lsps1_service_config: None,
42+
lsps2_service_config: Some(lsps2_service_config),
43+
advertise_service: true,
44+
};
45+
46+
let lsps2_client_config = LSPS2ClientConfig::default();
47+
let client_config = LiquidityClientConfig {
48+
lsps1_client_config: None,
49+
lsps2_client_config: Some(lsps2_client_config),
50+
};
51+
52+
let (service_node, client_node) =
53+
create_service_and_client_nodes("webhook_registration_flow", service_config, client_config);
54+
55+
let secp = bitcoin::secp256k1::Secp256k1::new();
56+
let service_node_id = bitcoin::secp256k1::PublicKey::from_secret_key(&secp, &signing_key);
57+
let client_node_id = client_node.channel_manager.get_our_node_id();
58+
59+
(service_node_id, client_node_id, service_node, client_node, promise_secret)
60+
}
61+
3162
fn create_jit_invoice(
3263
node: &Node, service_node_id: PublicKey, intercept_scid: u64, cltv_expiry_delta: u32,
3364
payment_size_msat: Option<u64>, description: &str, expiry_secs: u32,
@@ -82,29 +113,11 @@ fn create_jit_invoice(
82113

83114
#[test]
84115
fn invoice_generation_flow() {
85-
let promise_secret = [42; 32];
86-
let lsps2_service_config = LSPS2ServiceConfig { promise_secret };
87-
let service_config = LiquidityServiceConfig {
88-
#[cfg(lsps1_service)]
89-
lsps1_service_config: None,
90-
lsps2_service_config: Some(lsps2_service_config),
91-
advertise_service: true,
92-
};
93-
94-
let lsps2_client_config = LSPS2ClientConfig::default();
95-
let client_config = LiquidityClientConfig {
96-
lsps1_client_config: None,
97-
lsps2_client_config: Some(lsps2_client_config),
98-
};
99-
100-
let (service_node, client_node) =
101-
create_service_and_client_nodes("invoice_generation_flow", service_config, client_config);
102-
103-
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
104-
let service_node_id = service_node.channel_manager.get_our_node_id();
116+
let (service_node_id, client_node_id, service_node, client_node, promise_secret) =
117+
setup_test_lsps2();
105118

106119
let client_handler = client_node.liquidity_manager.lsps2_client_handler().unwrap();
107-
let client_node_id = client_node.channel_manager.get_our_node_id();
120+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
108121

109122
let get_info_request_id = client_handler.request_opening_params(service_node_id, None);
110123
let get_info_request = get_lsps_message!(client_node, service_node_id);
@@ -239,3 +252,255 @@ fn invoice_generation_flow() {
239252
)
240253
.unwrap();
241254
}
255+
256+
#[test]
257+
fn channel_open_failed() {
258+
let (service_node_id, client_node_id, service_node, client_node, _) = setup_test_lsps2();
259+
260+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
261+
262+
let get_info_request_id = client_node
263+
.liquidity_manager
264+
.lsps2_client_handler()
265+
.unwrap()
266+
.request_opening_params(service_node_id, None);
267+
let get_info_request = get_lsps_message!(client_node, service_node_id);
268+
service_node.liquidity_manager.handle_custom_message(get_info_request, client_node_id).unwrap();
269+
270+
let _get_info_event = service_node.liquidity_manager.next_event().unwrap();
271+
272+
let raw_opening_params = LSPS2RawOpeningFeeParams {
273+
min_fee_msat: 100,
274+
proportional: 21,
275+
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
276+
min_lifetime: 144,
277+
max_client_to_self_delay: 128,
278+
min_payment_size_msat: 1,
279+
max_payment_size_msat: 100_000_000,
280+
};
281+
service_handler
282+
.opening_fee_params_generated(
283+
&client_node_id,
284+
get_info_request_id.clone(),
285+
vec![raw_opening_params],
286+
)
287+
.unwrap();
288+
289+
let get_info_response = get_lsps_message!(service_node, client_node_id);
290+
client_node
291+
.liquidity_manager
292+
.handle_custom_message(get_info_response, service_node_id)
293+
.unwrap();
294+
295+
let opening_fee_params = match client_node.liquidity_manager.next_event().unwrap() {
296+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady {
297+
opening_fee_params_menu,
298+
..
299+
}) => opening_fee_params_menu.first().unwrap().clone(),
300+
_ => panic!("Unexpected event"),
301+
};
302+
303+
let payment_size_msat = Some(1_000_000);
304+
let buy_request_id = client_node
305+
.liquidity_manager
306+
.lsps2_client_handler()
307+
.unwrap()
308+
.select_opening_params(service_node_id, payment_size_msat, opening_fee_params.clone())
309+
.unwrap();
310+
let buy_request = get_lsps_message!(client_node, service_node_id);
311+
service_node.liquidity_manager.handle_custom_message(buy_request, client_node_id).unwrap();
312+
313+
let _buy_event = service_node.liquidity_manager.next_event().unwrap();
314+
let user_channel_id = 42;
315+
let cltv_expiry_delta = 144;
316+
let intercept_scid = service_node.channel_manager.get_intercept_scid();
317+
let client_trusts_lsp = true;
318+
319+
service_handler
320+
.invoice_parameters_generated(
321+
&client_node_id,
322+
buy_request_id.clone(),
323+
intercept_scid,
324+
cltv_expiry_delta,
325+
client_trusts_lsp,
326+
user_channel_id,
327+
)
328+
.unwrap();
329+
330+
let buy_response = get_lsps_message!(service_node, client_node_id);
331+
client_node.liquidity_manager.handle_custom_message(buy_response, service_node_id).unwrap();
332+
let _invoice_params_event = client_node.liquidity_manager.next_event().unwrap();
333+
334+
// Test calling channel_open_failed in invalid state (before HTLC interception)
335+
let result = service_handler.channel_open_failed(&client_node_id, user_channel_id);
336+
assert!(result.is_err());
337+
match result.unwrap_err() {
338+
APIError::APIMisuseError { err } => {
339+
assert!(err.contains("Channel is not in the PendingChannelOpen state."));
340+
},
341+
other => panic!("Unexpected error type: {:?}", other),
342+
}
343+
344+
let htlc_amount_msat = 1_000_000;
345+
let intercept_id = InterceptId([0; 32]);
346+
let payment_hash = PaymentHash([1; 32]);
347+
348+
// This should trigger an OpenChannel event
349+
service_handler
350+
.htlc_intercepted(intercept_scid, intercept_id, htlc_amount_msat, payment_hash)
351+
.unwrap();
352+
353+
let _ = match service_node.liquidity_manager.next_event().unwrap() {
354+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel {
355+
user_channel_id: channel_id,
356+
intercept_scid: scid,
357+
..
358+
}) => {
359+
assert_eq!(channel_id, user_channel_id);
360+
assert_eq!(scid, intercept_scid);
361+
true
362+
},
363+
_ => panic!("Expected OpenChannel event"),
364+
};
365+
366+
service_handler.channel_open_failed(&client_node_id, user_channel_id).unwrap();
367+
368+
// Verify we can restart the flow with another HTLC
369+
let new_intercept_id = InterceptId([1; 32]);
370+
service_handler
371+
.htlc_intercepted(intercept_scid, new_intercept_id, htlc_amount_msat, payment_hash)
372+
.unwrap();
373+
374+
// Should get another OpenChannel event which confirms the reset worked
375+
let _ = match service_node.liquidity_manager.next_event().unwrap() {
376+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel {
377+
user_channel_id: channel_id,
378+
intercept_scid: scid,
379+
..
380+
}) => {
381+
assert_eq!(channel_id, user_channel_id);
382+
assert_eq!(scid, intercept_scid);
383+
true
384+
},
385+
_ => panic!("Expected OpenChannel event after reset"),
386+
};
387+
}
388+
389+
#[test]
390+
fn channel_open_failed_nonexistent_channel() {
391+
let (_, client_node_id, service_node, _, _) = setup_test_lsps2();
392+
393+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
394+
395+
// Call channel_open_failed with a nonexistent user_channel_id
396+
let nonexistent_user_channel_id = 999;
397+
let result = service_handler.channel_open_failed(&client_node_id, nonexistent_user_channel_id);
398+
399+
assert!(result.is_err());
400+
match result.unwrap_err() {
401+
APIError::APIMisuseError { err } => {
402+
assert!(err.contains("No counterparty state for"));
403+
},
404+
other => panic!("Unexpected error type: {:?}", other),
405+
}
406+
}
407+
408+
#[test]
409+
fn channel_open_abandoned() {
410+
let (service_node_id, client_node_id, service_node, client_node, _) = setup_test_lsps2();
411+
412+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
413+
414+
// Set up a JIT channel
415+
let get_info_request_id = client_node
416+
.liquidity_manager
417+
.lsps2_client_handler()
418+
.unwrap()
419+
.request_opening_params(service_node_id, None);
420+
let get_info_request = get_lsps_message!(client_node, service_node_id);
421+
service_node.liquidity_manager.handle_custom_message(get_info_request, client_node_id).unwrap();
422+
let _get_info_event = service_node.liquidity_manager.next_event().unwrap();
423+
424+
let raw_opening_params = LSPS2RawOpeningFeeParams {
425+
min_fee_msat: 100,
426+
proportional: 21,
427+
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
428+
min_lifetime: 144,
429+
max_client_to_self_delay: 128,
430+
min_payment_size_msat: 1,
431+
max_payment_size_msat: 100_000_000,
432+
};
433+
service_handler
434+
.opening_fee_params_generated(
435+
&client_node_id,
436+
get_info_request_id.clone(),
437+
vec![raw_opening_params],
438+
)
439+
.unwrap();
440+
441+
let get_info_response = get_lsps_message!(service_node, client_node_id);
442+
client_node
443+
.liquidity_manager
444+
.handle_custom_message(get_info_response, service_node_id)
445+
.unwrap();
446+
447+
let opening_fee_params = match client_node.liquidity_manager.next_event().unwrap() {
448+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady {
449+
opening_fee_params_menu,
450+
..
451+
}) => opening_fee_params_menu.first().unwrap().clone(),
452+
_ => panic!("Unexpected event"),
453+
};
454+
455+
let payment_size_msat = Some(1_000_000);
456+
let buy_request_id = client_node
457+
.liquidity_manager
458+
.lsps2_client_handler()
459+
.unwrap()
460+
.select_opening_params(service_node_id, payment_size_msat, opening_fee_params.clone())
461+
.unwrap();
462+
let buy_request = get_lsps_message!(client_node, service_node_id);
463+
service_node.liquidity_manager.handle_custom_message(buy_request, client_node_id).unwrap();
464+
465+
let _buy_event = service_node.liquidity_manager.next_event().unwrap();
466+
let user_channel_id = 42;
467+
let cltv_expiry_delta = 144;
468+
let intercept_scid = service_node.channel_manager.get_intercept_scid();
469+
let client_trusts_lsp = true;
470+
471+
service_handler
472+
.invoice_parameters_generated(
473+
&client_node_id,
474+
buy_request_id.clone(),
475+
intercept_scid,
476+
cltv_expiry_delta,
477+
client_trusts_lsp,
478+
user_channel_id,
479+
)
480+
.unwrap();
481+
482+
// Call channel_open_abandoned
483+
service_handler.channel_open_abandoned(&client_node_id, user_channel_id).unwrap();
484+
485+
// Verify the channel is gone by trying to abandon it again, which should fail
486+
let result = service_handler.channel_open_abandoned(&client_node_id, user_channel_id);
487+
assert!(result.is_err());
488+
}
489+
490+
#[test]
491+
fn channel_open_abandoned_nonexistent_channel() {
492+
let (_, client_node_id, service_node, _, _) = setup_test_lsps2();
493+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
494+
495+
// Call channel_open_abandoned with a nonexistent user_channel_id
496+
let nonexistent_user_channel_id = 999;
497+
let result =
498+
service_handler.channel_open_abandoned(&client_node_id, nonexistent_user_channel_id);
499+
assert!(result.is_err());
500+
match result.unwrap_err() {
501+
APIError::APIMisuseError { err } => {
502+
assert!(err.contains("No counterparty state for"));
503+
},
504+
other => panic!("Unexpected error type: {:?}", other),
505+
}
506+
}

0 commit comments

Comments
 (0)