Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bf68eda

Browse files
committedMay 3, 2025··
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 000c0a3 commit bf68eda

File tree

2 files changed

+391
-29
lines changed

2 files changed

+391
-29
lines changed
 

‎lightning-liquidity/tests/common/mod.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use lightning::sign::EntropySource;
99

1010
use bitcoin::blockdata::constants::{genesis_block, ChainHash};
1111
use bitcoin::blockdata::transaction::Transaction;
12+
use bitcoin::secp256k1::SecretKey;
1213
use bitcoin::Network;
1314
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
1415
use lightning::chain::{chainmonitor, BestBlock, Confirm};
@@ -34,6 +35,8 @@ use lightning::util::persist::{
3435
SCORER_PERSISTENCE_SECONDARY_NAMESPACE,
3536
};
3637
use lightning::util::test_utils;
38+
use lightning_liquidity::lsps2::client::{LSPS2ClientConfig, LSPS2ClientHandler};
39+
use lightning_liquidity::lsps2::service::{LSPS2ServiceConfig, LSPS2ServiceHandler};
3740
use lightning_liquidity::{LiquidityClientConfig, LiquidityManager, LiquidityServiceConfig};
3841
use lightning_persister::fs_store::FilesystemStore;
3942

@@ -487,7 +490,7 @@ pub(crate) fn create_liquidity_node(
487490
}
488491
}
489492

490-
pub(crate) fn create_service_and_client_nodes(
493+
fn create_service_and_client_nodes(
491494
persist_dir: &str, service_config: LiquidityServiceConfig, client_config: LiquidityClientConfig,
492495
) -> (Node, Node) {
493496
let persist_temp_path = env::temp_dir().join(persist_dir);
@@ -671,3 +674,53 @@ fn advance_chain(node: &mut Node, num_blocks: u32) {
671674
}
672675
}
673676
}
677+
678+
pub(crate) fn setup_test_lsps2() -> (
679+
&'static LSPS2ClientHandler<Arc<KeysManager>>,
680+
&'static LSPS2ServiceHandler<Arc<ChannelManager>>,
681+
bitcoin::secp256k1::PublicKey,
682+
bitcoin::secp256k1::PublicKey,
683+
&'static Node,
684+
&'static Node,
685+
[u8; 32],
686+
) {
687+
let promise_secret = [42; 32];
688+
let signing_key = SecretKey::from_slice(&promise_secret).unwrap();
689+
let lsps2_service_config = LSPS2ServiceConfig { promise_secret };
690+
let service_config = LiquidityServiceConfig {
691+
#[cfg(lsps1_service)]
692+
lsps1_service_config: None,
693+
lsps2_service_config: Some(lsps2_service_config),
694+
advertise_service: true,
695+
};
696+
697+
let lsps2_client_config = LSPS2ClientConfig::default();
698+
let client_config = LiquidityClientConfig {
699+
lsps1_client_config: None,
700+
lsps2_client_config: Some(lsps2_client_config),
701+
};
702+
703+
let (service_node, client_node) =
704+
create_service_and_client_nodes("webhook_registration_flow", service_config, client_config);
705+
706+
// Leak the nodes to extend their lifetime to 'static since this is test code
707+
let service_node = Box::leak(Box::new(service_node));
708+
let client_node = Box::leak(Box::new(client_node));
709+
710+
let client_handler = client_node.liquidity_manager.lsps2_client_handler().unwrap();
711+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
712+
713+
let secp = bitcoin::secp256k1::Secp256k1::new();
714+
let service_node_id = bitcoin::secp256k1::PublicKey::from_secret_key(&secp, &signing_key);
715+
let client_node_id = client_node.channel_manager.get_our_node_id();
716+
717+
(
718+
client_handler,
719+
service_handler,
720+
service_node_id,
721+
client_node_id,
722+
service_node,
723+
client_node,
724+
promise_secret,
725+
)
726+
}

‎lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 337 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@
22

33
mod common;
44

5-
use common::{create_service_and_client_nodes, get_lsps_message, Node};
5+
use common::{get_lsps_message, setup_test_lsps2, Node};
66

77
use lightning_liquidity::events::LiquidityEvent;
88
use lightning_liquidity::lsps0::ser::LSPSDateTime;
9-
use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
109
use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
1110
use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams;
12-
use lightning_liquidity::lsps2::service::LSPS2ServiceConfig;
1311
use lightning_liquidity::lsps2::utils::is_valid_opening_fee_params;
14-
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
1512

16-
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
13+
use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA};
1714
use lightning::ln::peer_handler::CustomMessageHandler;
1815
use lightning::log_error;
1916
use lightning::routing::router::{RouteHint, RouteHintHop};
17+
use lightning::util::errors::APIError;
2018
use lightning::util::logger::Logger;
2119

2220
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
2321

22+
use lightning_types::payment::PaymentHash;
23+
2424
use bitcoin::hashes::{sha256, Hash};
2525
use bitcoin::secp256k1::{PublicKey, Secp256k1};
2626
use bitcoin::Network;
@@ -82,29 +82,15 @@ fn create_jit_invoice(
8282

8383
#[test]
8484
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();
105-
106-
let client_handler = client_node.liquidity_manager.lsps2_client_handler().unwrap();
107-
let client_node_id = client_node.channel_manager.get_our_node_id();
85+
let (
86+
client_handler,
87+
service_handler,
88+
service_node_id,
89+
client_node_id,
90+
service_node,
91+
client_node,
92+
promise_secret,
93+
) = setup_test_lsps2();
10894

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

0 commit comments

Comments
 (0)
Please sign in to comment.