diff --git a/lightning-liquidity/src/lsps1/service.rs b/lightning-liquidity/src/lsps1/service.rs index 28fe72ca905..44b9fc08c7f 100644 --- a/lightning-liquidity/src/lsps1/service.rs +++ b/lightning-liquidity/src/lsps1/service.rs @@ -30,8 +30,6 @@ use crate::prelude::{new_hash_map, HashMap}; use crate::sync::{Arc, Mutex, RwLock}; use crate::utils; -use lightning::chain::Filter; -use lightning::ln::channelmanager::AChannelManager; use lightning::ln::msgs::{ErrorAction, LightningError}; use lightning::sign::EntropySource; use lightning::util::errors::APIError; @@ -39,8 +37,6 @@ use lightning::util::logger::Level; use bitcoin::secp256k1::PublicKey; -use chrono::Utc; - /// Server-side configuration options for bLIP-51 / LSPS1 channel requests. #[derive(Clone, Debug)] pub struct LSPS1ServiceConfig { @@ -62,7 +58,6 @@ impl From for LightningError { enum OutboundRequestState { OrderCreated { order_id: LSPS1OrderId }, WaitingPayment { order_id: LSPS1OrderId }, - Ready, } impl OutboundRequestState { @@ -101,18 +96,11 @@ impl OutboundCRChannel { self.state = self.state.awaiting_payment()?; Ok(()) } - - fn check_order_validity(&self, supported_options: &LSPS1Options) -> bool { - let order = &self.config.order; - - is_valid(order, supported_options) - } } #[derive(Default)] struct PeerState { outbound_channels_by_order_id: HashMap, - request_to_cid: HashMap, pending_requests: HashMap, } @@ -120,48 +108,31 @@ impl PeerState { fn insert_outbound_channel(&mut self, order_id: LSPS1OrderId, channel: OutboundCRChannel) { self.outbound_channels_by_order_id.insert(order_id, channel); } - - fn insert_request(&mut self, request_id: LSPSRequestId, channel_id: u128) { - self.request_to_cid.insert(request_id, channel_id); - } - - fn remove_outbound_channel(&mut self, order_id: LSPS1OrderId) { - self.outbound_channels_by_order_id.remove(&order_id); - } } /// The main object allowing to send and receive bLIP-51 / LSPS1 messages. -pub struct LSPS1ServiceHandler +pub struct LSPS1ServiceHandler where ES::Target: EntropySource, - CM::Target: AChannelManager, - C::Target: Filter, { entropy_source: ES, - channel_manager: CM, - chain_source: Option, pending_messages: Arc, pending_events: Arc, per_peer_state: RwLock>>, config: LSPS1ServiceConfig, } -impl LSPS1ServiceHandler +impl LSPS1ServiceHandler where ES::Target: EntropySource, - CM::Target: AChannelManager, - C::Target: Filter, - ES::Target: EntropySource, { /// Constructs a `LSPS1ServiceHandler`. pub(crate) fn new( entropy_source: ES, pending_messages: Arc, pending_events: Arc, - channel_manager: CM, chain_source: Option, config: LSPS1ServiceConfig, + config: LSPS1ServiceConfig, ) -> Self { Self { entropy_source, - channel_manager, - chain_source, pending_messages, pending_events, per_peer_state: RwLock::new(new_hash_map()), @@ -432,12 +403,9 @@ where } } -impl LSPSProtocolMessageHandler - for LSPS1ServiceHandler +impl LSPSProtocolMessageHandler for LSPS1ServiceHandler where ES::Target: EntropySource, - CM::Target: AChannelManager, - C::Target: Filter, { type ProtocolMessage = LSPS1Message; const PROTOCOL_NUMBER: Option = Some(1); diff --git a/lightning-liquidity/src/manager.rs b/lightning-liquidity/src/manager.rs index f4cce6855cd..333934f577d 100644 --- a/lightning-liquidity/src/manager.rs +++ b/lightning-liquidity/src/manager.rs @@ -140,7 +140,7 @@ where lsps0_client_handler: LSPS0ClientHandler, lsps0_service_handler: Option, #[cfg(lsps1_service)] - lsps1_service_handler: Option>, + lsps1_service_handler: Option>, lsps1_client_handler: Option>, lsps2_service_handler: Option>, lsps2_client_handler: Option>, @@ -212,7 +212,7 @@ where { #[cfg(lsps1_service)] let lsps1_service_handler = service_config.as_ref().and_then(|config| { if let Some(number) = - as LSPSProtocolMessageHandler>::PROTOCOL_NUMBER + as LSPSProtocolMessageHandler>::PROTOCOL_NUMBER { supported_protocols.push(number); } @@ -221,8 +221,6 @@ where { entropy_source.clone(), Arc::clone(&pending_messages), Arc::clone(&pending_events), - channel_manager.clone(), - chain_source.clone(), config.clone(), ) }) @@ -279,7 +277,7 @@ where { /// Returns a reference to the LSPS1 server-side handler. #[cfg(lsps1_service)] - pub fn lsps1_service_handler(&self) -> Option<&LSPS1ServiceHandler> { + pub fn lsps1_service_handler(&self) -> Option<&LSPS1ServiceHandler> { self.lsps1_service_handler.as_ref() } diff --git a/lightning-liquidity/tests/lsps1_integration_tests.rs b/lightning-liquidity/tests/lsps1_integration_tests.rs new file mode 100644 index 00000000000..1991e371105 --- /dev/null +++ b/lightning-liquidity/tests/lsps1_integration_tests.rs @@ -0,0 +1,242 @@ +#![cfg(all(test, feature = "std"))] + +mod common; + +#[cfg(lsps1_service)] +use { + bitcoin::secp256k1::SecretKey, + common::create_service_and_client_nodes, + common::{get_lsps_message, Node}, + lightning::ln::peer_handler::CustomMessageHandler, + lightning_liquidity::events::LiquidityEvent, + lightning_liquidity::lsps0::ser::LSPSDateTime, + lightning_liquidity::lsps1::client::LSPS1ClientConfig, + lightning_liquidity::lsps1::event::LSPS1ClientEvent, + lightning_liquidity::lsps1::event::LSPS1ServiceEvent, + lightning_liquidity::lsps1::msgs::LSPS1OrderState, + lightning_liquidity::lsps1::msgs::{ + LSPS1OnchainPaymentInfo, LSPS1Options, LSPS1OrderParams, LSPS1PaymentInfo, + }, + lightning_liquidity::lsps1::service::LSPS1ServiceConfig, + lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig}, + std::str::FromStr, +}; + +#[cfg(lsps1_service)] +fn setup_test_lsps1( + persist_dir: &str, _supported_options: LSPS1Options, +) -> (bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey, Node, Node, [u8; 32]) { + let promise_secret = [42; 32]; + let signing_key = SecretKey::from_slice(&promise_secret).unwrap(); + + let lsps1_service_config = + LSPS1ServiceConfig { token: None, supported_options: Some(_supported_options) }; + let service_config: LiquidityServiceConfig = LiquidityServiceConfig { + lsps1_service_config: Some(lsps1_service_config), + lsps2_service_config: None, + advertise_service: true, + }; + + let lsps1_client_config = LSPS1ClientConfig { max_channel_fees_msat: None }; + let client_config = LiquidityClientConfig { + lsps1_client_config: Some(lsps1_client_config), + lsps2_client_config: None, + }; + + let (service_node, client_node) = + create_service_and_client_nodes(persist_dir, service_config, client_config); + + let secp = bitcoin::secp256k1::Secp256k1::new(); + let service_node_id = bitcoin::secp256k1::PublicKey::from_secret_key(&secp, &signing_key); + let client_node_id = client_node.channel_manager.get_our_node_id(); + + (service_node_id, client_node_id, service_node, client_node, promise_secret) +} + +#[cfg(lsps1_service)] +#[test] +fn lsps1_happy_path() { + let expected_options_supported = LSPS1Options { + min_required_channel_confirmations: 0, + min_funding_confirms_within_blocks: 6, + supports_zero_channel_reserve: true, + max_channel_expiry_blocks: 144, + min_initial_client_balance_sat: 10_000_000, + max_initial_client_balance_sat: 100_000_000, + min_initial_lsp_balance_sat: 100_000, + max_initial_lsp_balance_sat: 100_000_000, + min_channel_balance_sat: 100_000, + max_channel_balance_sat: 100_000_000, + }; + + let (service_node_id, client_node_id, service_node, client_node, _) = + setup_test_lsps1("lsps1_happy_path", expected_options_supported.clone()); + + let client_handler = client_node.liquidity_manager.lsps1_client_handler().unwrap(); + + let service_handler = service_node.liquidity_manager.lsps1_service_handler().unwrap(); + + let request_supported_options_id = client_handler.request_supported_options(service_node_id); + let request_supported_options = get_lsps_message!(client_node, service_node_id); + + service_node + .liquidity_manager + .handle_custom_message(request_supported_options, client_node_id) + .unwrap(); + + let get_info_message = get_lsps_message!(service_node, client_node_id); + + client_node.liquidity_manager.handle_custom_message(get_info_message, service_node_id).unwrap(); + + let get_info_event = client_node.liquidity_manager.next_event().unwrap(); + if let LiquidityEvent::LSPS1Client(LSPS1ClientEvent::SupportedOptionsReady { + request_id, + counterparty_node_id, + supported_options, + }) = get_info_event + { + assert_eq!(request_id, request_supported_options_id); + assert_eq!(counterparty_node_id, service_node_id); + assert_eq!(expected_options_supported, supported_options); + } else { + panic!("Unexpected event"); + } + + let order_params = LSPS1OrderParams { + lsp_balance_sat: 100_000, + client_balance_sat: 10_000_000, + required_channel_confirmations: 0, + funding_confirms_within_blocks: 6, + channel_expiry_blocks: 144, + token: None, + announce_channel: true, + }; + + let _create_order_id = + client_handler.create_order(&service_node_id, order_params.clone(), None); + let create_order = get_lsps_message!(client_node, service_node_id); + + service_node.liquidity_manager.handle_custom_message(create_order, client_node_id).unwrap(); + + let _request_for_payment_event = service_node.liquidity_manager.next_event().unwrap(); + + if let LiquidityEvent::LSPS1Service(LSPS1ServiceEvent::RequestForPaymentDetails { + request_id, + counterparty_node_id, + order, + }) = _request_for_payment_event + { + assert_eq!(request_id, _create_order_id.clone()); + assert_eq!(counterparty_node_id, client_node_id); + assert_eq!(order, order_params); + } else { + panic!("Unexpected event"); + } + + let json_str = r#"{ + "state": "EXPECT_PAYMENT", + "expires_at": "2025-01-01T00:00:00Z", + "fee_total_sat": "9999", + "order_total_sat": "200999", + "address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr", + "min_onchain_payment_confirmations": 1, + "min_fee_for_0conf": 253 + }"#; + + let onchain: LSPS1OnchainPaymentInfo = + serde_json::from_str(json_str).expect("Failed to parse JSON"); + let payment_info = LSPS1PaymentInfo { bolt11: None, bolt12: None, onchain: Some(onchain) }; + let _now = LSPSDateTime::from_str("2024-01-01T00:00:00Z").expect("Failed to parse date"); + + let _ = service_handler + .send_payment_details(_create_order_id.clone(), &client_node_id, payment_info.clone(), _now) + .unwrap(); + + let create_order_response = get_lsps_message!(service_node, client_node_id); + + client_node + .liquidity_manager + .handle_custom_message(create_order_response, service_node_id) + .unwrap(); + + let order_created_event = client_node.liquidity_manager.next_event().unwrap(); + let expected_order_id = if let LiquidityEvent::LSPS1Client(LSPS1ClientEvent::OrderCreated { + request_id, + counterparty_node_id, + order_id, + order, + payment, + channel, + }) = order_created_event + { + assert_eq!(request_id, _create_order_id); + assert_eq!(counterparty_node_id, service_node_id); + assert_eq!(order, order_params); + assert_eq!(payment, payment_info); + assert!(channel.is_none()); + order_id + } else { + panic!("Unexpected event"); + }; + + let check_order_status_id = + client_handler.check_order_status(&service_node_id, expected_order_id.clone()); + let check_order_status = get_lsps_message!(client_node, service_node_id); + + service_node + .liquidity_manager + .handle_custom_message(check_order_status, client_node_id) + .unwrap(); + + let _check_payment_confirmation_event = service_node.liquidity_manager.next_event().unwrap(); + + if let LiquidityEvent::LSPS1Service(LSPS1ServiceEvent::CheckPaymentConfirmation { + request_id, + counterparty_node_id, + order_id, + }) = _check_payment_confirmation_event + { + assert_eq!(request_id, check_order_status_id); + assert_eq!(counterparty_node_id, client_node_id); + assert_eq!(order_id, expected_order_id.clone()); + } else { + panic!("Unexpected event"); + } + + let _ = service_handler + .update_order_status( + check_order_status_id.clone(), + client_node_id, + expected_order_id.clone(), + LSPS1OrderState::Created, + None, + ) + .unwrap(); + + let order_status_response = get_lsps_message!(service_node, client_node_id); + + client_node + .liquidity_manager + .handle_custom_message(order_status_response, service_node_id) + .unwrap(); + + let order_status_event = client_node.liquidity_manager.next_event().unwrap(); + if let LiquidityEvent::LSPS1Client(LSPS1ClientEvent::OrderStatus { + request_id, + counterparty_node_id, + order_id, + order, + payment, + channel, + }) = order_status_event + { + assert_eq!(request_id, check_order_status_id); + assert_eq!(counterparty_node_id, service_node_id); + assert_eq!(order, order_params); + assert_eq!(payment, payment_info); + assert!(channel.is_none()); + assert_eq!(order_id, expected_order_id); + } else { + panic!("Unexpected event"); + } +}