|
2 | 2 |
|
3 | 3 | mod common;
|
4 | 4 |
|
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}; |
6 | 7 |
|
7 | 8 | use lightning_liquidity::events::LiquidityEvent;
|
8 | 9 | use lightning_liquidity::lsps0::ser::LSPSDateTime;
|
9 | 10 | 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; |
11 | 14 | use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams;
|
12 |
| -use lightning_liquidity::lsps2::service::LSPS2ServiceConfig; |
| 15 | +use lightning_liquidity::lsps2::service::{LSPS2ServiceConfig, LSPS2ServiceHandler}; |
13 | 16 | use lightning_liquidity::lsps2::utils::is_valid_opening_fee_params;
|
14 |
| -use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig}; |
15 | 17 |
|
16 | 18 | use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
|
17 | 19 | use lightning::ln::peer_handler::CustomMessageHandler;
|
18 | 20 | use lightning::log_error;
|
19 | 21 | use lightning::routing::router::{RouteHint, RouteHintHop};
|
| 22 | +use lightning::sign::KeysManager; |
20 | 23 | use lightning::util::logger::Logger;
|
21 | 24 |
|
22 | 25 | use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
|
23 | 26 |
|
24 | 27 | use bitcoin::hashes::{sha256, Hash};
|
25 |
| -use bitcoin::secp256k1::{PublicKey, Secp256k1}; |
| 28 | +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; |
26 | 29 | use bitcoin::Network;
|
| 30 | +use lightning_liquidity::LiquidityClientConfig; |
| 31 | +use lightning_liquidity::LiquidityServiceConfig; |
27 | 32 |
|
28 | 33 | use std::str::FromStr;
|
| 34 | +use std::sync::Arc; |
29 | 35 | use std::time::Duration;
|
30 | 36 |
|
| 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 | + |
31 | 90 | fn create_jit_invoice(
|
32 | 91 | node: &Node, service_node_id: PublicKey, intercept_scid: u64, cltv_expiry_delta: u32,
|
33 | 92 | payment_size_msat: Option<u64>, description: &str, expiry_secs: u32,
|
@@ -82,29 +141,15 @@ fn create_jit_invoice(
|
82 | 141 |
|
83 | 142 | #[test]
|
84 | 143 | 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(); |
108 | 153 |
|
109 | 154 | let get_info_request_id = client_handler.request_opening_params(service_node_id, None);
|
110 | 155 | let get_info_request = get_lsps_message!(client_node, service_node_id);
|
@@ -239,3 +284,249 @@ fn invoice_generation_flow() {
|
239 | 284 | )
|
240 | 285 | .unwrap();
|
241 | 286 | }
|
| 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