Skip to content

Commit 55f4a8a

Browse files
Add integration tests for error event handling
Add tests for the new error events: - Test per-peer request limit rejection (GetInfoFailed) - Test global request limit rejection (BuyRequestFailed) - Test invalid token handling (GetInfoFailed) These tests verify that error events are properly emitted when LSP requests fail, ensuring the error handling behavior works end-to-end.
1 parent b0b887c commit 55f4a8a

File tree

2 files changed

+320
-29
lines changed

2 files changed

+320
-29
lines changed

lightning-liquidity/tests/common/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type LockingWrapper<T> = lightning::routing::scoring::MultiThreadedLockableScore
6767
#[cfg(not(c_bindings))]
6868
type LockingWrapper<T> = std::sync::Mutex<T>;
6969

70-
type ChannelManager = channelmanager::ChannelManager<
70+
pub(crate) type ChannelManager = channelmanager::ChannelManager<
7171
Arc<ChainMonitor>,
7272
Arc<test_utils::TestBroadcaster>,
7373
Arc<KeysManager>,

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 319 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,91 @@
22

33
mod common;
44

5-
use common::{create_service_and_client_nodes, get_lsps_message, Node};
5+
use common::{create_service_and_client_nodes, ChannelManager};
6+
use common::{get_lsps_message, Node};
67

78
use lightning_liquidity::events::LiquidityEvent;
89
use lightning_liquidity::lsps0::ser::LSPSDateTime;
910
use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
10-
use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
11+
use lightning_liquidity::lsps2::client::LSPS2ClientHandler;
12+
use lightning_liquidity::lsps2::event::LSPS2ClientEvent;
13+
use lightning_liquidity::lsps2::event::LSPS2ServiceEvent;
1114
use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams;
12-
use lightning_liquidity::lsps2::service::LSPS2ServiceConfig;
15+
use lightning_liquidity::lsps2::service::{LSPS2ServiceConfig, LSPS2ServiceHandler};
1316
use lightning_liquidity::lsps2::utils::is_valid_opening_fee_params;
14-
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
1517

1618
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
1719
use lightning::ln::peer_handler::CustomMessageHandler;
1820
use lightning::log_error;
1921
use lightning::routing::router::{RouteHint, RouteHintHop};
22+
use lightning::sign::KeysManager;
2023
use lightning::util::logger::Logger;
2124

2225
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
2326

2427
use bitcoin::hashes::{sha256, Hash};
25-
use bitcoin::secp256k1::{PublicKey, Secp256k1};
28+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
2629
use bitcoin::Network;
30+
use lightning_liquidity::LiquidityClientConfig;
31+
use lightning_liquidity::LiquidityServiceConfig;
2732

2833
use std::str::FromStr;
34+
use std::sync::Arc;
2935
use std::time::Duration;
3036

37+
const MAX_PENDING_REQUESTS_PER_PEER: usize = 10;
38+
const MAX_TOTAL_PENDING_REQUESTS: usize = 1000;
39+
40+
fn setup_lsps2_test() -> (
41+
&'static LSPS2ClientHandler<Arc<KeysManager>>,
42+
&'static LSPS2ServiceHandler<Arc<ChannelManager>>,
43+
bitcoin::secp256k1::PublicKey,
44+
bitcoin::secp256k1::PublicKey,
45+
&'static Node,
46+
&'static Node,
47+
[u8; 32],
48+
) {
49+
let promise_secret = [42; 32];
50+
let signing_key = SecretKey::from_slice(&promise_secret).unwrap();
51+
let lsps2_service_config = LSPS2ServiceConfig { promise_secret };
52+
let service_config = LiquidityServiceConfig {
53+
#[cfg(lsps1_service)]
54+
lsps1_service_config: None,
55+
lsps2_service_config: Some(lsps2_service_config),
56+
advertise_service: true,
57+
};
58+
59+
let lsps2_client_config = LSPS2ClientConfig::default();
60+
let client_config = LiquidityClientConfig {
61+
lsps1_client_config: None,
62+
lsps2_client_config: Some(lsps2_client_config),
63+
};
64+
65+
let (service_node, client_node) =
66+
create_service_and_client_nodes("default_persist_dir", service_config, client_config);
67+
68+
// Leak the nodes to extend their lifetime to 'static since this is test code
69+
let service_node = Box::leak(Box::new(service_node));
70+
let client_node = Box::leak(Box::new(client_node));
71+
72+
let client_handler = client_node.liquidity_manager.lsps2_client_handler().unwrap();
73+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
74+
75+
let secp = bitcoin::secp256k1::Secp256k1::new();
76+
let service_node_id = bitcoin::secp256k1::PublicKey::from_secret_key(&secp, &signing_key);
77+
let client_node_id = client_node.channel_manager.get_our_node_id();
78+
79+
(
80+
client_handler,
81+
service_handler,
82+
service_node_id,
83+
client_node_id,
84+
service_node,
85+
client_node,
86+
promise_secret,
87+
)
88+
}
89+
3190
fn create_jit_invoice(
3291
node: &Node, service_node_id: PublicKey, intercept_scid: u64, cltv_expiry_delta: u32,
3392
payment_size_msat: Option<u64>, description: &str, expiry_secs: u32,
@@ -82,29 +141,15 @@ fn create_jit_invoice(
82141

83142
#[test]
84143
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();
144+
let (
145+
client_handler,
146+
service_handler,
147+
service_node_id,
148+
client_node_id,
149+
service_node,
150+
client_node,
151+
promise_secret,
152+
) = setup_lsps2_test();
108153

109154
let get_info_request_id = client_handler.request_opening_params(service_node_id, None);
110155
let get_info_request = get_lsps_message!(client_node, service_node_id);
@@ -239,3 +284,249 @@ fn invoice_generation_flow() {
239284
)
240285
.unwrap();
241286
}
287+
288+
#[test]
289+
fn max_pending_requests_per_peer_rejected() {
290+
let (client_handler, _, service_node_id, client_node_id, service_node, client_node, _) =
291+
setup_lsps2_test();
292+
293+
for _ in 0..MAX_PENDING_REQUESTS_PER_PEER {
294+
let _ = client_handler.request_opening_params(service_node_id, None);
295+
let req_msg = get_lsps_message!(client_node, service_node_id);
296+
let result = service_node.liquidity_manager.handle_custom_message(req_msg, client_node_id);
297+
assert!(result.is_ok());
298+
let event = service_node.liquidity_manager.next_event().unwrap();
299+
match event {
300+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo { .. }) => {},
301+
_ => panic!("Unexpected event"),
302+
}
303+
}
304+
305+
// Test per-peer limit: the next request should be rejected
306+
let rejected_req_id = client_handler.request_opening_params(service_node_id, None);
307+
let rejected_req_msg = get_lsps_message!(client_node, service_node_id);
308+
309+
let result =
310+
service_node.liquidity_manager.handle_custom_message(rejected_req_msg, client_node_id);
311+
assert!(result.is_err(), "We should have hit the per-peer limit");
312+
313+
let get_info_error_response = get_lsps_message!(service_node, client_node_id);
314+
let result = client_node
315+
.liquidity_manager
316+
.handle_custom_message(get_info_error_response, service_node_id);
317+
assert!(result.is_err());
318+
319+
let event = client_node.liquidity_manager.next_event().unwrap();
320+
match event {
321+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::GetInfoFailed {
322+
request_id,
323+
counterparty_node_id,
324+
error,
325+
}) => {
326+
assert_eq!(request_id, rejected_req_id);
327+
assert_eq!(counterparty_node_id, service_node_id);
328+
assert_eq!(error.code, 1); // LSPS0_CLIENT_REJECTED_ERROR_CODE
329+
},
330+
_ => panic!("Expected LSPS2ClientEvent::GetInfoFailed event"),
331+
}
332+
}
333+
334+
#[test]
335+
fn max_total_requests_buy_rejected() {
336+
let (client_handler, service_handler, service_node_id, _, service_node, client_node, _) =
337+
setup_lsps2_test();
338+
let secp = Secp256k1::new();
339+
340+
let special_sk_bytes = [99u8; 32];
341+
let special_sk = SecretKey::from_slice(&special_sk_bytes).unwrap();
342+
let special_node_id = PublicKey::from_secret_key(&secp, &special_sk);
343+
344+
let _ = client_handler.request_opening_params(service_node_id, None);
345+
let get_info_request = get_lsps_message!(client_node, service_node_id);
346+
service_node
347+
.liquidity_manager
348+
.handle_custom_message(get_info_request, special_node_id)
349+
.unwrap();
350+
351+
let get_info_event = service_node.liquidity_manager.next_event().unwrap();
352+
match get_info_event {
353+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo { request_id, .. }) => {
354+
let raw_opening_params = LSPS2RawOpeningFeeParams {
355+
min_fee_msat: 100,
356+
proportional: 21,
357+
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
358+
min_lifetime: 144,
359+
max_client_to_self_delay: 128,
360+
min_payment_size_msat: 1,
361+
max_payment_size_msat: 100_000_000,
362+
};
363+
364+
service_handler
365+
.opening_fee_params_generated(
366+
&special_node_id,
367+
request_id,
368+
vec![raw_opening_params],
369+
)
370+
.unwrap();
371+
},
372+
_ => panic!("Unexpected event"),
373+
}
374+
375+
let get_info_response = get_lsps_message!(service_node, special_node_id);
376+
client_node
377+
.liquidity_manager
378+
.handle_custom_message(get_info_response, service_node_id)
379+
.unwrap();
380+
381+
let opening_params_event = client_node.liquidity_manager.next_event().unwrap();
382+
let opening_fee_params = match opening_params_event {
383+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady {
384+
opening_fee_params_menu,
385+
..
386+
}) => opening_fee_params_menu.first().unwrap().clone(),
387+
_ => panic!("Unexpected event"),
388+
};
389+
390+
// Now fill up the global limit with additional GetInfo requests from other peers
391+
let mut filled = 0;
392+
let mut peer_idx = 0;
393+
394+
while filled < MAX_TOTAL_PENDING_REQUESTS {
395+
let sk_bytes = [peer_idx as u8 + 1; 32];
396+
let sk = SecretKey::from_slice(&sk_bytes).unwrap();
397+
let peer_node_id = PublicKey::from_secret_key(&secp, &sk);
398+
399+
// Skip if this is our special node
400+
if peer_node_id == special_node_id {
401+
peer_idx += 1;
402+
continue;
403+
}
404+
405+
for _ in 0..MAX_PENDING_REQUESTS_PER_PEER {
406+
if filled >= MAX_TOTAL_PENDING_REQUESTS {
407+
break;
408+
}
409+
410+
let _ = client_handler.request_opening_params(service_node_id, None);
411+
let req_msg = get_lsps_message!(client_node, service_node_id);
412+
let result =
413+
service_node.liquidity_manager.handle_custom_message(req_msg, peer_node_id);
414+
assert!(result.is_ok());
415+
416+
let event = service_node.liquidity_manager.next_event().unwrap();
417+
match event {
418+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo { .. }) => {},
419+
_ => panic!("Unexpected event"),
420+
}
421+
422+
filled += 1;
423+
}
424+
peer_idx += 1;
425+
}
426+
427+
// Now try to send a Buy request with our special node, which should be rejected
428+
let payment_size_msat = Some(1_000_000);
429+
let buy_request_id = client_handler
430+
.select_opening_params(service_node_id, payment_size_msat, opening_fee_params)
431+
.unwrap();
432+
let buy_request = get_lsps_message!(client_node, service_node_id);
433+
434+
let result = service_node.liquidity_manager.handle_custom_message(buy_request, special_node_id);
435+
assert!(result.is_err(), "The Buy request should have been rejected");
436+
437+
let buy_error_response = get_lsps_message!(service_node, special_node_id);
438+
let result =
439+
client_node.liquidity_manager.handle_custom_message(buy_error_response, service_node_id);
440+
assert!(result.is_err());
441+
442+
let event = client_node.liquidity_manager.next_event().unwrap();
443+
match event {
444+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::BuyRequestFailed {
445+
request_id,
446+
counterparty_node_id,
447+
error,
448+
}) => {
449+
assert_eq!(request_id, buy_request_id);
450+
assert_eq!(counterparty_node_id, service_node_id);
451+
assert_eq!(error.code, 1); // LSPS0_CLIENT_REJECTED_ERROR_CODE
452+
},
453+
_ => panic!("Expected LSPS2ClientEvent::BuyRequestFailed event"),
454+
}
455+
}
456+
457+
#[test]
458+
fn invalid_token_flow() {
459+
let (
460+
client_handler,
461+
service_handler,
462+
service_node_id,
463+
client_node_id,
464+
service_node,
465+
client_node,
466+
_promise_secret,
467+
) = setup_lsps2_test();
468+
469+
let token = Some("invalid_token".to_string());
470+
let get_info_request_id = client_handler.request_opening_params(service_node_id, token);
471+
let get_info_request = get_lsps_message!(client_node, service_node_id);
472+
473+
service_node.liquidity_manager.handle_custom_message(get_info_request, client_node_id).unwrap();
474+
475+
let get_info_event = service_node.liquidity_manager.next_event().unwrap();
476+
match get_info_event {
477+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo {
478+
request_id,
479+
counterparty_node_id,
480+
token,
481+
}) => {
482+
assert_eq!(request_id, get_info_request_id);
483+
assert_eq!(counterparty_node_id, client_node_id);
484+
assert_eq!(token, Some("invalid_token".to_string()));
485+
486+
// Service rejects the token as invalid
487+
service_handler.invalid_token_provided(&client_node_id, request_id.clone()).unwrap();
488+
489+
// Attempt to respond to the same request again which should fail
490+
// because the request has been removed from pending_requests
491+
let raw_opening_params = LSPS2RawOpeningFeeParams {
492+
min_fee_msat: 100,
493+
proportional: 21,
494+
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
495+
min_lifetime: 144,
496+
max_client_to_self_delay: 128,
497+
min_payment_size_msat: 1,
498+
max_payment_size_msat: 100_000_000,
499+
};
500+
501+
let result = service_handler.opening_fee_params_generated(
502+
&client_node_id,
503+
request_id.clone(),
504+
vec![raw_opening_params],
505+
);
506+
507+
assert!(result.is_err(), "Request should have been removed from pending_requests");
508+
},
509+
_ => panic!("Unexpected event"),
510+
}
511+
512+
let get_info_error_response = get_lsps_message!(service_node, client_node_id);
513+
514+
client_node
515+
.liquidity_manager
516+
.handle_custom_message(get_info_error_response, service_node_id)
517+
.unwrap_err();
518+
519+
let error_event = client_node.liquidity_manager.next_event().unwrap();
520+
match error_event {
521+
LiquidityEvent::LSPS2Client(LSPS2ClientEvent::GetInfoFailed {
522+
request_id,
523+
counterparty_node_id,
524+
error,
525+
}) => {
526+
assert_eq!(request_id, get_info_request_id);
527+
assert_eq!(counterparty_node_id, service_node_id);
528+
assert_eq!(error.code, 200); // LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE
529+
},
530+
_ => panic!("Expected LSPS2ClientEvent::GetInfoFailed event"),
531+
}
532+
}

0 commit comments

Comments
 (0)