From f08c175727fa7cad30999e7caf4390bd98ba46ac Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 6 Dec 2023 15:00:07 +0100 Subject: [PATCH 01/18] Make `lsps0` module public and add missing docs --- src/events.rs | 2 +- src/lib.rs | 4 +-- src/lsps0/message_handler.rs | 2 +- src/lsps0/msgs.rs | 52 ++++++++++++++++++++++++++++++++++++ src/lsps0/protocol.rs | 17 ++++++++++-- src/lsps1/event.rs | 2 -- src/lsps2/channel_manager.rs | 4 +-- src/lsps2/event.rs | 18 ++++++------- 8 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/events.rs b/src/events.rs index b3a3a8b..f98ef9c 100644 --- a/src/events.rs +++ b/src/events.rs @@ -13,7 +13,7 @@ //! Because we don't have a built-in runtime, it's up to the end-user to poll //! [`LiquidityManager::get_and_clear_pending_events`] to receive events. //! -//! [`LiquidityManager::get_and_clear_pending_events`]: crate::LiquidityManager::get_and_clear_pending_events +//! [`LiquidityManager::get_and_clear_pending_events`]: crate::lsps0::message_handler::LiquidityManager::get_and_clear_pending_events #[cfg(lsps1)] use crate::lsps1; diff --git a/src/lib.rs b/src/lib.rs index 11a9d09..277a990 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,9 @@ mod prelude { } pub mod events; -mod lsps0; +pub mod lsps0; #[cfg(lsps1)] mod lsps1; pub mod lsps2; mod sync; mod utils; - -pub use lsps0::message_handler::{JITChannelsConfig, LiquidityManager, LiquidityProviderConfig}; diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 43c198b..725cc01 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -1,4 +1,4 @@ -#![allow(missing_docs)] +//! Contains a [`CustomMessageHandler`] implementation for LSPS messages. #[cfg(lsps1)] use { diff --git a/src/lsps0/msgs.rs b/src/lsps0/msgs.rs index 6630111..7b659ef 100644 --- a/src/lsps0/msgs.rs +++ b/src/lsps0/msgs.rs @@ -1,3 +1,7 @@ +//! Contains basic data types that allow for the (de-)seralization of LSPS messages in the JSON-RPC 2.0 format. +//! +//! Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more information. + #[cfg(lsps1)] use crate::lsps1::msgs::{ LSPS1Message, LSPS1Request, LSPS1Response, LSPS1_CREATE_ORDER_METHOD_NAME, @@ -51,30 +55,56 @@ impl wire::Type for RawLSPSMessage { } } +/// A JSON-RPC request's `id`. +/// +/// Please refer to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#request_object) for +/// more information. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct RequestId(pub String); +/// An error returned in response to an JSON-RPC request. +/// +/// Please refer to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#error_object) for +/// more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct ResponseError { + /// A number that indicates the error type that occurred. pub code: i32, + /// A string providing a short description of the error. pub message: String, + /// A primitive or structured value that contains additional information about the error. pub data: Option, } +/// A `list_protocols` request. +/// +/// Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] pub struct ListProtocolsRequest {} +/// A response to a `list_protocols` request. +/// +/// Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct ListProtocolsResponse { + /// A list of supported protocols. pub protocols: Vec, } +/// An LSPS0 protocol request. +/// +/// Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS0Request { + /// A request calling `list_protocols`. ListProtocols(ListProtocolsRequest), } impl LSPS0Request { + /// Returns the method name associated with the given request variant. pub fn method(&self) -> &str { match self { LSPS0Request::ListProtocols(_) => LSPS0_LISTPROTOCOLS_METHOD_NAME, @@ -82,15 +112,27 @@ impl LSPS0Request { } } +/// An LSPS0 protocol request. +/// +/// Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS0Response { + /// A response to a `list_protocols` request. ListProtocols(ListProtocolsResponse), + /// An error response to a `list_protocols` request. ListProtocolsError(ResponseError), } +/// An LSPS0 protocol message. +/// +/// Please refer to the [LSPS0 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS0Message { + /// A request variant. Request(RequestId, LSPS0Request), + /// A response variant. Response(RequestId, LSPS0Response), } @@ -114,16 +156,25 @@ impl From for LSPSMessage { } } +/// A (de-)serializable LSPS message allowing to be sent over the wire. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPSMessage { + /// An invalid variant. Invalid, + /// An LSPS0 message. LSPS0(LSPS0Message), + /// An LSPS1 message. #[cfg(lsps1)] LSPS1(LSPS1Message), + /// An LSPS2 message. LSPS2(LSPS2Message), } impl LSPSMessage { + /// A constructor returning an `LSPSMessage` from a raw JSON string. + /// + /// The given `request_id_to_method` associates request ids with method names, as response objects + /// don't carry the latter. pub fn from_str_with_id_map( json_str: &str, request_id_to_method: &mut HashMap, ) -> Result { @@ -132,6 +183,7 @@ impl LSPSMessage { deserializer.deserialize_any(visitor) } + /// Returns the request id and the method. pub fn get_request_id_and_method(&self) -> Option<(String, String)> { match self { LSPSMessage::LSPS0(LSPS0Message::Request(request_id, request)) => { diff --git a/src/lsps0/protocol.rs b/src/lsps0/protocol.rs index 2a3e650..2d0abbb 100644 --- a/src/lsps0/protocol.rs +++ b/src/lsps0/protocol.rs @@ -1,3 +1,9 @@ +//! Contains the logic to handle LSPS0 protocol messages. +//! +//! Please refer to the [LSPS0 +//! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more +//! information. + use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{ LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, @@ -15,6 +21,7 @@ use bitcoin::secp256k1::PublicKey; use core::ops::Deref; +/// A message handler capable of sending and handling LSPS0 messages. pub struct LSPS0MessageHandler where ES::Target: EntropySource, @@ -28,6 +35,7 @@ impl LSPS0MessageHandler where ES::Target: EntropySource, { + /// Returns a new instance of [`LSPS0MessageHandler`]. pub fn new( entropy_source: ES, protocols: Vec, pending_messages: Arc>>, @@ -35,6 +43,11 @@ where Self { entropy_source, protocols, pending_messages } } + /// Calls LSPS0's `list_protocols`. + /// + /// Please refer to the [LSPS0 + /// specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) + /// for more information. pub fn list_protocols(&self, counterparty_node_id: PublicKey) { let msg = LSPS0Message::Request( utils::generate_request_id(&self.entropy_source), @@ -66,10 +79,10 @@ where } fn handle_response( - &self, response: LSPS0Response, counterparty_node_id: &PublicKey, + &self, response: LSPS0Response, _counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { match response { - LSPS0Response::ListProtocols(ListProtocolsResponse { protocols }) => Ok(()), + LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: _ }) => Ok(()), LSPS0Response::ListProtocolsError(ResponseError { code, message, data, .. }) => { Err(LightningError { err: format!( diff --git a/src/lsps1/event.rs b/src/lsps1/event.rs index 8bfe9d9..5baafa5 100644 --- a/src/lsps1/event.rs +++ b/src/lsps1/event.rs @@ -1,5 +1,3 @@ -#![allow(missing_docs)] - use super::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, Payment}; use crate::lsps0::msgs::RequestId; diff --git a/src/lsps2/channel_manager.rs b/src/lsps2/channel_manager.rs index 9dac867..c228293 100644 --- a/src/lsps2/channel_manager.rs +++ b/src/lsps2/channel_manager.rs @@ -8,14 +8,14 @@ // licenses. use crate::events::EventQueue; -use crate::lsps0::message_handler::ProtocolMessageHandler; +use crate::lsps0::message_handler::{JITChannelsConfig, ProtocolMessageHandler}; use crate::lsps0::msgs::{LSPSMessage, RequestId}; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; use crate::lsps2::LSPS2Event; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; +use crate::utils; use crate::{events::Event, lsps0::msgs::ResponseError}; -use crate::{utils, JITChannelsConfig}; use lightning::ln::channelmanager::{AChannelManager, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; diff --git a/src/lsps2/event.rs b/src/lsps2/event.rs index 6f094ce..071a44d 100644 --- a/src/lsps2/event.rs +++ b/src/lsps2/event.rs @@ -24,12 +24,12 @@ pub enum LSPS2Event { /// If an unrecognized or stale token is provided you can use /// `[LiquidityManager::invalid_token_provided`] to error the request. /// - /// [`LiquidityManager::opening_fee_params_generated`]: crate::LiquidityManager::opening_fee_params_generated - /// [`LiquidityManager::invalid_token_provided`]: crate::LiquidityManager::invalid_token_provided + /// [`LiquidityManager::opening_fee_params_generated`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_generated + /// [`LiquidityManager::invalid_token_provided`]: crate::lsps0::message_handler::LiquidityManager::invalid_token_provided GetInfo { /// An identifier that must be passed to [`LiquidityManager::opening_fee_params_generated`]. /// - /// [`LiquidityManager::opening_fee_params_generated`]: crate::LiquidityManager::opening_fee_params_generated + /// [`LiquidityManager::opening_fee_params_generated`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_generated request_id: RequestId, /// The node id of the client making the information request. counterparty_node_id: PublicKey, @@ -43,13 +43,13 @@ pub enum LSPS2Event { /// You must call [`LiquidityManager::opening_fee_params_selected`] with the fee parameter /// you want to use if you wish to proceed opening a channel. /// - /// [`LiquidityManager::opening_fee_params_selected`]: crate::LiquidityManager::opening_fee_params_selected + /// [`LiquidityManager::opening_fee_params_selected`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_selected GetInfoResponse { /// This is a randomly generated identifier used to track the JIT channel state. /// It is not related in anyway to the eventual lightning channel id. /// It needs to be passed to [`LiquidityManager::opening_fee_params_selected`]. /// - /// [`LiquidityManager::opening_fee_params_selected`]: crate::LiquidityManager::opening_fee_params_selected + /// [`LiquidityManager::opening_fee_params_selected`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_selected jit_channel_id: u128, /// The node id of the LSP that provided this response. counterparty_node_id: PublicKey, @@ -62,7 +62,7 @@ pub enum LSPS2Event { max_payment_size_msat: u64, /// The user_channel_id value passed in to [`LiquidityManager::lsps2_create_invoice`]. /// - /// [`LiquidityManager::lsps2_create_invoice`]: crate::LiquidityManager::lsps2_create_invoice + /// [`LiquidityManager::lsps2_create_invoice`]: crate::lsps0::message_handler::LiquidityManager::lsps2_create_invoice user_channel_id: u128, }, /// A client has selected a opening fee parameter to use and would like to @@ -74,11 +74,11 @@ pub enum LSPS2Event { /// You must generate an scid and `cltv_expiry_delta` for them to use /// and call [`LiquidityManager::invoice_parameters_generated`]. /// - /// [`LiquidityManager::invoice_parameters_generated`]: crate::LiquidityManager::invoice_parameters_generated + /// [`LiquidityManager::invoice_parameters_generated`]: crate::lsps0::message_handler::LiquidityManager::invoice_parameters_generated BuyRequest { /// An identifier that must be passed into [`LiquidityManager::invoice_parameters_generated`]. /// - /// [`LiquidityManager::invoice_parameters_generated`]: crate::LiquidityManager::invoice_parameters_generated + /// [`LiquidityManager::invoice_parameters_generated`]: crate::lsps0::message_handler::LiquidityManager::invoice_parameters_generated request_id: RequestId, /// The client node id that is making this request. counterparty_node_id: PublicKey, @@ -106,7 +106,7 @@ pub enum LSPS2Event { client_trusts_lsp: bool, /// The `user_channel_id` value passed in to [`LiquidityManager::lsps2_create_invoice`]. /// - /// [`LiquidityManager::lsps2_create_invoice`]: crate::LiquidityManager::lsps2_create_invoice + /// [`LiquidityManager::lsps2_create_invoice`]: crate::lsps0::message_handler::LiquidityManager::lsps2_create_invoice user_channel_id: u128, }, /// You should open a channel using [`ChannelManager::create_channel`]. From 9ee2cd94d1363f85f30876fa78f41c2701f0b066 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 6 Dec 2023 15:14:58 +0100 Subject: [PATCH 02/18] Ensure `no_std` builds are properly documented --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 558d65e..1e160e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,9 @@ jobs: - name: Cargo check run: cargo check --release - name: Check documentation - run: cargo doc --release + run: | + cargo doc --release + cargo doc --no-default-features --features no-std - name: Build on Rust ${{ matrix.toolchain }} run: cargo build --verbose --color always - name: Check formatting From 316e56b09c406d891038f6e97a327a3f59494556 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:19:11 +0100 Subject: [PATCH 03/18] Drop LSPS-specific API methods, allow retrieval of message handler refs Instead of re-exposing all relevant API calls on `LiquidityManager`, we simply expose them on the original message handlers and expose methods to retrieve those. --- src/lsps0/message_handler.rs | 256 +++-------------------------------- src/lsps1/channel_manager.rs | 2 + src/lsps1/event.rs | 4 +- src/lsps2/channel_manager.rs | 108 ++++++++++++--- src/lsps2/event.rs | 38 +++--- src/lsps2/mod.rs | 4 +- 6 files changed, 132 insertions(+), 280 deletions(-) diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 725cc01..d758f88 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -97,8 +97,8 @@ pub struct CRChannelConfig { /// Users need to continually poll [`LiquidityManager::get_and_clear_pending_events`] in order to surface /// [`Event`]'s that likely need to be handled. /// -/// Users must forward the [`Event::HTLCIntercepted`] event parameters to [`LiquidityManager::htlc_intercepted`] -/// and the [`Event::ChannelReady`] event parameters to [`LiquidityManager::channel_ready`]. +/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`JITChannelManager::htlc_intercepted`] +/// and the [`Event::ChannelReady`] event parameters to [`JITChannelManager::channel_ready`]. /// /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager /// [`MessageHandler`]: lightning::ln::peer_handler::MessageHandler @@ -197,6 +197,22 @@ where { } } + /// Returns a reference to the LSPS0 message handler. + pub fn lsps0_message_handler(&self) -> &LSPS0MessageHandler { + &self.lsps0_message_handler + } + + /// Returns a reference to the LSPS1 message handler. + #[cfg(lsps1)] + pub fn lsps1_message_handler(&self) -> Option<&CRManager> { + self.lsps1_message_handler.as_ref() + } + + /// Returns a reference to the LSPS2 message handler. + pub fn lsps2_message_handler(&self) -> Option<&JITChannelManager> { + self.lsps2_message_handler.as_ref() + } + /// Blocks the current thread until next event is ready and returns it. /// /// Typically you would spawn a thread or task that calls this in a loop. @@ -219,7 +235,7 @@ where { self.pending_events.get_and_clear_pending_events() } - /// Set a [`PeerManager`] reference for the message handlers. + /// Set a [`PeerManager`] reference for all configured message handlers. /// /// This allows the message handlers to wake the [`PeerManager`] by calling /// [`PeerManager::process_events`] after enqueing messages to be sent. @@ -239,240 +255,6 @@ where { } } - #[cfg(lsps1)] - pub fn request_for_info( - &self, counterparty_node_id: PublicKey, channel_id: u128, - ) -> Result<(), APIError> { - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.request_for_info(counterparty_node_id, channel_id); - } - Ok(()) - } - - #[cfg(lsps1)] - pub fn place_order( - &self, channel_id: u128, counterparty_node_id: &PublicKey, order: Order, - ) -> Result<(), APIError> { - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.place_order(channel_id, counterparty_node_id, order)?; - } - Ok(()) - } - - #[cfg(lsps1)] - pub fn send_invoice_for_order( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: Payment, - created_at: chrono::DateTime, expires_at: chrono::DateTime, - ) -> Result<(), APIError> { - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.send_invoice_for_order( - request_id, - counterparty_node_id, - payment, - created_at, - expires_at, - )?; - } - Ok(()) - } - - #[cfg(lsps1)] - pub fn check_order_status( - self, channel_id: u128, counterparty_node_id: &PublicKey, order_id: OrderId, - ) -> Result<(), APIError> { - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.check_order_status(counterparty_node_id, order_id, channel_id)?; - } - Ok(()) - } - - #[cfg(lsps1)] - pub fn update_order_status( - &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, - order_state: OrderState, channel: Option, - ) -> Result<(), APIError> { - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.update_order_status( - request_id, - counterparty_node_id, - order_id, - order_state, - channel, - )?; - } - Ok(()) - } - - /// Initiate the creation of an invoice that when paid will open a channel - /// with enough inbound liquidity to be able to receive the payment. - /// - /// `counterparty_node_id` is the node_id of the LSP you would like to use. - /// - /// If `payment_size_msat` is [`Option::Some`] then the invoice will be for a fixed amount - /// and MPP can be used to pay it. - /// - /// If `payment_size_msat` is [`Option::None`] then the invoice can be for an arbitrary amount - /// but MPP can no longer be used to pay it. - /// - /// `token` is an optional String that will be provided to the LSP. - /// It can be used by the LSP as an API key, coupon code, or some other way to identify a user. - pub fn lsps2_create_invoice( - &self, counterparty_node_id: PublicKey, payment_size_msat: Option, - token: Option, user_channel_id: u128, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.create_invoice( - counterparty_node_id, - payment_size_msat, - token, - user_channel_id, - ); - Ok(()) - } else { - Err(APIError::APIMisuseError { - err: "JIT Channels were not configured when LSPManager was instantiated" - .to_string(), - }) - } - } - - /// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid. - /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. - /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo - pub fn invalid_token_provided( - &self, counterparty_node_id: PublicKey, request_id: RequestId, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.invalid_token_provided(counterparty_node_id, request_id) - } else { - Err(APIError::APIMisuseError { - err: "JIT Channels were not configured when LSPManager was instantiated" - .to_string(), - }) - } - } - - /// Used by LSP to provide fee parameters to a client requesting a JIT Channel. - /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. - /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo - pub fn opening_fee_params_generated( - &self, counterparty_node_id: PublicKey, request_id: RequestId, - opening_fee_params_menu: Vec, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.opening_fee_params_generated( - counterparty_node_id, - request_id, - opening_fee_params_menu, - ) - } else { - Err(APIError::APIMisuseError { - err: "JIT Channels were not configured when LSPManager was instantiated" - .to_string(), - }) - } - } - - /// Used by client to confirm which channel parameters to use for the JIT Channel buy request. - /// The client agrees to paying an opening fee equal to - /// `max(min_fee_msat, proportional*(payment_size_msat/1_000_000))`. - /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfoResponse`] event. - /// - /// [`LSPS2Event::GetInfoResponse`]: crate::lsps2::LSPS2Event::GetInfoResponse - pub fn opening_fee_params_selected( - &self, counterparty_node_id: PublicKey, channel_id: u128, - opening_fee_params: OpeningFeeParams, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.opening_fee_params_selected( - counterparty_node_id, - channel_id, - opening_fee_params, - ) - } else { - Err(APIError::APIMisuseError { - err: "JIT Channels were not configured when LSPManager was instantiated" - .to_string(), - }) - } - } - - /// Used by LSP to provide client with the scid and cltv_expiry_delta to use in their invoice. - /// - /// Should be called in response to receiving a [`LSPS2Event::BuyRequest`] event. - /// - /// [`LSPS2Event::BuyRequest`]: crate::lsps2::LSPS2Event::BuyRequest - pub fn invoice_parameters_generated( - &self, counterparty_node_id: PublicKey, request_id: RequestId, scid: u64, - cltv_expiry_delta: u32, client_trusts_lsp: bool, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.invoice_parameters_generated( - counterparty_node_id, - request_id, - scid, - cltv_expiry_delta, - client_trusts_lsp, - ) - } else { - Err(APIError::APIMisuseError { - err: "JIT Channels were not configured when LSPManager was instantiated" - .to_string(), - }) - } - } - - /// Forward [`Event::HTLCIntercepted`] event parameters into this function. - /// - /// Will fail the intercepted HTLC if the scid matches a payment we are expecting - /// but the payment amount is incorrect or the expiry has passed. - /// - /// Will generate a [`LSPS2Event::OpenChannel`] event if the scid matches a payment we are expected - /// and the payment amount is correct and the offer has not expired. - /// - /// Will do nothing if the scid does not match any of the ones we gave out. - /// - /// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted - /// [`LSPS2Event::OpenChannel`]: crate::lsps2::LSPS2Event::OpenChannel - pub fn htlc_intercepted( - &self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.htlc_intercepted( - scid, - intercept_id, - expected_outbound_amount_msat, - )?; - } - - Ok(()) - } - - /// Forward [`Event::ChannelReady`] event parameters into this function. - /// - /// Will forward the intercepted HTLC if it matches a channel - /// we need to forward a payment over otherwise it will be ignored. - /// - /// [`Event::ChannelReady`]: lightning::events::Event::ChannelReady - pub fn channel_ready( - &self, user_channel_id: u128, channel_id: &ChannelId, counterparty_node_id: &PublicKey, - ) -> Result<(), APIError> { - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.channel_ready( - user_channel_id, - channel_id, - counterparty_node_id, - )?; - } - - Ok(()) - } - fn handle_lsps_message( &self, msg: LSPSMessage, sender_node_id: &PublicKey, ) -> Result<(), lightning::ln::msgs::LightningError> { diff --git a/src/lsps1/channel_manager.rs b/src/lsps1/channel_manager.rs index 007075a..a47c6c8 100644 --- a/src/lsps1/channel_manager.rs +++ b/src/lsps1/channel_manager.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +//! Contains the main LSPS1 object, `CRManager`. + use super::msgs::{ ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, GetOrderRequest, GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, diff --git a/src/lsps1/event.rs b/src/lsps1/event.rs index 5baafa5..73a51a8 100644 --- a/src/lsps1/event.rs +++ b/src/lsps1/event.rs @@ -1,3 +1,5 @@ +//! Contains LSPS1 event types + use super::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, Payment}; use crate::lsps0::msgs::RequestId; @@ -5,7 +7,7 @@ use crate::prelude::String; use bitcoin::secp256k1::PublicKey; -/// An "Event" which you should probably take some action in response to. +/// An event which you should probably take some action in response to. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { GetInfoResponse { diff --git a/src/lsps2/channel_manager.rs b/src/lsps2/channel_manager.rs index c228293..31a137d 100644 --- a/src/lsps2/channel_manager.rs +++ b/src/lsps2/channel_manager.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +//! Contains the main LSPS2 object, `JITChannelManager`. + use crate::events::EventQueue; use crate::lsps0::message_handler::{JITChannelsConfig, ProtocolMessageHandler}; use crate::lsps0::msgs::{LSPSMessage, RequestId}; @@ -141,9 +143,7 @@ struct InboundJITChannel { } impl InboundJITChannel { - pub fn new( - id: u128, user_id: u128, payment_size_msat: Option, token: Option, - ) -> Self { + fn new(id: u128, user_id: u128, payment_size_msat: Option, token: Option) -> Self { Self { id, config: InboundJITChannelConfig { user_id, payment_size_msat, token }, @@ -151,7 +151,7 @@ impl InboundJITChannel { } } - pub fn versions_received(&mut self, versions: Vec) -> Result { + fn versions_received(&mut self, versions: Vec) -> Result { self.state = self.state.versions_received(versions)?; match self.state { @@ -163,12 +163,12 @@ impl InboundJITChannel { } } - pub fn info_received(&mut self) -> Result<(), LightningError> { + fn info_received(&mut self) -> Result<(), LightningError> { self.state = self.state.info_received()?; Ok(()) } - pub fn opening_fee_params_selected(&mut self) -> Result { + fn opening_fee_params_selected(&mut self) -> Result { self.state = self.state.opening_fee_params_selected()?; match self.state { @@ -180,7 +180,7 @@ impl InboundJITChannel { } } - pub fn invoice_params_received( + fn invoice_params_received( &mut self, client_trusts_lsp: bool, jit_channel_scid: JitChannelScid, ) -> Result<(), LightningError> { self.state = self.state.invoice_params_received(client_trusts_lsp, jit_channel_scid)?; @@ -208,7 +208,7 @@ enum OutboundJITChannelState { } impl OutboundJITChannelState { - pub fn new(payment_size_msat: Option, opening_fee_params: OpeningFeeParams) -> Self { + fn new(payment_size_msat: Option, opening_fee_params: OpeningFeeParams) -> Self { OutboundJITChannelState::AwaitingPayment { min_fee_msat: opening_fee_params.min_fee_msat, proportional_fee: opening_fee_params.proportional, @@ -217,7 +217,7 @@ impl OutboundJITChannelState { } } - pub fn htlc_intercepted(&self, htlc: InterceptedHTLC) -> Result { + fn htlc_intercepted(&self, htlc: InterceptedHTLC) -> Result { match self { OutboundJITChannelState::AwaitingPayment { htlcs, @@ -278,7 +278,7 @@ impl OutboundJITChannelState { } } - pub fn channel_ready(&self) -> Result { + fn channel_ready(&self) -> Result { match self { OutboundJITChannelState::PendingChannelOpen { htlcs, amt_to_forward_msat, .. } => { Ok(OutboundJITChannelState::ChannelReady { @@ -302,7 +302,7 @@ struct OutboundJITChannel { } impl OutboundJITChannel { - pub fn new( + fn new( scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, payment_size_msat: Option, opening_fee_params: OpeningFeeParams, ) -> Self { @@ -314,7 +314,7 @@ impl OutboundJITChannel { } } - pub fn htlc_intercepted( + fn htlc_intercepted( &mut self, htlc: InterceptedHTLC, ) -> Result, LightningError> { self.state = self.state.htlc_intercepted(htlc)?; @@ -339,7 +339,7 @@ impl OutboundJITChannel { } } - pub fn channel_ready(&mut self) -> Result<(Vec, u64), LightningError> { + fn channel_ready(&mut self) -> Result<(Vec, u64), LightningError> { self.state = self.state.channel_ready()?; match &self.state { @@ -365,7 +365,7 @@ struct PeerState { } impl PeerState { - pub fn new() -> Self { + fn new() -> Self { let inbound_channels_by_id = HashMap::new(); let outbound_channels_by_scid = HashMap::new(); let request_to_cid = HashMap::new(); @@ -373,27 +373,28 @@ impl PeerState { Self { inbound_channels_by_id, outbound_channels_by_scid, request_to_cid, pending_requests } } - pub fn insert_inbound_channel(&mut self, jit_channel_id: u128, channel: InboundJITChannel) { + fn insert_inbound_channel(&mut self, jit_channel_id: u128, channel: InboundJITChannel) { self.inbound_channels_by_id.insert(jit_channel_id, channel); } - pub fn insert_outbound_channel(&mut self, scid: u64, channel: OutboundJITChannel) { + fn insert_outbound_channel(&mut self, scid: u64, channel: OutboundJITChannel) { self.outbound_channels_by_scid.insert(scid, channel); } - pub fn insert_request(&mut self, request_id: RequestId, jit_channel_id: u128) { + fn insert_request(&mut self, request_id: RequestId, jit_channel_id: u128) { self.request_to_cid.insert(request_id, jit_channel_id); } - pub fn remove_inbound_channel(&mut self, jit_channel_id: u128) { + fn remove_inbound_channel(&mut self, jit_channel_id: u128) { self.inbound_channels_by_id.remove(&jit_channel_id); } - pub fn remove_outbound_channel(&mut self, scid: u64) { + fn remove_outbound_channel(&mut self, scid: u64) { self.outbound_channels_by_scid.remove(&scid); } } +/// The main object allowing to send and receive LSPS2 messages. pub struct JITChannelManager where ES::Target: EntropySource, @@ -418,6 +419,7 @@ where CM::Target: AChannelManager, PM::Target: APeerManager, { + /// Constructs a `JITChannelManager`. pub(crate) fn new( entropy_source: ES, config: &JITChannelsConfig, pending_messages: Arc>>, @@ -437,10 +439,33 @@ where } } + /// Set a [`PeerManager`] reference for the message handler. + /// + /// This allows the message handler to wake the [`PeerManager`] by calling + /// [`PeerManager::process_events`] after enqueing messages to be sent. + /// + /// Without this the messages will be sent based on whatever polling interval + /// your background processor uses. + /// + /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager + /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events pub fn set_peer_manager(&self, peer_manager: PM) { *self.peer_manager.lock().unwrap() = Some(peer_manager); } + /// Initiate the creation of an invoice that when paid will open a channel + /// with enough inbound liquidity to be able to receive the payment. + /// + /// `counterparty_node_id` is the node_id of the LSP you would like to use. + /// + /// If `payment_size_msat` is [`Option::Some`] then the invoice will be for a fixed amount + /// and MPP can be used to pay it. + /// + /// If `payment_size_msat` is [`Option::None`] then the invoice can be for an arbitrary amount + /// but MPP can no longer be used to pay it. + /// + /// `token` is an optional String that will be provided to the LSP. + /// It can be used by the LSP as an API key, coupon code, or some other way to identify a user. pub fn create_invoice( &self, counterparty_node_id: PublicKey, payment_size_msat: Option, token: Option, user_channel_id: u128, @@ -472,6 +497,11 @@ where } } + /// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid. + /// + /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. + /// + /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo pub fn invalid_token_provided( &self, counterparty_node_id: PublicKey, request_id: RequestId, ) -> Result<(), APIError> { @@ -505,6 +535,11 @@ where } } + /// Used by LSP to provide fee parameters to a client requesting a JIT Channel. + /// + /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. + /// + /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo pub fn opening_fee_params_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, opening_fee_params_menu: Vec, @@ -542,6 +577,13 @@ where } } + /// Used by client to confirm which channel parameters to use for the JIT Channel buy request. + /// The client agrees to paying an opening fee equal to + /// `max(min_fee_msat, proportional*(payment_size_msat/1_000_000))`. + /// + /// Should be called in response to receiving a [`LSPS2Event::GetInfoResponse`] event. + /// + /// [`LSPS2Event::GetInfoResponse`]: crate::lsps2::LSPS2Event::GetInfoResponse pub fn opening_fee_params_selected( &self, counterparty_node_id: PublicKey, jit_channel_id: u128, opening_fee_params: OpeningFeeParams, @@ -599,6 +641,11 @@ where Ok(()) } + /// Used by LSP to provide client with the scid and cltv_expiry_delta to use in their invoice. + /// + /// Should be called in response to receiving a [`LSPS2Event::BuyRequest`] event. + /// + /// [`LSPS2Event::BuyRequest`]: crate::lsps2::LSPS2Event::BuyRequest pub fn invoice_parameters_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, @@ -649,7 +696,19 @@ where } } - pub(crate) fn htlc_intercepted( + /// Forward [`Event::HTLCIntercepted`] event parameters into this function. + /// + /// Will fail the intercepted HTLC if the scid matches a payment we are expecting + /// but the payment amount is incorrect or the expiry has passed. + /// + /// Will generate a [`LSPS2Event::OpenChannel`] event if the scid matches a payment we are expected + /// and the payment amount is correct and the offer has not expired. + /// + /// Will do nothing if the scid does not match any of the ones we gave out. + /// + /// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted + /// [`LSPS2Event::OpenChannel`]: crate::lsps2::LSPS2Event::OpenChannel + pub fn htlc_intercepted( &self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64, ) -> Result<(), APIError> { let peer_by_scid = self.peer_by_scid.read().unwrap(); @@ -692,8 +751,13 @@ where Ok(()) } - // figure out which intercept id is waiting on this channel and enqueue ForwardInterceptedHTLC event - pub(crate) fn channel_ready( + /// Forward [`Event::ChannelReady`] event parameters into this function. + /// + /// Will forward the intercepted HTLC if it matches a channel + /// we need to forward a payment over otherwise it will be ignored. + /// + /// [`Event::ChannelReady`]: lightning::events::Event::ChannelReady + pub fn channel_ready( &self, user_channel_id: u128, channel_id: &ChannelId, counterparty_node_id: &PublicKey, ) -> Result<(), APIError> { if let Ok(scid) = user_channel_id.try_into() { diff --git a/src/lsps2/event.rs b/src/lsps2/event.rs index 071a44d..20f7c3c 100644 --- a/src/lsps2/event.rs +++ b/src/lsps2/event.rs @@ -7,6 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +//! Contains LSPS2 event types + use super::msgs::OpeningFeeParams; use crate::lsps0::msgs::RequestId; use crate::prelude::{String, Vec}; @@ -19,17 +21,17 @@ pub enum LSPS2Event { /// A request from a client for information about JIT Channel parameters. /// /// You must calculate the parameters for this client and pass them to - /// [`LiquidityManager::opening_fee_params_generated`]. + /// [`JITChannelManager::opening_fee_params_generated`]. /// /// If an unrecognized or stale token is provided you can use - /// `[LiquidityManager::invalid_token_provided`] to error the request. + /// `[JITChannelManager::invalid_token_provided`] to error the request. /// - /// [`LiquidityManager::opening_fee_params_generated`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_generated - /// [`LiquidityManager::invalid_token_provided`]: crate::lsps0::message_handler::LiquidityManager::invalid_token_provided + /// [`JITChannelManager::opening_fee_params_generated`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_generated + /// [`JITChannelManager::invalid_token_provided`]: crate::lsps2::channel_manager::JITChannelManager::invalid_token_provided GetInfo { - /// An identifier that must be passed to [`LiquidityManager::opening_fee_params_generated`]. + /// An identifier that must be passed to [`JITChannelManager::opening_fee_params_generated`]. /// - /// [`LiquidityManager::opening_fee_params_generated`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_generated + /// [`JITChannelManager::opening_fee_params_generated`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_generated request_id: RequestId, /// The node id of the client making the information request. counterparty_node_id: PublicKey, @@ -40,16 +42,16 @@ pub enum LSPS2Event { }, /// Information from the LSP about their current fee rates and channel parameters. /// - /// You must call [`LiquidityManager::opening_fee_params_selected`] with the fee parameter + /// You must call [`JITChannelManager::opening_fee_params_selected`] with the fee parameter /// you want to use if you wish to proceed opening a channel. /// - /// [`LiquidityManager::opening_fee_params_selected`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_selected + /// [`JITChannelManager::opening_fee_params_selected`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_selected GetInfoResponse { /// This is a randomly generated identifier used to track the JIT channel state. /// It is not related in anyway to the eventual lightning channel id. - /// It needs to be passed to [`LiquidityManager::opening_fee_params_selected`]. + /// It needs to be passed to [`JITChannelManager::opening_fee_params_selected`]. /// - /// [`LiquidityManager::opening_fee_params_selected`]: crate::lsps0::message_handler::LiquidityManager::opening_fee_params_selected + /// [`JITChannelManager::opening_fee_params_selected`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_selected jit_channel_id: u128, /// The node id of the LSP that provided this response. counterparty_node_id: PublicKey, @@ -60,9 +62,9 @@ pub enum LSPS2Event { min_payment_size_msat: u64, /// The max payment size allowed when opening the channel. max_payment_size_msat: u64, - /// The user_channel_id value passed in to [`LiquidityManager::lsps2_create_invoice`]. + /// The user_channel_id value passed in to [`JITChannelManager::create_invoice`]. /// - /// [`LiquidityManager::lsps2_create_invoice`]: crate::lsps0::message_handler::LiquidityManager::lsps2_create_invoice + /// [`JITChannelManager::create_invoice`]: crate::lsps2::channel_manager::JITChannelManager::create_invoice user_channel_id: u128, }, /// A client has selected a opening fee parameter to use and would like to @@ -72,13 +74,13 @@ pub enum LSPS2Event { /// If `payment_size_msat` is [`Option::None`] then the payer cannot use MPP. /// /// You must generate an scid and `cltv_expiry_delta` for them to use - /// and call [`LiquidityManager::invoice_parameters_generated`]. + /// and call [`JITChannelManager::invoice_parameters_generated`]. /// - /// [`LiquidityManager::invoice_parameters_generated`]: crate::lsps0::message_handler::LiquidityManager::invoice_parameters_generated + /// [`JITChannelManager::invoice_parameters_generated`]: crate::lsps2::channel_manager::JITChannelManager::invoice_parameters_generated BuyRequest { - /// An identifier that must be passed into [`LiquidityManager::invoice_parameters_generated`]. + /// An identifier that must be passed into [`JITChannelManager::invoice_parameters_generated`]. /// - /// [`LiquidityManager::invoice_parameters_generated`]: crate::lsps0::message_handler::LiquidityManager::invoice_parameters_generated + /// [`JITChannelManager::invoice_parameters_generated`]: crate::lsps2::channel_manager::JITChannelManager::invoice_parameters_generated request_id: RequestId, /// The client node id that is making this request. counterparty_node_id: PublicKey, @@ -104,9 +106,9 @@ pub enum LSPS2Event { payment_size_msat: Option, /// The trust model the LSP expects. client_trusts_lsp: bool, - /// The `user_channel_id` value passed in to [`LiquidityManager::lsps2_create_invoice`]. + /// The `user_channel_id` value passed in to [`JITChannelManager::create_invoice`]. /// - /// [`LiquidityManager::lsps2_create_invoice`]: crate::lsps0::message_handler::LiquidityManager::lsps2_create_invoice + /// [`JITChannelManager::create_invoice`]: crate::lsps2::channel_manager::JITChannelManager::create_invoice user_channel_id: u128, }, /// You should open a channel using [`ChannelManager::create_channel`]. diff --git a/src/lsps2/mod.rs b/src/lsps2/mod.rs index 2cee68b..0f087cc 100644 --- a/src/lsps2/mod.rs +++ b/src/lsps2/mod.rs @@ -9,8 +9,8 @@ //! Implementation of LSPS2: JIT Channel Negotiation specification. -pub(crate) mod channel_manager; -pub(crate) mod event; +pub mod channel_manager; +pub mod event; /// Message, request, and other primitive types used to implement LSPS2. pub mod msgs; pub(crate) mod utils; From 3511abf526c9cca7bfa8ddc52274e12937b0d752 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:22:32 +0100 Subject: [PATCH 04/18] Consistently use `JIT` prefix over mixing `Jit` and `JIT` --- src/lsps2/channel_manager.rs | 8 ++++---- src/lsps2/msgs.rs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lsps2/channel_manager.rs b/src/lsps2/channel_manager.rs index 31a137d..096a81f 100644 --- a/src/lsps2/channel_manager.rs +++ b/src/lsps2/channel_manager.rs @@ -34,7 +34,7 @@ use core::ops::Deref; use crate::lsps2::msgs::{ BuyRequest, BuyResponse, GetInfoRequest, GetInfoResponse, GetVersionsRequest, - GetVersionsResponse, JitChannelScid, LSPS2Message, LSPS2Request, LSPS2Response, + GetVersionsResponse, JITChannelScid, LSPS2Message, LSPS2Request, LSPS2Response, OpeningFeeParams, RawOpeningFeeParams, LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE, LSPS2_BUY_REQUEST_INVALID_VERSION_ERROR_CODE, LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE, @@ -71,7 +71,7 @@ enum InboundJITChannelState { MenuRequested { version: u16 }, PendingMenuSelection { version: u16 }, BuyRequested { version: u16 }, - PendingPayment { client_trusts_lsp: bool, short_channel_id: JitChannelScid }, + PendingPayment { client_trusts_lsp: bool, short_channel_id: JITChannelScid }, } impl InboundJITChannelState { @@ -122,7 +122,7 @@ impl InboundJITChannelState { } fn invoice_params_received( - &self, client_trusts_lsp: bool, short_channel_id: JitChannelScid, + &self, client_trusts_lsp: bool, short_channel_id: JITChannelScid, ) -> Result { match self { InboundJITChannelState::BuyRequested { .. } => { @@ -181,7 +181,7 @@ impl InboundJITChannel { } fn invoice_params_received( - &mut self, client_trusts_lsp: bool, jit_channel_scid: JitChannelScid, + &mut self, client_trusts_lsp: bool, jit_channel_scid: JITChannelScid, ) -> Result<(), LightningError> { self.state = self.state.invoice_params_received(client_trusts_lsp, jit_channel_scid)?; Ok(()) diff --git a/src/lsps2/msgs.rs b/src/lsps2/msgs.rs index d1b9205..4c33976 100644 --- a/src/lsps2/msgs.rs +++ b/src/lsps2/msgs.rs @@ -126,9 +126,9 @@ pub struct BuyRequest { /// A newtype that holds a `short_channel_id` in human readable format of BBBxTTTx000. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct JitChannelScid(String); +pub struct JITChannelScid(String); -impl From for JitChannelScid { +impl From for JITChannelScid { fn from(scid: u64) -> Self { let block = utils::block_from_scid(&scid); let tx_index = utils::tx_index_from_scid(&scid); @@ -138,8 +138,8 @@ impl From for JitChannelScid { } } -impl JitChannelScid { - /// Try to convert a [`JitChannelScid`] into a u64 used by LDK. +impl JITChannelScid { + /// Try to convert a [`JITChannelScid`] into a u64 used by LDK. pub fn to_scid(&self) -> Result { utils::scid_from_human_readable_string(&self.0) } @@ -151,7 +151,7 @@ impl JitChannelScid { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct BuyResponse { /// The short channel id used by LSP to identify need to open channel. - pub jit_channel_scid: JitChannelScid, + pub jit_channel_scid: JITChannelScid, /// The locktime expiry delta the lsp requires. pub lsp_cltv_expiry_delta: u32, /// A flag that indicates who is trusting who. From a33ba5d3da36ecf2a083fd989a4e6368e734ce9f Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:41:26 +0100 Subject: [PATCH 05/18] Rename `JITChannelManager` to `LSPS2MessageHandler` .. and move it to `lsps2/message_handler.rs` --- src/lsps0/message_handler.rs | 12 +++---- src/lsps2/event.rs | 36 +++++++++---------- ...{channel_manager.rs => message_handler.rs} | 10 +++--- src/lsps2/mod.rs | 3 +- src/lsps2/msgs.rs | 2 ++ 5 files changed, 32 insertions(+), 31 deletions(-) rename src/lsps2/{channel_manager.rs => message_handler.rs} (99%) diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index d758f88..54603a6 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -10,7 +10,7 @@ use crate::events::{Event, EventQueue}; use crate::lsps0::msgs::RequestId; use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; use crate::lsps0::protocol::LSPS0MessageHandler; -use crate::lsps2::channel_manager::JITChannelManager; +use crate::lsps2::message_handler::LSPS2MessageHandler; use crate::lsps2::msgs::{OpeningFeeParams, RawOpeningFeeParams}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; @@ -97,8 +97,8 @@ pub struct CRChannelConfig { /// Users need to continually poll [`LiquidityManager::get_and_clear_pending_events`] in order to surface /// [`Event`]'s that likely need to be handled. /// -/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`JITChannelManager::htlc_intercepted`] -/// and the [`Event::ChannelReady`] event parameters to [`JITChannelManager::channel_ready`]. +/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`LSPS2MessageHandler::htlc_intercepted`] +/// and the [`Event::ChannelReady`] event parameters to [`LSPS2MessageHandler::channel_ready`]. /// /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager /// [`MessageHandler`]: lightning::ln::peer_handler::MessageHandler @@ -121,7 +121,7 @@ pub struct LiquidityManager< lsps0_message_handler: LSPS0MessageHandler, #[cfg(lsps1)] lsps1_message_handler: Option>, - lsps2_message_handler: Option>, + lsps2_message_handler: Option>, provider_config: Option, channel_manager: CM, chain_source: Option, @@ -156,7 +156,7 @@ where { let lsps2_message_handler = provider_config.as_ref().and_then(|config| { config.lsps2_config.as_ref().map(|config| { - JITChannelManager::new( + LSPS2MessageHandler::new( entropy_source.clone(), config, Arc::clone(&pending_messages), @@ -209,7 +209,7 @@ where { } /// Returns a reference to the LSPS2 message handler. - pub fn lsps2_message_handler(&self) -> Option<&JITChannelManager> { + pub fn lsps2_message_handler(&self) -> Option<&LSPS2MessageHandler> { self.lsps2_message_handler.as_ref() } diff --git a/src/lsps2/event.rs b/src/lsps2/event.rs index 20f7c3c..75d4363 100644 --- a/src/lsps2/event.rs +++ b/src/lsps2/event.rs @@ -21,17 +21,17 @@ pub enum LSPS2Event { /// A request from a client for information about JIT Channel parameters. /// /// You must calculate the parameters for this client and pass them to - /// [`JITChannelManager::opening_fee_params_generated`]. + /// [`LSPS2MessageHandler::opening_fee_params_generated`]. /// /// If an unrecognized or stale token is provided you can use - /// `[JITChannelManager::invalid_token_provided`] to error the request. + /// `[LSPS2MessageHandler::invalid_token_provided`] to error the request. /// - /// [`JITChannelManager::opening_fee_params_generated`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_generated - /// [`JITChannelManager::invalid_token_provided`]: crate::lsps2::channel_manager::JITChannelManager::invalid_token_provided + /// [`LSPS2MessageHandler::opening_fee_params_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_generated + /// [`LSPS2MessageHandler::invalid_token_provided`]: crate::lsps2::message_handler::LSPS2MessageHandler::invalid_token_provided GetInfo { - /// An identifier that must be passed to [`JITChannelManager::opening_fee_params_generated`]. + /// An identifier that must be passed to [`LSPS2MessageHandler::opening_fee_params_generated`]. /// - /// [`JITChannelManager::opening_fee_params_generated`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_generated + /// [`LSPS2MessageHandler::opening_fee_params_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_generated request_id: RequestId, /// The node id of the client making the information request. counterparty_node_id: PublicKey, @@ -42,16 +42,16 @@ pub enum LSPS2Event { }, /// Information from the LSP about their current fee rates and channel parameters. /// - /// You must call [`JITChannelManager::opening_fee_params_selected`] with the fee parameter + /// You must call [`LSPS2MessageHandler::opening_fee_params_selected`] with the fee parameter /// you want to use if you wish to proceed opening a channel. /// - /// [`JITChannelManager::opening_fee_params_selected`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_selected + /// [`LSPS2MessageHandler::opening_fee_params_selected`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_selected GetInfoResponse { /// This is a randomly generated identifier used to track the JIT channel state. /// It is not related in anyway to the eventual lightning channel id. - /// It needs to be passed to [`JITChannelManager::opening_fee_params_selected`]. + /// It needs to be passed to [`LSPS2MessageHandler::opening_fee_params_selected`]. /// - /// [`JITChannelManager::opening_fee_params_selected`]: crate::lsps2::channel_manager::JITChannelManager::opening_fee_params_selected + /// [`LSPS2MessageHandler::opening_fee_params_selected`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_selected jit_channel_id: u128, /// The node id of the LSP that provided this response. counterparty_node_id: PublicKey, @@ -62,9 +62,9 @@ pub enum LSPS2Event { min_payment_size_msat: u64, /// The max payment size allowed when opening the channel. max_payment_size_msat: u64, - /// The user_channel_id value passed in to [`JITChannelManager::create_invoice`]. + /// The user_channel_id value passed in to [`LSPS2MessageHandler::create_invoice`]. /// - /// [`JITChannelManager::create_invoice`]: crate::lsps2::channel_manager::JITChannelManager::create_invoice + /// [`LSPS2MessageHandler::create_invoice`]: crate::lsps2::message_handler::LSPS2MessageHandler::create_invoice user_channel_id: u128, }, /// A client has selected a opening fee parameter to use and would like to @@ -74,13 +74,13 @@ pub enum LSPS2Event { /// If `payment_size_msat` is [`Option::None`] then the payer cannot use MPP. /// /// You must generate an scid and `cltv_expiry_delta` for them to use - /// and call [`JITChannelManager::invoice_parameters_generated`]. + /// and call [`LSPS2MessageHandler::invoice_parameters_generated`]. /// - /// [`JITChannelManager::invoice_parameters_generated`]: crate::lsps2::channel_manager::JITChannelManager::invoice_parameters_generated + /// [`LSPS2MessageHandler::invoice_parameters_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::invoice_parameters_generated BuyRequest { - /// An identifier that must be passed into [`JITChannelManager::invoice_parameters_generated`]. + /// An identifier that must be passed into [`LSPS2MessageHandler::invoice_parameters_generated`]. /// - /// [`JITChannelManager::invoice_parameters_generated`]: crate::lsps2::channel_manager::JITChannelManager::invoice_parameters_generated + /// [`LSPS2MessageHandler::invoice_parameters_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::invoice_parameters_generated request_id: RequestId, /// The client node id that is making this request. counterparty_node_id: PublicKey, @@ -106,9 +106,9 @@ pub enum LSPS2Event { payment_size_msat: Option, /// The trust model the LSP expects. client_trusts_lsp: bool, - /// The `user_channel_id` value passed in to [`JITChannelManager::create_invoice`]. + /// The `user_channel_id` value passed in to [`LSPS2MessageHandler::create_invoice`]. /// - /// [`JITChannelManager::create_invoice`]: crate::lsps2::channel_manager::JITChannelManager::create_invoice + /// [`LSPS2MessageHandler::create_invoice`]: crate::lsps2::message_handler::LSPS2MessageHandler::create_invoice user_channel_id: u128, }, /// You should open a channel using [`ChannelManager::create_channel`]. diff --git a/src/lsps2/channel_manager.rs b/src/lsps2/message_handler.rs similarity index 99% rename from src/lsps2/channel_manager.rs rename to src/lsps2/message_handler.rs index 096a81f..b18e49b 100644 --- a/src/lsps2/channel_manager.rs +++ b/src/lsps2/message_handler.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Contains the main LSPS2 object, `JITChannelManager`. +//! Contains the main LSPS2 object, `LSPS2MessageHandler`. use crate::events::EventQueue; use crate::lsps0::message_handler::{JITChannelsConfig, ProtocolMessageHandler}; @@ -395,7 +395,7 @@ impl PeerState { } /// The main object allowing to send and receive LSPS2 messages. -pub struct JITChannelManager +pub struct LSPS2MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -413,13 +413,13 @@ where max_payment_size_msat: u64, } -impl JITChannelManager +impl LSPS2MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, PM::Target: APeerManager, { - /// Constructs a `JITChannelManager`. + /// Constructs a `LSPS2MessageHandler`. pub(crate) fn new( entropy_source: ES, config: &JITChannelsConfig, pending_messages: Arc>>, @@ -1287,7 +1287,7 @@ where } impl ProtocolMessageHandler - for JITChannelManager + for LSPS2MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, diff --git a/src/lsps2/mod.rs b/src/lsps2/mod.rs index 0f087cc..f5f5aee 100644 --- a/src/lsps2/mod.rs +++ b/src/lsps2/mod.rs @@ -9,9 +9,8 @@ //! Implementation of LSPS2: JIT Channel Negotiation specification. -pub mod channel_manager; pub mod event; -/// Message, request, and other primitive types used to implement LSPS2. +pub mod message_handler; pub mod msgs; pub(crate) mod utils; diff --git a/src/lsps2/msgs.rs b/src/lsps2/msgs.rs index 4c33976..5aac1ab 100644 --- a/src/lsps2/msgs.rs +++ b/src/lsps2/msgs.rs @@ -1,3 +1,5 @@ +//! Message, request, and other primitive types used to implement LSPS2. + use core::convert::TryFrom; use bitcoin::hashes::hmac::{Hmac, HmacEngine}; From 8582a7f5d60d4d39aeb9749341330588102bd02f Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:45:39 +0100 Subject: [PATCH 06/18] Rename `CRManager` to `LSPS1MessageHandler` .. and move it to `lsps1/message_handler.rs` --- src/lsps0/message_handler.rs | 18 +++++++++--------- .../{channel_manager.rs => message_handler.rs} | 8 ++++---- src/lsps1/mod.rs | 3 ++- 3 files changed, 15 insertions(+), 14 deletions(-) rename src/lsps1/{channel_manager.rs => message_handler.rs} (99%) diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 54603a6..4ab49df 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -2,7 +2,7 @@ #[cfg(lsps1)] use { - crate::lsps1::channel_manager::CRManager, + crate::lsps1::message_handler::LSPS1MessageHandler, crate::lsps1::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment}, }; @@ -120,7 +120,7 @@ pub struct LiquidityManager< request_id_to_method_map: Mutex>, lsps0_message_handler: LSPS0MessageHandler, #[cfg(lsps1)] - lsps1_message_handler: Option>, + lsps1_message_handler: Option>, lsps2_message_handler: Option>, provider_config: Option, channel_manager: CM, @@ -169,7 +169,7 @@ where { #[cfg(lsps1)] let lsps1_message_handler = provider_config.as_ref().and_then(|config| { config.lsps1_config.as_ref().map(|lsps1_config| { - CRManager::new( + LSPS1MessageHandler::new( entropy_source.clone(), lsps1_config, Arc::clone(&pending_messages), @@ -204,7 +204,7 @@ where { /// Returns a reference to the LSPS1 message handler. #[cfg(lsps1)] - pub fn lsps1_message_handler(&self) -> Option<&CRManager> { + pub fn lsps1_message_handler(&self) -> Option<&LSPS1MessageHandler> { self.lsps1_message_handler.as_ref() } @@ -411,7 +411,7 @@ where *best_block = BestBlock::new(header.prev_blockhash, new_height) } - // TODO: Call block_disconnected on all sub-modules that require it, e.g., CRManager. + // TODO: Call block_disconnected on all sub-modules that require it, e.g., LSPS1MessageHandler. // Internally this should call transaction_unconfirmed for all transactions that were // confirmed at a height <= the one we now disconnected. } @@ -429,21 +429,21 @@ where &self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, height: u32, ) { - // TODO: Call transactions_confirmed on all sub-modules that require it, e.g., CRManager. + // TODO: Call transactions_confirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. } fn transaction_unconfirmed(&self, txid: &bitcoin::Txid) { - // TODO: Call transaction_unconfirmed on all sub-modules that require it, e.g., CRManager. + // TODO: Call transaction_unconfirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. // Internally this should call transaction_unconfirmed for all transactions that were // confirmed at a height <= the one we now unconfirmed. } fn best_block_updated(&self, header: &bitcoin::BlockHeader, height: u32) { - // TODO: Call best_block_updated on all sub-modules that require it, e.g., CRManager. + // TODO: Call best_block_updated on all sub-modules that require it, e.g., LSPS1MessageHandler. } fn get_relevant_txids(&self) -> Vec<(bitcoin::Txid, Option)> { - // TODO: Collect relevant txids from all sub-modules that, e.g., CRManager. + // TODO: Collect relevant txids from all sub-modules that, e.g., LSPS1MessageHandler. Vec::new() } } diff --git a/src/lsps1/channel_manager.rs b/src/lsps1/message_handler.rs similarity index 99% rename from src/lsps1/channel_manager.rs rename to src/lsps1/message_handler.rs index a47c6c8..6e29f80 100644 --- a/src/lsps1/channel_manager.rs +++ b/src/lsps1/message_handler.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Contains the main LSPS1 object, `CRManager`. +//! Contains the main LSPS1 object, `LSPS1MessageHandler`. use super::msgs::{ ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, @@ -275,7 +275,7 @@ impl PeerState { } } -pub struct CRManager +pub struct LSPS1MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -294,7 +294,7 @@ where max_fees: Option, } -impl CRManager +impl LSPS1MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -963,7 +963,7 @@ where } impl ProtocolMessageHandler - for CRManager + for LSPS1MessageHandler where ES::Target: EntropySource, CM::Target: AChannelManager, diff --git a/src/lsps1/mod.rs b/src/lsps1/mod.rs index 07f75f1..99825af 100644 --- a/src/lsps1/mod.rs +++ b/src/lsps1/mod.rs @@ -8,7 +8,8 @@ // licenses. //! Types and primitives that implement the LSPS1: Channel Request specification. -pub(crate) mod channel_manager; + +pub(crate) mod message_handler; pub(crate) mod event; /// Message, request, and other primitive types used to implement LSPS2. pub mod msgs; From 08e0a81dbe56e8daf3f55eafceb8d277cf375216 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:48:14 +0100 Subject: [PATCH 07/18] Rename `JITChannelsConfig` to `LSPS2Config` .. and move it to `lsps2/message_handler.rs` --- src/lsps0/message_handler.rs | 16 ++-------------- src/lsps1/mod.rs | 2 +- src/lsps2/message_handler.rs | 16 ++++++++++++++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 4ab49df..0cf1fa7 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -10,7 +10,7 @@ use crate::events::{Event, EventQueue}; use crate::lsps0::msgs::RequestId; use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; use crate::lsps0::protocol::LSPS0MessageHandler; -use crate::lsps2::message_handler::LSPS2MessageHandler; +use crate::lsps2::message_handler::{LSPS2Config, LSPS2MessageHandler}; use crate::lsps2::msgs::{OpeningFeeParams, RawOpeningFeeParams}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; @@ -62,19 +62,7 @@ pub struct LiquidityProviderConfig { pub lsps1_config: Option, /// Optional configuration for JIT channels /// should you want to support them. - pub lsps2_config: Option, -} - -/// Configuration options for JIT channels. -pub struct JITChannelsConfig { - /// Used to calculate the promise for channel parameters supplied to clients. - /// - /// Note: If this changes then old promises given out will be considered invalid. - pub promise_secret: [u8; 32], - /// The minimum payment size you are willing to accept. - pub min_payment_size_msat: u64, - /// The maximum payment size you are willing to accept. - pub max_payment_size_msat: u64, + pub lsps2_config: Option, } #[cfg(lsps1)] diff --git a/src/lsps1/mod.rs b/src/lsps1/mod.rs index 99825af..00fce16 100644 --- a/src/lsps1/mod.rs +++ b/src/lsps1/mod.rs @@ -9,8 +9,8 @@ //! Types and primitives that implement the LSPS1: Channel Request specification. -pub(crate) mod message_handler; pub(crate) mod event; +pub(crate) mod message_handler; /// Message, request, and other primitive types used to implement LSPS2. pub mod msgs; pub(crate) mod utils; diff --git a/src/lsps2/message_handler.rs b/src/lsps2/message_handler.rs index b18e49b..e5ff7aa 100644 --- a/src/lsps2/message_handler.rs +++ b/src/lsps2/message_handler.rs @@ -10,7 +10,7 @@ //! Contains the main LSPS2 object, `LSPS2MessageHandler`. use crate::events::EventQueue; -use crate::lsps0::message_handler::{JITChannelsConfig, ProtocolMessageHandler}; +use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RequestId}; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; use crate::lsps2::LSPS2Event; @@ -43,6 +43,18 @@ use crate::lsps2::msgs::{ LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE, }; +/// Configuration options for JIT channels. +pub struct LSPS2Config { + /// Used to calculate the promise for channel parameters supplied to clients. + /// + /// Note: If this changes then old promises given out will be considered invalid. + pub promise_secret: [u8; 32], + /// The minimum payment size you are willing to accept. + pub min_payment_size_msat: u64, + /// The maximum payment size you are willing to accept. + pub max_payment_size_msat: u64, +} + const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -421,7 +433,7 @@ where { /// Constructs a `LSPS2MessageHandler`. pub(crate) fn new( - entropy_source: ES, config: &JITChannelsConfig, + entropy_source: ES, config: &LSPS2Config, pending_messages: Arc>>, pending_events: Arc, channel_manager: CM, ) -> Self { From bbef03bf2316feaa7c81084781fe783a43a563ab Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 15:50:40 +0100 Subject: [PATCH 08/18] Rename `CRChannelConfig` to `LSPS1Config` .. and move it to `lsps1/message_handler.rs` --- src/lsps0/message_handler.rs | 12 ++---------- src/lsps1/message_handler.rs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 0cf1fa7..570d886 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -2,7 +2,7 @@ #[cfg(lsps1)] use { - crate::lsps1::message_handler::LSPS1MessageHandler, + crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}, crate::lsps1::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment}, }; @@ -59,20 +59,12 @@ pub(crate) trait ProtocolMessageHandler { pub struct LiquidityProviderConfig { /// LSPS1 Configuration #[cfg(lsps1)] - pub lsps1_config: Option, + pub lsps1_config: Option, /// Optional configuration for JIT channels /// should you want to support them. pub lsps2_config: Option, } -#[cfg(lsps1)] -pub struct CRChannelConfig { - pub token: Option, - pub max_fees: Option, - pub options_supported: Option, - pub website: Option, -} - /// The main interface into LSP functionality. /// /// Should be used as a [`CustomMessageHandler`] for your diff --git a/src/lsps1/message_handler.rs b/src/lsps1/message_handler.rs index 6e29f80..e3d7113 100644 --- a/src/lsps1/message_handler.rs +++ b/src/lsps1/message_handler.rs @@ -18,7 +18,7 @@ use super::msgs::{ use super::utils::is_valid; use crate::events::EventQueue; -use crate::lsps0::message_handler::{CRChannelConfig, ProtocolMessageHandler}; +use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RequestId}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; @@ -40,6 +40,13 @@ use core::ops::Deref; const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; +pub struct LSPS1Config { + pub token: Option, + pub max_fees: Option, + pub options_supported: Option, + pub website: Option, +} + struct ChannelStateError(String); impl From for LightningError { @@ -211,7 +218,7 @@ impl OutboundRequestState { } } -struct OutboundCRChannelConfig { +struct OutboundLSPS1Config { order: Order, created_at: chrono::DateTime, expires_at: chrono::DateTime, @@ -220,7 +227,7 @@ struct OutboundCRChannelConfig { struct OutboundCRChannel { state: OutboundRequestState, - config: OutboundCRChannelConfig, + config: OutboundLSPS1Config, } impl OutboundCRChannel { @@ -230,7 +237,7 @@ impl OutboundCRChannel { ) -> Self { Self { state: OutboundRequestState::OrderCreated { order_id }, - config: OutboundCRChannelConfig { order, created_at, expires_at, payment }, + config: OutboundLSPS1Config { order, created_at, expires_at, payment }, } } pub fn create_payment_invoice(&mut self) -> Result<(), LightningError> { @@ -303,7 +310,7 @@ where ES::Target: EntropySource, { pub(crate) fn new( - entropy_source: ES, config: &CRChannelConfig, + entropy_source: ES, config: &LSPS1Config, pending_messages: Arc>>, pending_events: Arc, channel_manager: CM, chain_source: Option, ) -> Self { From 0391302567efcc954c63c6532e0a2cf7681c1af9 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 16:03:09 +0100 Subject: [PATCH 09/18] Move `LiquidityManager` to `manager.rs` .. and `LSPS0MessageHandler` to `lsps0/message_handler.rs` --- src/events.rs | 2 +- src/lib.rs | 3 + src/lsps0/message_handler.rs | 490 ++++++++++------------------------- src/lsps0/mod.rs | 1 - src/lsps0/protocol.rs | 201 -------------- src/manager.rs | 407 +++++++++++++++++++++++++++++ 6 files changed, 548 insertions(+), 556 deletions(-) delete mode 100644 src/lsps0/protocol.rs create mode 100644 src/manager.rs diff --git a/src/events.rs b/src/events.rs index f98ef9c..b3a3a8b 100644 --- a/src/events.rs +++ b/src/events.rs @@ -13,7 +13,7 @@ //! Because we don't have a built-in runtime, it's up to the end-user to poll //! [`LiquidityManager::get_and_clear_pending_events`] to receive events. //! -//! [`LiquidityManager::get_and_clear_pending_events`]: crate::lsps0::message_handler::LiquidityManager::get_and_clear_pending_events +//! [`LiquidityManager::get_and_clear_pending_events`]: crate::LiquidityManager::get_and_clear_pending_events #[cfg(lsps1)] use crate::lsps1; diff --git a/src/lib.rs b/src/lib.rs index 277a990..c9f1ebb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,5 +43,8 @@ pub mod lsps0; #[cfg(lsps1)] mod lsps1; pub mod lsps2; +mod manager; mod sync; mod utils; + +pub use manager::{LiquidityManager, LiquidityProviderConfig}; diff --git a/src/lsps0/message_handler.rs b/src/lsps0/message_handler.rs index 570d886..9d79c15 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/message_handler.rs @@ -1,44 +1,25 @@ -//! Contains a [`CustomMessageHandler`] implementation for LSPS messages. - -#[cfg(lsps1)] -use { - crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}, - crate::lsps1::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment}, +//! Contains the logic to handle LSPS0 protocol messages. +//! +//! Please refer to the [LSPS0 +//! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more +//! information. + +use crate::lsps0::msgs::{ + LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, + ListProtocolsResponse, RequestId, ResponseError, }; +use crate::prelude::Vec; +use crate::sync::{Arc, Mutex}; +use crate::utils; -use crate::events::{Event, EventQueue}; -use crate::lsps0::msgs::RequestId; -use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; -use crate::lsps0::protocol::LSPS0MessageHandler; -use crate::lsps2::message_handler::{LSPS2Config, LSPS2MessageHandler}; -use crate::lsps2::msgs::{OpeningFeeParams, RawOpeningFeeParams}; -use crate::prelude::{HashMap, String, ToString, Vec}; -use crate::sync::{Arc, Mutex, RwLock}; - -use lightning::chain::{self, BestBlock, Confirm, Filter, Listen}; -use lightning::ln::channelmanager::{AChannelManager, ChainParameters, InterceptId}; -use lightning::ln::features::{InitFeatures, NodeFeatures}; use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::ln::peer_handler::{APeerManager, CustomMessageHandler}; -use lightning::ln::wire::CustomMessageReader; -use lightning::ln::ChannelId; use lightning::sign::EntropySource; -use lightning::util::errors::APIError; use lightning::util::logger::Level; -use lightning::util::ser::Readable; -use bitcoin::blockdata::constants::genesis_block; use bitcoin::secp256k1::PublicKey; -use bitcoin::BlockHash; - -#[cfg(lsps1)] -use chrono::Utc; -use core::convert::TryFrom; use core::ops::Deref; -const LSPS_FEATURE_BIT: usize = 729; - /// A trait used to implement a specific LSPS protocol. /// /// The messages the protocol uses need to be able to be mapped @@ -52,378 +33,181 @@ pub(crate) trait ProtocolMessageHandler { ) -> Result<(), LightningError>; } -/// A configuration for [`LiquidityManager`]. -/// -/// Allows end-user to configure options when using the [`LiquidityManager`] -/// to provide liquidity services to clients. -pub struct LiquidityProviderConfig { - /// LSPS1 Configuration - #[cfg(lsps1)] - pub lsps1_config: Option, - /// Optional configuration for JIT channels - /// should you want to support them. - pub lsps2_config: Option, -} - -/// The main interface into LSP functionality. -/// -/// Should be used as a [`CustomMessageHandler`] for your -/// [`PeerManager`]'s [`MessageHandler`]. -/// -/// Should provide a reference to your [`PeerManager`] by calling -/// [`LiquidityManager::set_peer_manager`] post construction. This allows the [`LiquidityManager`] to -/// wake the [`PeerManager`] when there are pending messages to be sent. -/// -/// Users need to continually poll [`LiquidityManager::get_and_clear_pending_events`] in order to surface -/// [`Event`]'s that likely need to be handled. -/// -/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`LSPS2MessageHandler::htlc_intercepted`] -/// and the [`Event::ChannelReady`] event parameters to [`LSPS2MessageHandler::channel_ready`]. -/// -/// [`PeerManager`]: lightning::ln::peer_handler::PeerManager -/// [`MessageHandler`]: lightning::ln::peer_handler::MessageHandler -/// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted -/// [`Event::ChannelReady`]: lightning::events::Event::ChannelReady -pub struct LiquidityManager< - ES: Deref + Clone, - CM: Deref + Clone, - PM: Deref + Clone, - C: Deref + Clone, -> where +/// A message handler capable of sending and handling LSPS0 messages. +pub struct LSPS0MessageHandler +where ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, { + entropy_source: ES, pending_messages: Arc>>, - pending_events: Arc, - request_id_to_method_map: Mutex>, - lsps0_message_handler: LSPS0MessageHandler, - #[cfg(lsps1)] - lsps1_message_handler: Option>, - lsps2_message_handler: Option>, - provider_config: Option, - channel_manager: CM, - chain_source: Option, - genesis_hash: Option, - best_block: Option>, + protocols: Vec, } -impl - LiquidityManager +impl LSPS0MessageHandler where ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, { - /// Constructor for the [`LiquidityManager`]. - /// - /// Sets up the required protocol message handlers based on the given [`LiquidityProviderConfig`]. + /// Returns a new instance of [`LSPS0MessageHandler`]. pub fn new( - entropy_source: ES, provider_config: Option, channel_manager: CM, - chain_source: Option, chain_params: Option, - ) -> Self -where { - let pending_messages = Arc::new(Mutex::new(vec![])); - let pending_events = Arc::new(EventQueue::new()); - - let lsps0_message_handler = LSPS0MessageHandler::new( - entropy_source.clone().clone(), - vec![], - Arc::clone(&pending_messages), - ); - - let lsps2_message_handler = provider_config.as_ref().and_then(|config| { - config.lsps2_config.as_ref().map(|config| { - LSPS2MessageHandler::new( - entropy_source.clone(), - config, - Arc::clone(&pending_messages), - Arc::clone(&pending_events), - channel_manager.clone(), - ) - }) - }); - - #[cfg(lsps1)] - let lsps1_message_handler = provider_config.as_ref().and_then(|config| { - config.lsps1_config.as_ref().map(|lsps1_config| { - LSPS1MessageHandler::new( - entropy_source.clone(), - lsps1_config, - Arc::clone(&pending_messages), - Arc::clone(&pending_events), - channel_manager.clone(), - chain_source.clone(), - ) - }) - }); - - Self { - pending_messages, - pending_events, - request_id_to_method_map: Mutex::new(HashMap::new()), - lsps0_message_handler, - #[cfg(lsps1)] - lsps1_message_handler, - lsps2_message_handler, - provider_config, - channel_manager, - chain_source, - genesis_hash: chain_params - .map(|chain_params| genesis_block(chain_params.network).header.block_hash()), - best_block: chain_params.map(|chain_params| RwLock::new(chain_params.best_block)), - } - } - - /// Returns a reference to the LSPS0 message handler. - pub fn lsps0_message_handler(&self) -> &LSPS0MessageHandler { - &self.lsps0_message_handler + entropy_source: ES, protocols: Vec, + pending_messages: Arc>>, + ) -> Self { + Self { entropy_source, protocols, pending_messages } } - /// Returns a reference to the LSPS1 message handler. - #[cfg(lsps1)] - pub fn lsps1_message_handler(&self) -> Option<&LSPS1MessageHandler> { - self.lsps1_message_handler.as_ref() - } - - /// Returns a reference to the LSPS2 message handler. - pub fn lsps2_message_handler(&self) -> Option<&LSPS2MessageHandler> { - self.lsps2_message_handler.as_ref() - } - - /// Blocks the current thread until next event is ready and returns it. + /// Calls LSPS0's `list_protocols`. /// - /// Typically you would spawn a thread or task that calls this in a loop. - #[cfg(feature = "std")] - pub fn wait_next_event(&self) -> Event { - self.pending_events.wait_next_event() - } + /// Please refer to the [LSPS0 + /// specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) + /// for more information. + pub fn list_protocols(&self, counterparty_node_id: PublicKey) { + let msg = LSPS0Message::Request( + utils::generate_request_id(&self.entropy_source), + LSPS0Request::ListProtocols(ListProtocolsRequest {}), + ); - /// Returns `Some` if an event is ready. - /// - /// Typically you would spawn a thread or task that calls this in a loop. - pub fn next_event(&self) -> Option { - self.pending_events.next_event() + self.enqueue_message(counterparty_node_id, msg); } - /// Returns and clears all events without blocking. - /// - /// Typically you would spawn a thread or task that calls this in a loop. - pub fn get_and_clear_pending_events(&self) -> Vec { - self.pending_events.get_and_clear_pending_events() + fn enqueue_message(&self, counterparty_node_id: PublicKey, message: LSPS0Message) { + self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); } - /// Set a [`PeerManager`] reference for all configured message handlers. - /// - /// This allows the message handlers to wake the [`PeerManager`] by calling - /// [`PeerManager::process_events`] after enqueing messages to be sent. - /// - /// Without this the messages will be sent based on whatever polling interval - /// your background processor uses. - /// - /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager - /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events - pub fn set_peer_manager(&self, peer_manager: PM) { - #[cfg(lsps1)] - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.set_peer_manager(peer_manager.clone()); - } - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.set_peer_manager(peer_manager); - } - } - - fn handle_lsps_message( - &self, msg: LSPSMessage, sender_node_id: &PublicKey, + fn handle_request( + &self, request_id: RequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, ) -> Result<(), lightning::ln::msgs::LightningError> { - match msg { - LSPSMessage::Invalid => { - return Err(LightningError { err: format!("{} did not understand a message we previously sent, maybe they don't support a protocol we are trying to use?", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Error)}); - } - LSPSMessage::LSPS0(msg) => { - self.lsps0_message_handler.handle_message(msg, sender_node_id)?; + match request { + LSPS0Request::ListProtocols(_) => { + let msg = LSPS0Message::Response( + request_id, + LSPS0Response::ListProtocols(ListProtocolsResponse { + protocols: self.protocols.clone(), + }), + ); + self.enqueue_message(*counterparty_node_id, msg); + Ok(()) } - #[cfg(lsps1)] - LSPSMessage::LSPS1(msg) => match &self.lsps1_message_handler { - Some(lsps1_message_handler) => { - lsps1_message_handler.handle_message(msg, sender_node_id)?; - } - None => { - return Err(LightningError { err: format!("Received LSPS1 message without LSPS1 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); - } - }, - LSPSMessage::LSPS2(msg) => match &self.lsps2_message_handler { - Some(lsps2_message_handler) => { - lsps2_message_handler.handle_message(msg, sender_node_id)?; - } - None => { - return Err(LightningError { err: format!("Received LSPS2 message without LSPS2 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); - } - }, } - Ok(()) } - fn enqueue_message(&self, node_id: PublicKey, msg: LSPSMessage) { - let mut pending_msgs = self.pending_messages.lock().unwrap(); - pending_msgs.push((node_id, msg)); - } -} - -impl - CustomMessageReader for LiquidityManager -where - ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, -{ - type CustomMessage = RawLSPSMessage; - - fn read( - &self, message_type: u16, buffer: &mut RD, - ) -> Result, lightning::ln::msgs::DecodeError> { - match message_type { - LSPS_MESSAGE_TYPE_ID => Ok(Some(RawLSPSMessage::read(buffer)?)), - _ => Ok(None), + fn handle_response( + &self, response: LSPS0Response, _counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + match response { + LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: _ }) => Ok(()), + LSPS0Response::ListProtocolsError(ResponseError { code, message, data, .. }) => { + Err(LightningError { + err: format!( + "ListProtocols error received. code = {}, message = {}, data = {:?}", + code, message, data + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } } } } -impl CustomMessageHandler - for LiquidityManager +impl ProtocolMessageHandler for LSPS0MessageHandler where ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, { - fn handle_custom_message( - &self, msg: Self::CustomMessage, sender_node_id: &PublicKey, - ) -> Result<(), lightning::ln::msgs::LightningError> { - let message = { - let mut request_id_to_method_map = self.request_id_to_method_map.lock().unwrap(); - LSPSMessage::from_str_with_id_map(&msg.payload, &mut request_id_to_method_map) - }; + type ProtocolMessage = LSPS0Message; + const PROTOCOL_NUMBER: Option = None; + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { match message { - Ok(msg) => self.handle_lsps_message(msg, sender_node_id), - Err(_) => { - self.enqueue_message(*sender_node_id, LSPSMessage::Invalid); - Ok(()) + LSPS0Message::Request(request_id, request) => { + self.handle_request(request_id, request, counterparty_node_id) + } + LSPS0Message::Response(_, response) => { + self.handle_response(response, counterparty_node_id) } } } +} - fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { - let mut request_id_to_method_map = self.request_id_to_method_map.lock().unwrap(); - self.pending_messages - .lock() - .unwrap() - .drain(..) - .map(|(public_key, lsps_message)| { - if let Some((request_id, method_name)) = lsps_message.get_request_id_and_method() { - request_id_to_method_map.insert(request_id, method_name); - } - ( - public_key, - RawLSPSMessage { payload: serde_json::to_string(&lsps_message).unwrap() }, - ) - }) - .collect() - } +#[cfg(test)] +mod tests { - fn provided_node_features(&self) -> NodeFeatures { - let mut features = NodeFeatures::empty(); + use alloc::string::ToString; + use alloc::sync::Arc; - if self.provider_config.is_some() { - features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); - } + use super::*; - features + struct TestEntropy {} + impl EntropySource for TestEntropy { + fn get_secure_random_bytes(&self) -> [u8; 32] { + [0; 32] + } } - fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { - let mut features = InitFeatures::empty(); + #[test] + fn test_handle_list_protocols_request() { + let entropy = Arc::new(TestEntropy {}); + let protocols: Vec = vec![]; + let pending_messages = Arc::new(Mutex::new(vec![])); - if self.provider_config.is_some() { - features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); - } + let lsps0_handler = + Arc::new(LSPS0MessageHandler::new(entropy, protocols, pending_messages.clone())); - features - } -} + let list_protocols_request = LSPS0Message::Request( + RequestId("xyz123".to_string()), + LSPS0Request::ListProtocols(ListProtocolsRequest {}), + ); + let counterparty_node_id = utils::parse_pubkey( + "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", + ) + .unwrap(); -impl Listen - for LiquidityManager -where - ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, -{ - fn filtered_block_connected( - &self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, - height: u32, - ) { - if let Some(best_block) = &self.best_block { - let best_block = best_block.read().unwrap(); - assert_eq!(best_block.block_hash(), header.prev_blockhash, - "Blocks must be connected in chain-order - the connected header must build on the last connected header"); - assert_eq!(best_block.height(), height - 1, - "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height"); - } + lsps0_handler.handle_message(list_protocols_request, &counterparty_node_id).unwrap(); + let pending_messages = pending_messages.lock().unwrap(); - self.transactions_confirmed(header, txdata, height); - self.best_block_updated(header, height); - } + assert_eq!(pending_messages.len(), 1); - fn block_disconnected(&self, header: &bitcoin::BlockHeader, height: u32) { - let new_height = height - 1; - if let Some(best_block) = &self.best_block { - let mut best_block = best_block.write().unwrap(); - assert_eq!(best_block.block_hash(), header.block_hash(), - "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header"); - assert_eq!(best_block.height(), height, - "Blocks must be disconnected in chain-order - the disconnected block must have the correct height"); - *best_block = BestBlock::new(header.prev_blockhash, new_height) - } + let (pubkey, message) = &pending_messages[0]; - // TODO: Call block_disconnected on all sub-modules that require it, e.g., LSPS1MessageHandler. - // Internally this should call transaction_unconfirmed for all transactions that were - // confirmed at a height <= the one we now disconnected. + assert_eq!(*pubkey, counterparty_node_id); + assert_eq!( + *message, + LSPSMessage::LSPS0(LSPS0Message::Response( + RequestId("xyz123".to_string()), + LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: vec![] }) + )) + ); } -} -impl Confirm - for LiquidityManager -where - ES::Target: EntropySource, - CM::Target: AChannelManager, - PM::Target: APeerManager, - C::Target: Filter, -{ - fn transactions_confirmed( - &self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, - height: u32, - ) { - // TODO: Call transactions_confirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. - } + #[test] + fn test_list_protocols() { + let pending_messages = Arc::new(Mutex::new(vec![])); - fn transaction_unconfirmed(&self, txid: &bitcoin::Txid) { - // TODO: Call transaction_unconfirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. - // Internally this should call transaction_unconfirmed for all transactions that were - // confirmed at a height <= the one we now unconfirmed. - } + let lsps0_handler = Arc::new(LSPS0MessageHandler::new( + Arc::new(TestEntropy {}), + vec![1, 2, 3], + pending_messages.clone(), + )); - fn best_block_updated(&self, header: &bitcoin::BlockHeader, height: u32) { - // TODO: Call best_block_updated on all sub-modules that require it, e.g., LSPS1MessageHandler. - } + let counterparty_node_id = utils::parse_pubkey( + "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", + ) + .unwrap(); - fn get_relevant_txids(&self) -> Vec<(bitcoin::Txid, Option)> { - // TODO: Collect relevant txids from all sub-modules that, e.g., LSPS1MessageHandler. - Vec::new() + lsps0_handler.list_protocols(counterparty_node_id); + let pending_messages = pending_messages.lock().unwrap(); + + assert_eq!(pending_messages.len(), 1); + + let (pubkey, message) = &pending_messages[0]; + + assert_eq!(*pubkey, counterparty_node_id); + assert_eq!( + *message, + LSPSMessage::LSPS0(LSPS0Message::Request( + RequestId("00000000000000000000000000000000".to_string()), + LSPS0Request::ListProtocols(ListProtocolsRequest {}) + )) + ); } } diff --git a/src/lsps0/mod.rs b/src/lsps0/mod.rs index 00185eb..9843fd8 100644 --- a/src/lsps0/mod.rs +++ b/src/lsps0/mod.rs @@ -11,4 +11,3 @@ pub mod message_handler; pub mod msgs; -pub mod protocol; diff --git a/src/lsps0/protocol.rs b/src/lsps0/protocol.rs deleted file mode 100644 index 2d0abbb..0000000 --- a/src/lsps0/protocol.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Contains the logic to handle LSPS0 protocol messages. -//! -//! Please refer to the [LSPS0 -//! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more -//! information. - -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{ - LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, - ListProtocolsResponse, RequestId, ResponseError, -}; -use crate::prelude::Vec; -use crate::sync::{Arc, Mutex}; -use crate::utils; - -use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::sign::EntropySource; -use lightning::util::logger::Level; - -use bitcoin::secp256k1::PublicKey; - -use core::ops::Deref; - -/// A message handler capable of sending and handling LSPS0 messages. -pub struct LSPS0MessageHandler -where - ES::Target: EntropySource, -{ - entropy_source: ES, - pending_messages: Arc>>, - protocols: Vec, -} - -impl LSPS0MessageHandler -where - ES::Target: EntropySource, -{ - /// Returns a new instance of [`LSPS0MessageHandler`]. - pub fn new( - entropy_source: ES, protocols: Vec, - pending_messages: Arc>>, - ) -> Self { - Self { entropy_source, protocols, pending_messages } - } - - /// Calls LSPS0's `list_protocols`. - /// - /// Please refer to the [LSPS0 - /// specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) - /// for more information. - pub fn list_protocols(&self, counterparty_node_id: PublicKey) { - let msg = LSPS0Message::Request( - utils::generate_request_id(&self.entropy_source), - LSPS0Request::ListProtocols(ListProtocolsRequest {}), - ); - - self.enqueue_message(counterparty_node_id, msg); - } - - fn enqueue_message(&self, counterparty_node_id: PublicKey, message: LSPS0Message) { - self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); - } - - fn handle_request( - &self, request_id: RequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, - ) -> Result<(), lightning::ln::msgs::LightningError> { - match request { - LSPS0Request::ListProtocols(_) => { - let msg = LSPS0Message::Response( - request_id, - LSPS0Response::ListProtocols(ListProtocolsResponse { - protocols: self.protocols.clone(), - }), - ); - self.enqueue_message(*counterparty_node_id, msg); - Ok(()) - } - } - } - - fn handle_response( - &self, response: LSPS0Response, _counterparty_node_id: &PublicKey, - ) -> Result<(), LightningError> { - match response { - LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: _ }) => Ok(()), - LSPS0Response::ListProtocolsError(ResponseError { code, message, data, .. }) => { - Err(LightningError { - err: format!( - "ListProtocols error received. code = {}, message = {}, data = {:?}", - code, message, data - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }) - } - } - } -} - -impl ProtocolMessageHandler for LSPS0MessageHandler -where - ES::Target: EntropySource, -{ - type ProtocolMessage = LSPS0Message; - const PROTOCOL_NUMBER: Option = None; - - fn handle_message( - &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, - ) -> Result<(), LightningError> { - match message { - LSPS0Message::Request(request_id, request) => { - self.handle_request(request_id, request, counterparty_node_id) - } - LSPS0Message::Response(_, response) => { - self.handle_response(response, counterparty_node_id) - } - } - } -} - -#[cfg(test)] -mod tests { - - use alloc::string::ToString; - use alloc::sync::Arc; - - use super::*; - - struct TestEntropy {} - impl EntropySource for TestEntropy { - fn get_secure_random_bytes(&self) -> [u8; 32] { - [0; 32] - } - } - - #[test] - fn test_handle_list_protocols_request() { - let entropy = Arc::new(TestEntropy {}); - let protocols: Vec = vec![]; - let pending_messages = Arc::new(Mutex::new(vec![])); - - let lsps0_handler = - Arc::new(LSPS0MessageHandler::new(entropy, protocols, pending_messages.clone())); - - let list_protocols_request = LSPS0Message::Request( - RequestId("xyz123".to_string()), - LSPS0Request::ListProtocols(ListProtocolsRequest {}), - ); - let counterparty_node_id = utils::parse_pubkey( - "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", - ) - .unwrap(); - - lsps0_handler.handle_message(list_protocols_request, &counterparty_node_id).unwrap(); - let pending_messages = pending_messages.lock().unwrap(); - - assert_eq!(pending_messages.len(), 1); - - let (pubkey, message) = &pending_messages[0]; - - assert_eq!(*pubkey, counterparty_node_id); - assert_eq!( - *message, - LSPSMessage::LSPS0(LSPS0Message::Response( - RequestId("xyz123".to_string()), - LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: vec![] }) - )) - ); - } - - #[test] - fn test_list_protocols() { - let pending_messages = Arc::new(Mutex::new(vec![])); - - let lsps0_handler = Arc::new(LSPS0MessageHandler::new( - Arc::new(TestEntropy {}), - vec![1, 2, 3], - pending_messages.clone(), - )); - - let counterparty_node_id = utils::parse_pubkey( - "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", - ) - .unwrap(); - - lsps0_handler.list_protocols(counterparty_node_id); - let pending_messages = pending_messages.lock().unwrap(); - - assert_eq!(pending_messages.len(), 1); - - let (pubkey, message) = &pending_messages[0]; - - assert_eq!(*pubkey, counterparty_node_id); - assert_eq!( - *message, - LSPSMessage::LSPS0(LSPS0Message::Request( - RequestId("00000000000000000000000000000000".to_string()), - LSPS0Request::ListProtocols(ListProtocolsRequest {}) - )) - ); - } -} diff --git a/src/manager.rs b/src/manager.rs new file mode 100644 index 0000000..78be13f --- /dev/null +++ b/src/manager.rs @@ -0,0 +1,407 @@ +use crate::events::{Event, EventQueue}; +use crate::lsps0::message_handler::LSPS0MessageHandler; +use crate::lsps0::message_handler::ProtocolMessageHandler; +use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; + +#[cfg(lsps1)] +use { + crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}, + crate::lsps1::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment}, +}; + +use crate::lsps2::message_handler::{LSPS2Config, LSPS2MessageHandler}; +use crate::prelude::{HashMap, String, Vec}; +use crate::sync::{Arc, Mutex, RwLock}; + +use lightning::chain::{self, BestBlock, Confirm, Filter, Listen}; +use lightning::ln::channelmanager::{AChannelManager, ChainParameters}; +use lightning::ln::features::{InitFeatures, NodeFeatures}; +use lightning::ln::msgs::{ErrorAction, LightningError}; +use lightning::ln::peer_handler::{APeerManager, CustomMessageHandler}; +use lightning::ln::wire::CustomMessageReader; +use lightning::sign::EntropySource; +use lightning::util::logger::Level; +use lightning::util::ser::Readable; + +use bitcoin::blockdata::constants::genesis_block; +use bitcoin::secp256k1::PublicKey; +use bitcoin::BlockHash; + +use core::ops::Deref; +const LSPS_FEATURE_BIT: usize = 729; + +/// A configuration for [`LiquidityManager`]. +/// +/// Allows end-user to configure options when using the [`LiquidityManager`] +/// to provide liquidity services to clients. +pub struct LiquidityProviderConfig { + /// LSPS1 Configuration + #[cfg(lsps1)] + pub lsps1_config: Option, + /// Optional configuration for JIT channels + /// should you want to support them. + pub lsps2_config: Option, +} + +/// The main interface into LSP functionality. +/// +/// Should be used as a [`CustomMessageHandler`] for your +/// [`PeerManager`]'s [`MessageHandler`]. +/// +/// Should provide a reference to your [`PeerManager`] by calling +/// [`LiquidityManager::set_peer_manager`] post construction. This allows the [`LiquidityManager`] to +/// wake the [`PeerManager`] when there are pending messages to be sent. +/// +/// Users need to continually poll [`LiquidityManager::get_and_clear_pending_events`] in order to surface +/// [`Event`]'s that likely need to be handled. +/// +/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`LSPS2MessageHandler::htlc_intercepted`] +/// and the [`Event::ChannelReady`] event parameters to [`LSPS2MessageHandler::channel_ready`]. +/// +/// [`PeerManager`]: lightning::ln::peer_handler::PeerManager +/// [`MessageHandler`]: lightning::ln::peer_handler::MessageHandler +/// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted +/// [`Event::ChannelReady`]: lightning::events::Event::ChannelReady +pub struct LiquidityManager< + ES: Deref + Clone, + CM: Deref + Clone, + PM: Deref + Clone, + C: Deref + Clone, +> where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + pending_messages: Arc>>, + pending_events: Arc, + request_id_to_method_map: Mutex>, + lsps0_message_handler: LSPS0MessageHandler, + #[cfg(lsps1)] + lsps1_message_handler: Option>, + lsps2_message_handler: Option>, + provider_config: Option, + channel_manager: CM, + chain_source: Option, + genesis_hash: Option, + best_block: Option>, +} + +impl + LiquidityManager +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + /// Constructor for the [`LiquidityManager`]. + /// + /// Sets up the required protocol message handlers based on the given [`LiquidityProviderConfig`]. + pub fn new( + entropy_source: ES, provider_config: Option, channel_manager: CM, + chain_source: Option, chain_params: Option, + ) -> Self +where { + let pending_messages = Arc::new(Mutex::new(vec![])); + let pending_events = Arc::new(EventQueue::new()); + + let lsps0_message_handler = LSPS0MessageHandler::new( + entropy_source.clone().clone(), + vec![], + Arc::clone(&pending_messages), + ); + + let lsps2_message_handler = provider_config.as_ref().and_then(|config| { + config.lsps2_config.as_ref().map(|config| { + LSPS2MessageHandler::new( + entropy_source.clone(), + config, + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + channel_manager.clone(), + ) + }) + }); + + #[cfg(lsps1)] + let lsps1_message_handler = provider_config.as_ref().and_then(|config| { + config.lsps1_config.as_ref().map(|lsps1_config| { + LSPS1MessageHandler::new( + entropy_source.clone(), + lsps1_config, + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + channel_manager.clone(), + chain_source.clone(), + ) + }) + }); + + Self { + pending_messages, + pending_events, + request_id_to_method_map: Mutex::new(HashMap::new()), + lsps0_message_handler, + #[cfg(lsps1)] + lsps1_message_handler, + lsps2_message_handler, + provider_config, + channel_manager, + chain_source, + genesis_hash: chain_params + .map(|chain_params| genesis_block(chain_params.network).header.block_hash()), + best_block: chain_params.map(|chain_params| RwLock::new(chain_params.best_block)), + } + } + + /// Returns a reference to the LSPS0 message handler. + pub fn lsps0_message_handler(&self) -> &LSPS0MessageHandler { + &self.lsps0_message_handler + } + + /// Returns a reference to the LSPS1 message handler. + #[cfg(lsps1)] + pub fn lsps1_message_handler(&self) -> Option<&LSPS1MessageHandler> { + self.lsps1_message_handler.as_ref() + } + + /// Returns a reference to the LSPS2 message handler. + pub fn lsps2_message_handler(&self) -> Option<&LSPS2MessageHandler> { + self.lsps2_message_handler.as_ref() + } + + /// Blocks the current thread until next event is ready and returns it. + /// + /// Typically you would spawn a thread or task that calls this in a loop. + #[cfg(feature = "std")] + pub fn wait_next_event(&self) -> Event { + self.pending_events.wait_next_event() + } + + /// Returns `Some` if an event is ready. + /// + /// Typically you would spawn a thread or task that calls this in a loop. + pub fn next_event(&self) -> Option { + self.pending_events.next_event() + } + + /// Returns and clears all events without blocking. + /// + /// Typically you would spawn a thread or task that calls this in a loop. + pub fn get_and_clear_pending_events(&self) -> Vec { + self.pending_events.get_and_clear_pending_events() + } + + /// Set a [`PeerManager`] reference for all configured message handlers. + /// + /// This allows the message handlers to wake the [`PeerManager`] by calling + /// [`PeerManager::process_events`] after enqueing messages to be sent. + /// + /// Without this the messages will be sent based on whatever polling interval + /// your background processor uses. + /// + /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager + /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events + pub fn set_peer_manager(&self, peer_manager: PM) { + #[cfg(lsps1)] + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.set_peer_manager(peer_manager.clone()); + } + if let Some(lsps2_message_handler) = &self.lsps2_message_handler { + lsps2_message_handler.set_peer_manager(peer_manager); + } + } + + fn handle_lsps_message( + &self, msg: LSPSMessage, sender_node_id: &PublicKey, + ) -> Result<(), lightning::ln::msgs::LightningError> { + match msg { + LSPSMessage::Invalid => { + return Err(LightningError { err: format!("{} did not understand a message we previously sent, maybe they don't support a protocol we are trying to use?", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Error)}); + } + LSPSMessage::LSPS0(msg) => { + self.lsps0_message_handler.handle_message(msg, sender_node_id)?; + } + #[cfg(lsps1)] + LSPSMessage::LSPS1(msg) => match &self.lsps1_message_handler { + Some(lsps1_message_handler) => { + lsps1_message_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS1 message without LSPS1 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + }, + LSPSMessage::LSPS2(msg) => match &self.lsps2_message_handler { + Some(lsps2_message_handler) => { + lsps2_message_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS2 message without LSPS2 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + }, + } + Ok(()) + } + + fn enqueue_message(&self, node_id: PublicKey, msg: LSPSMessage) { + let mut pending_msgs = self.pending_messages.lock().unwrap(); + pending_msgs.push((node_id, msg)); + } +} + +impl + CustomMessageReader for LiquidityManager +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + type CustomMessage = RawLSPSMessage; + + fn read( + &self, message_type: u16, buffer: &mut RD, + ) -> Result, lightning::ln::msgs::DecodeError> { + match message_type { + LSPS_MESSAGE_TYPE_ID => Ok(Some(RawLSPSMessage::read(buffer)?)), + _ => Ok(None), + } + } +} + +impl CustomMessageHandler + for LiquidityManager +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + fn handle_custom_message( + &self, msg: Self::CustomMessage, sender_node_id: &PublicKey, + ) -> Result<(), lightning::ln::msgs::LightningError> { + let message = { + let mut request_id_to_method_map = self.request_id_to_method_map.lock().unwrap(); + LSPSMessage::from_str_with_id_map(&msg.payload, &mut request_id_to_method_map) + }; + + match message { + Ok(msg) => self.handle_lsps_message(msg, sender_node_id), + Err(_) => { + self.enqueue_message(*sender_node_id, LSPSMessage::Invalid); + Ok(()) + } + } + } + + fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { + let mut request_id_to_method_map = self.request_id_to_method_map.lock().unwrap(); + self.pending_messages + .lock() + .unwrap() + .drain(..) + .map(|(public_key, lsps_message)| { + if let Some((request_id, method_name)) = lsps_message.get_request_id_and_method() { + request_id_to_method_map.insert(request_id, method_name); + } + ( + public_key, + RawLSPSMessage { payload: serde_json::to_string(&lsps_message).unwrap() }, + ) + }) + .collect() + } + + fn provided_node_features(&self) -> NodeFeatures { + let mut features = NodeFeatures::empty(); + + if self.provider_config.is_some() { + features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); + } + + features + } + + fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { + let mut features = InitFeatures::empty(); + + if self.provider_config.is_some() { + features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); + } + + features + } +} + +impl Listen + for LiquidityManager +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + fn filtered_block_connected( + &self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, + height: u32, + ) { + if let Some(best_block) = &self.best_block { + let best_block = best_block.read().unwrap(); + assert_eq!(best_block.block_hash(), header.prev_blockhash, + "Blocks must be connected in chain-order - the connected header must build on the last connected header"); + assert_eq!(best_block.height(), height - 1, + "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height"); + } + + self.transactions_confirmed(header, txdata, height); + self.best_block_updated(header, height); + } + + fn block_disconnected(&self, header: &bitcoin::BlockHeader, height: u32) { + let new_height = height - 1; + if let Some(best_block) = &self.best_block { + let mut best_block = best_block.write().unwrap(); + assert_eq!(best_block.block_hash(), header.block_hash(), + "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header"); + assert_eq!(best_block.height(), height, + "Blocks must be disconnected in chain-order - the disconnected block must have the correct height"); + *best_block = BestBlock::new(header.prev_blockhash, new_height) + } + + // TODO: Call block_disconnected on all sub-modules that require it, e.g., LSPS1MessageHandler. + // Internally this should call transaction_unconfirmed for all transactions that were + // confirmed at a height <= the one we now disconnected. + } +} + +impl Confirm + for LiquidityManager +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + fn transactions_confirmed( + &self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, + height: u32, + ) { + // TODO: Call transactions_confirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. + } + + fn transaction_unconfirmed(&self, txid: &bitcoin::Txid) { + // TODO: Call transaction_unconfirmed on all sub-modules that require it, e.g., LSPS1MessageHandler. + // Internally this should call transaction_unconfirmed for all transactions that were + // confirmed at a height <= the one we now unconfirmed. + } + + fn best_block_updated(&self, header: &bitcoin::BlockHeader, height: u32) { + // TODO: Call best_block_updated on all sub-modules that require it, e.g., LSPS1MessageHandler. + } + + fn get_relevant_txids(&self) -> Vec<(bitcoin::Txid, Option)> { + // TODO: Collect relevant txids from all sub-modules that, e.g., LSPS1MessageHandler. + Vec::new() + } +} From 4bbe70a9b72881365bb246c09e4b18fc457b478d Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 Dec 2023 16:11:22 +0100 Subject: [PATCH 10/18] Drop unnecessary re-exports in LSPS2 (for now) --- src/events.rs | 2 +- src/lsps2/message_handler.rs | 12 ++++++------ src/lsps2/mod.rs | 3 --- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/events.rs b/src/events.rs index b3a3a8b..ac52f05 100644 --- a/src/events.rs +++ b/src/events.rs @@ -79,7 +79,7 @@ impl EventQueue { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { /// An LSPS2 (JIT Channel) protocol event. - LSPS2(lsps2::LSPS2Event), + LSPS2(lsps2::event::LSPS2Event), /// An LSPS1 protocol event. #[cfg(lsps1)] LSPS1(lsps1::event::Event), diff --git a/src/lsps2/message_handler.rs b/src/lsps2/message_handler.rs index e5ff7aa..53803e1 100644 --- a/src/lsps2/message_handler.rs +++ b/src/lsps2/message_handler.rs @@ -12,8 +12,8 @@ use crate::events::EventQueue; use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps2::event::LSPS2Event; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; -use crate::lsps2::LSPS2Event; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; use crate::utils; @@ -513,7 +513,7 @@ where /// /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo + /// [`LSPS2Event::GetInfo`]: crate::lsps2::event::LSPS2Event::GetInfo pub fn invalid_token_provided( &self, counterparty_node_id: PublicKey, request_id: RequestId, ) -> Result<(), APIError> { @@ -551,7 +551,7 @@ where /// /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::LSPS2Event::GetInfo + /// [`LSPS2Event::GetInfo`]: crate::lsps2::event::LSPS2Event::GetInfo pub fn opening_fee_params_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, opening_fee_params_menu: Vec, @@ -595,7 +595,7 @@ where /// /// Should be called in response to receiving a [`LSPS2Event::GetInfoResponse`] event. /// - /// [`LSPS2Event::GetInfoResponse`]: crate::lsps2::LSPS2Event::GetInfoResponse + /// [`LSPS2Event::GetInfoResponse`]: crate::lsps2::event::LSPS2Event::GetInfoResponse pub fn opening_fee_params_selected( &self, counterparty_node_id: PublicKey, jit_channel_id: u128, opening_fee_params: OpeningFeeParams, @@ -657,7 +657,7 @@ where /// /// Should be called in response to receiving a [`LSPS2Event::BuyRequest`] event. /// - /// [`LSPS2Event::BuyRequest`]: crate::lsps2::LSPS2Event::BuyRequest + /// [`LSPS2Event::BuyRequest`]: crate::lsps2::event::LSPS2Event::BuyRequest pub fn invoice_parameters_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, @@ -719,7 +719,7 @@ where /// Will do nothing if the scid does not match any of the ones we gave out. /// /// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted - /// [`LSPS2Event::OpenChannel`]: crate::lsps2::LSPS2Event::OpenChannel + /// [`LSPS2Event::OpenChannel`]: crate::lsps2::event::LSPS2Event::OpenChannel pub fn htlc_intercepted( &self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64, ) -> Result<(), APIError> { diff --git a/src/lsps2/mod.rs b/src/lsps2/mod.rs index f5f5aee..3a6cdc7 100644 --- a/src/lsps2/mod.rs +++ b/src/lsps2/mod.rs @@ -13,6 +13,3 @@ pub mod event; pub mod message_handler; pub mod msgs; pub(crate) mod utils; - -pub use event::LSPS2Event; -pub use msgs::{BuyResponse, GetInfoResponse, OpeningFeeParams, RawOpeningFeeParams}; From 3dfd51caee77642858063fcc44dce3b30e84643b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 11 Dec 2023 14:56:17 +0100 Subject: [PATCH 11/18] Add missing docs for `lsps1/msgs.rs`, make submodules `pub` In parallel to LSPS2, we expose the internal `msgs`/`message_handler`/`event` modules of LSPS1. To this end, we add docs to all message types, mark docs in `event.rs` as TODO, and hide the public methods of LSPS1's message handler, as we'll want to expose-and-document them individually at a later date. --- src/lib.rs | 2 +- src/lsps1/event.rs | 53 ++++++++++---- src/lsps1/message_handler.rs | 93 +++++++++++++++---------- src/lsps1/mod.rs | 5 +- src/lsps1/msgs.rs | 129 ++++++++++++++++++++++++++++++++--- src/lsps1/utils.rs | 4 +- src/manager.rs | 5 +- 7 files changed, 222 insertions(+), 69 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c9f1ebb..9ac32ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ mod prelude { pub mod events; pub mod lsps0; #[cfg(lsps1)] -mod lsps1; +pub mod lsps1; pub mod lsps2; mod manager; mod sync; diff --git a/src/lsps1/event.rs b/src/lsps1/event.rs index 73a51a8..b0657e0 100644 --- a/src/lsps1/event.rs +++ b/src/lsps1/event.rs @@ -1,6 +1,6 @@ //! Contains LSPS1 event types -use super::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, Payment}; +use super::msgs::{ChannelInfo, OptionsSupported, OrderId, OrderParams, OrderPayment}; use crate::lsps0::msgs::RequestId; use crate::prelude::String; @@ -10,52 +10,81 @@ use bitcoin::secp256k1::PublicKey; /// An event which you should probably take some action in response to. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { + /// TODO GetInfoResponse { + /// TODO id: u128, + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, + /// TODO version: u16, + /// TODO website: String, + /// TODO options_supported: OptionsSupported, }, - + /// TODO CreateInvoice { + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, - order: Order, + /// TODO + order: OrderParams, }, - + /// TODO DisplayOrder { + /// TODO id: u128, + /// TODO counterparty_node_id: PublicKey, - order: Order, - payment: Payment, + /// TODO + order: OrderParams, + /// TODO + payment: OrderPayment, + /// TODO channel: Option, }, - + /// TODO PayforChannel { + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, - order: Order, - payment: Payment, + /// TODO + order: OrderParams, + /// TODO + payment: OrderPayment, + /// TODO channel: Option, }, - + /// TODO CheckPaymentConfirmation { + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, + /// TODO order_id: OrderId, }, - + /// TODO OpenChannel { + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, + /// TODO order_id: OrderId, }, - + /// TODO Refund { + /// TODO request_id: RequestId, + /// TODO counterparty_node_id: PublicKey, + /// TODO order_id: OrderId, }, } diff --git a/src/lsps1/message_handler.rs b/src/lsps1/message_handler.rs index e3d7113..a1ca7cf 100644 --- a/src/lsps1/message_handler.rs +++ b/src/lsps1/message_handler.rs @@ -12,7 +12,8 @@ use super::msgs::{ ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, GetOrderRequest, GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, - Order, OrderId, OrderState, Payment, LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, + OrderId, OrderParams, OrderPayment, OrderState, + LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, }; use super::utils::is_valid; @@ -40,10 +41,15 @@ use core::ops::Deref; const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; +/// Configuration options for LSPS1 channel requests. pub struct LSPS1Config { + /// A token to be send with each channel request. pub token: Option, - pub max_fees: Option, + /// The maximally allowed channel fees. + pub max_channel_fees_msat: Option, + /// The options supported by the LSP. pub options_supported: Option, + /// The LSP's website. pub website: Option, } @@ -59,7 +65,7 @@ impl From for LightningError { enum InboundRequestState { InfoRequested, OptionsSupport { version: u16, options_supported: OptionsSupported }, - OrderRequested { version: u16, order: Order }, + OrderRequested { version: u16, order: OrderParams }, PendingPayment { order_id: OrderId }, AwaitingConfirmation { id: u128, order_id: OrderId }, } @@ -90,7 +96,7 @@ impl InboundRequestState { } } - pub fn order_requested(&self, order: Order) -> Result { + fn order_requested(&self, order: OrderParams) -> Result { match self { InboundRequestState::OptionsSupport { version, options_supported } => { if is_valid(&order, options_supported) { @@ -109,8 +115,8 @@ impl InboundRequestState { } } - pub fn order_received( - &self, response_order: &Order, order_id: OrderId, + fn order_received( + &self, response_order: &OrderParams, order_id: OrderId, ) -> Result { match self { InboundRequestState::OrderRequested { version, order } => { @@ -130,7 +136,7 @@ impl InboundRequestState { } } - pub fn pay_for_channel(&self, channel_id: u128) -> Result { + fn pay_for_channel(&self, channel_id: u128) -> Result { match self { InboundRequestState::PendingPayment { order_id } => { Ok(InboundRequestState::AwaitingConfirmation { @@ -152,11 +158,11 @@ struct InboundCRChannel { } impl InboundCRChannel { - pub fn new(id: u128) -> Self { + fn new(id: u128) -> Self { Self { id, state: InboundRequestState::InfoRequested } } - pub fn info_received( + fn info_received( &mut self, versions: Vec, options: OptionsSupported, ) -> Result { self.state = self.state.info_received(versions, options)?; @@ -170,7 +176,7 @@ impl InboundCRChannel { } } - pub fn order_requested(&mut self, order: Order) -> Result { + fn order_requested(&mut self, order: OrderParams) -> Result { self.state = self.state.order_requested(order)?; match self.state { @@ -184,14 +190,14 @@ impl InboundCRChannel { } } - pub fn order_received( - &mut self, order: &Order, order_id: OrderId, + fn order_received( + &mut self, order: &OrderParams, order_id: OrderId, ) -> Result<(), LightningError> { self.state = self.state.order_received(order, order_id)?; Ok(()) } - pub fn pay_for_channel(&mut self, channel_id: u128) -> Result<(), LightningError> { + fn pay_for_channel(&mut self, channel_id: u128) -> Result<(), LightningError> { self.state = self.state.pay_for_channel(channel_id)?; Ok(()) } @@ -205,7 +211,7 @@ enum OutboundRequestState { } impl OutboundRequestState { - pub fn create_payment_invoice(&self) -> Result { + fn create_payment_invoice(&self) -> Result { match self { OutboundRequestState::OrderCreated { order_id } => { Ok(OutboundRequestState::WaitingPayment { order_id: order_id.clone() }) @@ -219,10 +225,10 @@ impl OutboundRequestState { } struct OutboundLSPS1Config { - order: Order, + order: OrderParams, created_at: chrono::DateTime, expires_at: chrono::DateTime, - payment: Payment, + payment: OrderPayment, } struct OutboundCRChannel { @@ -231,21 +237,21 @@ struct OutboundCRChannel { } impl OutboundCRChannel { - pub fn new( - order: Order, created_at: chrono::DateTime, expires_at: chrono::DateTime, - order_id: OrderId, payment: Payment, + fn new( + order: OrderParams, created_at: chrono::DateTime, expires_at: chrono::DateTime, + order_id: OrderId, payment: OrderPayment, ) -> Self { Self { state: OutboundRequestState::OrderCreated { order_id }, config: OutboundLSPS1Config { order, created_at, expires_at, payment }, } } - pub fn create_payment_invoice(&mut self) -> Result<(), LightningError> { + fn create_payment_invoice(&mut self) -> Result<(), LightningError> { self.state = self.state.create_payment_invoice()?; Ok(()) } - pub fn check_order_validity(&self, options_supported: &OptionsSupported) -> bool { + fn check_order_validity(&self, options_supported: &OptionsSupported) -> bool { let order = &self.config.order; is_valid(order, options_supported) @@ -261,27 +267,28 @@ struct PeerState { } impl PeerState { - pub fn insert_inbound_channel(&mut self, id: u128, channel: InboundCRChannel) { + fn insert_inbound_channel(&mut self, id: u128, channel: InboundCRChannel) { self.inbound_channels_by_id.insert(id, channel); } - pub fn insert_outbound_channel(&mut self, order_id: OrderId, channel: OutboundCRChannel) { + fn insert_outbound_channel(&mut self, order_id: OrderId, channel: OutboundCRChannel) { self.outbound_channels_by_order_id.insert(order_id, channel); } - pub fn insert_request(&mut self, request_id: RequestId, channel_id: u128) { + fn insert_request(&mut self, request_id: RequestId, channel_id: u128) { self.request_to_cid.insert(request_id, channel_id); } - pub fn remove_inbound_channel(&mut self, id: u128) { + fn remove_inbound_channel(&mut self, id: u128) { self.inbound_channels_by_id.remove(&id); } - pub fn remove_outbound_channel(&mut self, order_id: OrderId) { + fn remove_outbound_channel(&mut self, order_id: OrderId) { self.outbound_channels_by_order_id.remove(&order_id); } } +/// The main object allowing to send and receive LSPS1 messages. pub struct LSPS1MessageHandler where ES::Target: EntropySource, @@ -298,7 +305,7 @@ where per_peer_state: RwLock>>, options_config: Option, website: Option, - max_fees: Option, + max_channel_fees_msat: Option, } impl LSPS1MessageHandler @@ -324,15 +331,25 @@ where per_peer_state: RwLock::new(HashMap::new()), options_config: config.options_supported.clone(), website: config.website.clone(), - max_fees: config.max_fees, + max_channel_fees_msat: config.max_channel_fees_msat, } } + /// Set a [`PeerManager`] reference for the message handler. + /// + /// This allows the message handler to wake the [`PeerManager`] by calling + /// [`PeerManager::process_events`] after enqueing messages to be sent. + /// + /// Without this the messages will be sent based on whatever polling interval + /// your background processor uses. + /// + /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager + /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events pub fn set_peer_manager(&self, peer_manager: PM) { *self.peer_manager.lock().unwrap() = Some(peer_manager); } - pub fn request_for_info(&self, counterparty_node_id: PublicKey, channel_id: u128) { + fn request_for_info(&self, counterparty_node_id: PublicKey, channel_id: u128) { let channel = InboundCRChannel::new(channel_id); let mut outer_state_lock = self.per_peer_state.write().unwrap(); @@ -439,8 +456,8 @@ where Ok(()) } - pub fn place_order( - &self, channel_id: u128, counterparty_node_id: &PublicKey, order: Order, + fn place_order( + &self, channel_id: u128, counterparty_node_id: &PublicKey, order: OrderParams, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.write().unwrap(); @@ -548,8 +565,8 @@ where Ok(()) } - pub fn send_invoice_for_order( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: Payment, + fn send_invoice_for_order( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: OrderPayment, created_at: chrono::DateTime, expires_at: chrono::DateTime, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.read().unwrap(); @@ -643,9 +660,11 @@ where } let total_fees = response.payment.fee_total_sat + response.order.client_balance_sat; - let max_fees = self.max_fees.unwrap_or(u64::MAX); + let max_channel_fees_msat = self.max_channel_fees_msat.unwrap_or(u64::MAX); - if total_fees == response.payment.order_total_sat && total_fees < max_fees { + if total_fees == response.payment.order_total_sat + && total_fees < max_channel_fees_msat + { self.enqueue_event(Event::LSPS1(super::event::Event::DisplayOrder { id: channel_id, counterparty_node_id: *counterparty_node_id, @@ -710,7 +729,7 @@ where } } - pub fn check_order_status( + fn check_order_status( &self, counterparty_node_id: &PublicKey, order_id: OrderId, channel_id: u128, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.write().unwrap(); @@ -811,7 +830,7 @@ where Ok(()) } - pub fn update_order_status( + fn update_order_status( &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, order_state: OrderState, channel: Option, ) -> Result<(), APIError> { diff --git a/src/lsps1/mod.rs b/src/lsps1/mod.rs index 00fce16..dc7c14f 100644 --- a/src/lsps1/mod.rs +++ b/src/lsps1/mod.rs @@ -9,8 +9,7 @@ //! Types and primitives that implement the LSPS1: Channel Request specification. -pub(crate) mod event; -pub(crate) mod message_handler; -/// Message, request, and other primitive types used to implement LSPS2. +pub mod event; +pub mod message_handler; pub mod msgs; pub(crate) mod utils; diff --git a/src/lsps1/msgs.rs b/src/lsps1/msgs.rs index 535727e..c01a919 100644 --- a/src/lsps1/msgs.rs +++ b/src/lsps1/msgs.rs @@ -1,3 +1,5 @@ +//! Message, request, and other primitive types used to implement LSPS1. + use crate::lsps0::msgs::{LSPSMessage, RequestId, ResponseError}; use crate::prelude::{String, Vec}; @@ -17,135 +19,233 @@ pub(crate) const LSPS1_CREATE_ORDER_REQUEST_CLIENT_REJECTED_ERROR_CODE: i32 = 10 pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE: i32 = 1; pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_TOKEN_ERROR_CODE: i32 = 2; +/// The identifier of an order. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)] pub struct OrderId(pub String); +/// A request made to an LSP to retrieve the supported options. +/// +/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#1-lsps1info) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] #[serde(default)] pub struct GetInfoRequest {} +/// An object representing the supported protocol options. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct OptionsSupported { + /// The minimum number of block confirmations before the LSP accepts a channel as confirmed. pub minimum_channel_confirmations: u8, + /// The minimum number of block confirmations before the LSP accepts an on-chain payment as confirmed. pub minimum_onchain_payment_confirmations: u8, + /// Indicates if the LSP supports zero reserve. pub supports_zero_channel_reserve: bool, + /// Indicates the minimum amount of satoshi that is required for the LSP to accept a payment + /// on-chain. pub min_onchain_payment_size_sat: Option, + /// The maximum number of blocks a channel can be leased for. pub max_channel_expiry_blocks: u32, + /// The minimum number of satoshi that the client MUST request. pub min_initial_client_balance_sat: u64, + /// The maximum number of satoshi that the client MUST request. pub max_initial_client_balance_sat: u64, + /// The minimum number of satoshi that the LSP will provide to the channel. pub min_initial_lsp_balance_sat: u64, + /// The maximum number of satoshi that the LSP will provide to the channel. pub max_initial_lsp_balance_sat: u64, + /// The minimal channel size. pub min_channel_balance_sat: u64, + /// The maximal channel size. pub max_channel_balance_sat: u64, } +/// A response to an [`GetInfoRequest`]. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct GetInfoResponse { + /// A list of all supported API versions by the LSP. pub supported_versions: Vec, + /// The website of the LSP. pub website: String, + /// All options supported by the LSP. pub options: OptionsSupported, } +/// A request made to an LSP to create an order. +/// +/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#2-lsps1create_order) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CreateOrderRequest { + /// TODO: this is superfluous and should likely be removed. pub version: u16, - pub order: Order, + /// The order made. + pub order: OrderParams, } +/// An object representing an LSPS1 channel order. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Order { +pub struct OrderParams { + /// The API version that the client wants to work with. pub api_version: u16, + /// Indicates how many satoshi the LSP will provide on their side. pub lsp_balance_sat: u64, + /// Indicates how many satoshi the client will provide on their side. + /// + /// The client sends these funds to the LSP, who will push them back to the client upon opening + /// the channel. pub client_balance_sat: u64, + /// The number of blocks the client wants to wait maximally for the channel to be confirmed. pub confirms_within_blocks: u32, + /// Indicates how long the channel is leased for in block time. pub channel_expiry_blocks: u32, + /// May contain arbitrary associated data like a coupon code or a authentication token. pub token: String, - pub announce_channel: bool, + /// The address where the LSP will send the funds if the order fails. pub refund_onchain_address: Option, + /// Indicates if the channel should be announced to the network. + pub announce_channel: bool, } +/// A response to a [`CreateOrderRequest`]. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CreateOrderResponse { + /// The id of the channel order. pub order_id: OrderId, - pub order: Order, - pub order_state: OrderState, + /// The parameters of channel order. + pub order: OrderParams, + /// The datetime when the order was created pub created_at: chrono::DateTime, + /// The datetime when the order expires. pub expires_at: chrono::DateTime, - pub payment: Payment, + /// The current state of the order. + pub order_state: OrderState, + /// Contains details about how to pay for the order. + pub payment: OrderPayment, + /// Contains information about the channel state. pub channel: Option, } +/// An object representing the state of an order. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum OrderState { + /// TODO: this is superfluous and should likely be removed. Requested, + /// The order has been created. Created, + /// The LSP has opened the channel and published the funding transaction. Completed, + /// The order failed. Failed, } +/// Details regarding how to pay for an order. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Payment { +pub struct OrderPayment { + /// Indicates the current state of the payment. pub state: PaymentState, + /// The total fee the LSP will charge to open this channel in satoshi. pub fee_total_sat: u64, + /// What the client needs to pay in total to open the requested channel. pub order_total_sat: u64, - pub onchain_address: String, + /// A BOLT11 invoice the client can pay to have to channel opened. pub bolt11_invoice: String, + /// An on-chain address the client can send [`Self::order_total_sat`] to to have the channel + /// opened. + pub onchain_address: String, + /// The minimum number of block confirmations that are required for the on-chain payment to be + /// considered confirmed. pub onchain_block_confirmations_required: u8, + /// The minimum fee rate for the on-chain payment in case the client wants the payment to be + /// confirmed without a confirmation. pub minimum_fee_for_0conf: u8, + /// Details regarding a detected on-chain payment. pub onchain_payment: OnchainPayment, } +/// The state of an [`OrderPayment`]. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum PaymentState { + /// A payment is expected. ExpectPayment, + /// A Lighting payment has arrived, but the preimage has not been released yet. Hold, + /// A sufficient payment has been received. Paid, + /// The payment has been refunded. Refunded, } +/// Details regarding a detected on-chain payment. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct OnchainPayment { + /// The outpoint of the payment. pub outpoint: String, + /// The amount of satoshi paid. pub sat: u64, + /// Indicates if the LSP regards the transaction as sufficiently confirmed. pub confirmed: bool, } +/// Details regarding the state of an ordered channel. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct ChannelInfo { - pub state: ChannelStatus, + /// The current state of the channel. + pub state: ChannelState, + /// The datetime when the funding transaction has been published. pub funded_at: String, + /// The outpoint of the funding transaction. pub funding_outpoint: String, + /// The channel's short channel id. pub scid: Option, + /// The earliest datetime when the channel may be closed by the LSP. pub expires_at: String, + /// The transaction id of the channel. pub closing_transaction: Option, + /// The datetime when the closing transaction was published. pub closed_at: Option, } +/// The current state of an ordered channel. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub enum ChannelStatus { +pub enum ChannelState { + /// The funding transaction has been published. Opening, + /// The channel has been opened. Opened, + /// The channel has been closed. Closed, } +/// A request made to an LSP to retrieve information about an previously made order. +/// +/// Please refer to the [LSPS1 specification](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1#21-lsps1get_order) +/// for more information. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct GetOrderRequest { + /// The id of the order. pub order_id: OrderId, } +/// A response to an [`GetOrderRequest`]. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct GetOrderResponse { + /// The response to the order request. pub response: CreateOrderResponse, } +/// An enum that captures all the valid JSON-RPC requests in the LSPS1 protocol. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS1Request { + /// A request to learn about the options supported by the LSP. GetInfo(GetInfoRequest), + /// A request to create a channel order. CreateOrder(CreateOrderRequest), + /// A request to query a previously created channel order. GetOrder(GetOrderRequest), } impl LSPS1Request { + /// Get the JSON-RPC method name for the underlying request. pub fn method(&self) -> &str { match self { LSPS1Request::GetInfo(_) => LSPS1_GET_INFO_METHOD_NAME, @@ -155,18 +255,27 @@ impl LSPS1Request { } } +/// An enum that captures all the valid JSON-RPC responses in the LSPS1 protocol. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS1Response { + /// A successful response to a [`GetInfoRequest`]. GetInfo(GetInfoResponse), + /// A successful response to a [`CreateOrderRequest`]. CreateOrder(CreateOrderResponse), + /// An error response to a [`CreateOrderRequest`]. CreateOrderError(ResponseError), + /// A successful response to a [`GetOrderRequest`]. GetOrder(GetOrderResponse), + /// An error response to a [`GetOrderRequest`]. GetOrderError(ResponseError), } +/// An enum that captures all valid JSON-RPC messages in the LSPS1 protocol. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LSPS1Message { + /// An LSPS1 JSON-RPC request. Request(RequestId, LSPS1Request), + /// An LSPS1 JSON-RPC response. Response(RequestId, LSPS1Response), } diff --git a/src/lsps1/utils.rs b/src/lsps1/utils.rs index 298b6d4..7a67521 100644 --- a/src/lsps1/utils.rs +++ b/src/lsps1/utils.rs @@ -1,10 +1,10 @@ -use super::msgs::{OptionsSupported, Order}; +use super::msgs::{OptionsSupported, OrderParams}; pub fn check_range(min: u64, max: u64, value: u64) -> bool { (value >= min) && (value <= max) } -pub fn is_valid(order: &Order, options: &OptionsSupported) -> bool { +pub fn is_valid(order: &OrderParams, options: &OptionsSupported) -> bool { let bool = check_range( options.min_initial_client_balance_sat, options.max_initial_client_balance_sat, diff --git a/src/manager.rs b/src/manager.rs index 78be13f..f11e257 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -4,10 +4,7 @@ use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; #[cfg(lsps1)] -use { - crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}, - crate::lsps1::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment}, -}; +use crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}; use crate::lsps2::message_handler::{LSPS2Config, LSPS2MessageHandler}; use crate::prelude::{HashMap, String, Vec}; From 6155dd4a1cb32ebd200831a57ddc5feab1200cfd Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 11 Dec 2023 15:01:13 +0100 Subject: [PATCH 12/18] Also enforce documentation for LSPS1 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e160e3..f93a776 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,8 @@ jobs: run: | cargo doc --release cargo doc --no-default-features --features no-std + RUSTFLAGS="--cfg lsps1" cargo doc --release + RUSTFLAGS="--cfg lsps1" cargo doc --no-default-features --features no-std - name: Build on Rust ${{ matrix.toolchain }} run: cargo build --verbose --color always - name: Check formatting From c24948ef889bc41a7ea1a29876289eb5cf13fa23 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 14 Dec 2023 11:31:55 +0100 Subject: [PATCH 13/18] Split LSPS2 in client/server handler modules --- src/events.rs | 6 +- src/lsps2/client.rs | 682 +++++++++++++++++++ src/lsps2/event.rs | 113 +-- src/lsps2/mod.rs | 3 +- src/lsps2/{message_handler.rs => service.rs} | 642 ++--------------- src/manager.rs | 79 ++- 6 files changed, 853 insertions(+), 672 deletions(-) create mode 100644 src/lsps2/client.rs rename src/lsps2/{message_handler.rs => service.rs} (55%) diff --git a/src/events.rs b/src/events.rs index ac52f05..6c8a943 100644 --- a/src/events.rs +++ b/src/events.rs @@ -78,8 +78,10 @@ impl EventQueue { /// An event which you should probably take some action in response to. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { - /// An LSPS2 (JIT Channel) protocol event. - LSPS2(lsps2::event::LSPS2Event), + /// An LSPS2 (JIT Channel) client event. + LSPS2Client(lsps2::event::LSPS2ClientEvent), + /// An LSPS2 (JIT Channel) server event. + LSPS2Service(lsps2::event::LSPS2ServiceEvent), /// An LSPS1 protocol event. #[cfg(lsps1)] LSPS1(lsps1::event::Event), diff --git a/src/lsps2/client.rs b/src/lsps2/client.rs new file mode 100644 index 0000000..281dc49 --- /dev/null +++ b/src/lsps2/client.rs @@ -0,0 +1,682 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Contains the main LSPS2 client object, [`LSPS2ClientHandler`]. + +use crate::events::EventQueue; +use crate::lsps0::message_handler::ProtocolMessageHandler; +use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps2::event::LSPS2ClientEvent; +use crate::prelude::{HashMap, String, ToString, Vec}; +use crate::sync::{Arc, Mutex, RwLock}; +use crate::utils; +use crate::{events::Event, lsps0::msgs::ResponseError}; + +use lightning::ln::msgs::{ErrorAction, LightningError}; +use lightning::ln::peer_handler::APeerManager; +use lightning::sign::EntropySource; +use lightning::util::errors::APIError; +use lightning::util::logger::Level; + +use bitcoin::secp256k1::PublicKey; + +use core::ops::Deref; + +use crate::lsps2::msgs::{ + BuyRequest, BuyResponse, GetInfoRequest, GetInfoResponse, GetVersionsRequest, + GetVersionsResponse, JITChannelScid, LSPS2Message, LSPS2Request, LSPS2Response, + OpeningFeeParams, +}; + +/// Client-side configuration options for JIT channels. +#[derive(Clone, Debug, Copy)] +pub struct LSPS2ClientConfig {} + +const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; + +struct ChannelStateError(String); + +impl From for LightningError { + fn from(value: ChannelStateError) -> Self { + LightningError { err: value.0, action: ErrorAction::IgnoreAndLog(Level::Info) } + } +} + +struct InboundJITChannelConfig { + pub user_id: u128, + pub token: Option, + pub payment_size_msat: Option, +} + +#[derive(PartialEq, Debug)] +enum InboundJITChannelState { + VersionsRequested, + MenuRequested { version: u16 }, + PendingMenuSelection { version: u16 }, + BuyRequested { version: u16 }, + PendingPayment { client_trusts_lsp: bool, short_channel_id: JITChannelScid }, +} + +impl InboundJITChannelState { + fn versions_received(&self, versions: Vec) -> Result { + let max_shared_version = versions + .iter() + .filter(|version| SUPPORTED_SPEC_VERSIONS.contains(version)) + .max() + .cloned() + .ok_or(ChannelStateError(format!( + "LSP does not support any of our specification versions. ours = {:?}. theirs = {:?}", + SUPPORTED_SPEC_VERSIONS, versions + )))?; + + match self { + InboundJITChannelState::VersionsRequested => { + Ok(InboundJITChannelState::MenuRequested { version: max_shared_version }) + } + state => Err(ChannelStateError(format!( + "Received unexpected get_versions response. JIT Channel was in state: {:?}", + state + ))), + } + } + + fn info_received(&self) -> Result { + match self { + InboundJITChannelState::MenuRequested { version } => { + Ok(InboundJITChannelState::PendingMenuSelection { version: *version }) + } + state => Err(ChannelStateError(format!( + "Received unexpected get_info response. JIT Channel was in state: {:?}", + state + ))), + } + } + + fn opening_fee_params_selected(&self) -> Result { + match self { + InboundJITChannelState::PendingMenuSelection { version } => { + Ok(InboundJITChannelState::BuyRequested { version: *version }) + } + state => Err(ChannelStateError(format!( + "Opening fee params selected when JIT Channel was in state: {:?}", + state + ))), + } + } + + fn invoice_params_received( + &self, client_trusts_lsp: bool, short_channel_id: JITChannelScid, + ) -> Result { + match self { + InboundJITChannelState::BuyRequested { .. } => { + Ok(InboundJITChannelState::PendingPayment { client_trusts_lsp, short_channel_id }) + } + state => Err(ChannelStateError(format!( + "Invoice params received when JIT Channel was in state: {:?}", + state + ))), + } + } +} + +struct InboundJITChannel { + id: u128, + state: InboundJITChannelState, + config: InboundJITChannelConfig, +} + +impl InboundJITChannel { + fn new(id: u128, user_id: u128, payment_size_msat: Option, token: Option) -> Self { + Self { + id, + config: InboundJITChannelConfig { user_id, payment_size_msat, token }, + state: InboundJITChannelState::VersionsRequested, + } + } + + fn versions_received(&mut self, versions: Vec) -> Result { + self.state = self.state.versions_received(versions)?; + + match self.state { + InboundJITChannelState::MenuRequested { version } => Ok(version), + _ => Err(LightningError { + action: ErrorAction::IgnoreAndLog(Level::Error), + err: "impossible state transition".to_string(), + }), + } + } + + fn info_received(&mut self) -> Result<(), LightningError> { + self.state = self.state.info_received()?; + Ok(()) + } + + fn opening_fee_params_selected(&mut self) -> Result { + self.state = self.state.opening_fee_params_selected()?; + + match self.state { + InboundJITChannelState::BuyRequested { version } => Ok(version), + _ => Err(LightningError { + action: ErrorAction::IgnoreAndLog(Level::Error), + err: "impossible state transition".to_string(), + }), + } + } + + fn invoice_params_received( + &mut self, client_trusts_lsp: bool, jit_channel_scid: JITChannelScid, + ) -> Result<(), LightningError> { + self.state = self.state.invoice_params_received(client_trusts_lsp, jit_channel_scid)?; + Ok(()) + } +} + +struct PeerState { + inbound_channels_by_id: HashMap, + request_to_cid: HashMap, +} + +impl PeerState { + fn new() -> Self { + let inbound_channels_by_id = HashMap::new(); + let request_to_cid = HashMap::new(); + Self { inbound_channels_by_id, request_to_cid } + } + + fn insert_inbound_channel(&mut self, jit_channel_id: u128, channel: InboundJITChannel) { + self.inbound_channels_by_id.insert(jit_channel_id, channel); + } + + fn insert_request(&mut self, request_id: RequestId, jit_channel_id: u128) { + self.request_to_cid.insert(request_id, jit_channel_id); + } + + fn remove_inbound_channel(&mut self, jit_channel_id: u128) { + self.inbound_channels_by_id.remove(&jit_channel_id); + } +} + +/// The main object allowing to send and receive LSPS2 messages. +pub struct LSPS2ClientHandler +where + ES::Target: EntropySource, + PM::Target: APeerManager, +{ + entropy_source: ES, + peer_manager: Mutex>, + pending_messages: Arc>>, + pending_events: Arc, + per_peer_state: RwLock>>, + _config: LSPS2ClientConfig, +} + +impl LSPS2ClientHandler +where + ES::Target: EntropySource, + PM::Target: APeerManager, +{ + /// Constructs an `LSPS2ClientHandler`. + pub(crate) fn new( + entropy_source: ES, pending_messages: Arc>>, + pending_events: Arc, config: LSPS2ClientConfig, + ) -> Self { + Self { + entropy_source, + pending_messages, + pending_events, + per_peer_state: RwLock::new(HashMap::new()), + peer_manager: Mutex::new(None), + _config: config, + } + } + + /// Set a [`PeerManager`] reference for the message handler. + /// + /// This allows the message handler to wake the [`PeerManager`] by calling + /// [`PeerManager::process_events`] after enqueing messages to be sent. + /// + /// Without this the messages will be sent based on whatever polling interval + /// your background processor uses. + /// + /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager + /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events + pub fn set_peer_manager(&self, peer_manager: PM) { + *self.peer_manager.lock().unwrap() = Some(peer_manager); + } + + /// Initiate the creation of an invoice that when paid will open a channel + /// with enough inbound liquidity to be able to receive the payment. + /// + /// `counterparty_node_id` is the node_id of the LSP you would like to use. + /// + /// If `payment_size_msat` is [`Option::Some`] then the invoice will be for a fixed amount + /// and MPP can be used to pay it. + /// + /// If `payment_size_msat` is [`Option::None`] then the invoice can be for an arbitrary amount + /// but MPP can no longer be used to pay it. + /// + /// `token` is an optional String that will be provided to the LSP. + /// It can be used by the LSP as an API key, coupon code, or some other way to identify a user. + pub fn create_invoice( + &self, counterparty_node_id: PublicKey, payment_size_msat: Option, + token: Option, user_channel_id: u128, + ) { + let jit_channel_id = self.generate_jit_channel_id(); + let channel = + InboundJITChannel::new(jit_channel_id, user_channel_id, payment_size_msat, token); + + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = + outer_state_lock.entry(counterparty_node_id).or_insert(Mutex::new(PeerState::new())); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + peer_state_lock.insert_inbound_channel(jit_channel_id, channel); + + let request_id = self.generate_request_id(); + peer_state_lock.insert_request(request_id.clone(), jit_channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + counterparty_node_id, + LSPS2Message::Request(request_id, LSPS2Request::GetVersions(GetVersionsRequest {})) + .into(), + )); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.as_ref().process_events(); + } + } + + /// Used by client to confirm which channel parameters to use for the JIT Channel buy request. + /// The client agrees to paying an opening fee equal to + /// `max(min_fee_msat, proportional*(payment_size_msat/1_000_000))`. + /// + /// Should be called in response to receiving a [`LSPS2ClientEvent::GetInfoResponse`] event. + /// + /// [`LSPS2ClientEvent::GetInfoResponse`]: crate::lsps2::event::LSPS2ClientEvent::GetInfoResponse + pub fn opening_fee_params_selected( + &self, counterparty_node_id: PublicKey, jit_channel_id: u128, + opening_fee_params: OpeningFeeParams, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + if let Some(jit_channel) = + peer_state.inbound_channels_by_id.get_mut(&jit_channel_id) + { + let version = match jit_channel.opening_fee_params_selected() { + Ok(version) => version, + Err(e) => { + peer_state.remove_inbound_channel(jit_channel_id); + return Err(APIError::APIMisuseError { err: e.err }); + } + }; + + let request_id = self.generate_request_id(); + let payment_size_msat = jit_channel.config.payment_size_msat; + peer_state.insert_request(request_id.clone(), jit_channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + counterparty_node_id, + LSPS2Message::Request( + request_id, + LSPS2Request::Buy(BuyRequest { + version, + opening_fee_params, + payment_size_msat, + }), + ) + .into(), + )); + } + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.as_ref().process_events(); + } + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not found", jit_channel_id), + }); + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }) + } + } + + Ok(()) + } + + fn generate_jit_channel_id(&self) -> u128 { + let bytes = self.entropy_source.get_secure_random_bytes(); + let mut id_bytes: [u8; 16] = [0; 16]; + id_bytes.copy_from_slice(&bytes[0..16]); + u128::from_be_bytes(id_bytes) + } + + fn generate_request_id(&self) -> RequestId { + let bytes = self.entropy_source.get_secure_random_bytes(); + RequestId(utils::hex_str(&bytes[0..16])) + } + + fn enqueue_event(&self, event: Event) { + self.pending_events.enqueue(event); + } + + fn handle_get_versions_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetVersionsResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let jit_channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_versions response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let jit_channel = peer_state + .inbound_channels_by_id + .get_mut(&jit_channel_id) + .ok_or(LightningError { + err: format!( + "Received get_versions response for an unknown channel: {:?}", + jit_channel_id, + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let token = jit_channel.config.token.clone(); + + let version = match jit_channel.versions_received(result.versions) { + Ok(version) => version, + Err(e) => { + peer_state.remove_inbound_channel(jit_channel_id); + return Err(e); + } + }; + + let request_id = self.generate_request_id(); + peer_state.insert_request(request_id.clone(), jit_channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + *counterparty_node_id, + LSPS2Message::Request( + request_id, + LSPS2Request::GetInfo(GetInfoRequest { version, token }), + ) + .into(), + )); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.as_ref().process_events(); + } + } + None => { + return Err(LightningError { + err: format!( + "Received get_versions response from unknown peer: {:?}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } + } + + Ok(()) + } + + fn handle_get_info_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetInfoResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let jit_channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_info response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let jit_channel = peer_state + .inbound_channels_by_id + .get_mut(&jit_channel_id) + .ok_or(LightningError { + err: format!( + "Received get_info response for an unknown channel: {:?}", + jit_channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + if let Err(e) = jit_channel.info_received() { + peer_state.remove_inbound_channel(jit_channel_id); + return Err(e); + } + + self.enqueue_event(Event::LSPS2Client(LSPS2ClientEvent::GetInfoResponse { + counterparty_node_id: *counterparty_node_id, + opening_fee_params_menu: result.opening_fee_params_menu, + min_payment_size_msat: result.min_payment_size_msat, + max_payment_size_msat: result.max_payment_size_msat, + jit_channel_id, + user_channel_id: jit_channel.config.user_id, + })); + } + None => { + return Err(LightningError { + err: format!( + "Received get_info response from unknown peer: {:?}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } + } + + Ok(()) + } + + fn handle_get_info_error( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, _error: ResponseError, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let jit_channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_info error for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + peer_state.inbound_channels_by_id.remove(&jit_channel_id).ok_or( + LightningError { + err: format!( + "Received get_info error for an unknown channel: {:?}", + jit_channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }, + )?; + Ok(()) + } + None => { + return Err(LightningError { err: format!("Received error response for a get_info request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + } + } + + fn handle_buy_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: BuyResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let jit_channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received buy response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let jit_channel = peer_state + .inbound_channels_by_id + .get_mut(&jit_channel_id) + .ok_or(LightningError { + err: format!( + "Received buy response for an unknown channel: {:?}", + jit_channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + if let Err(e) = jit_channel.invoice_params_received( + result.client_trusts_lsp, + result.jit_channel_scid.clone(), + ) { + peer_state.remove_inbound_channel(jit_channel_id); + return Err(e); + } + + if let Ok(scid) = result.jit_channel_scid.to_scid() { + self.enqueue_event(Event::LSPS2Client( + LSPS2ClientEvent::InvoiceGenerationReady { + counterparty_node_id: *counterparty_node_id, + scid, + cltv_expiry_delta: result.lsp_cltv_expiry_delta, + payment_size_msat: jit_channel.config.payment_size_msat, + client_trusts_lsp: result.client_trusts_lsp, + user_channel_id: jit_channel.config.user_id, + }, + )); + } else { + return Err(LightningError { + err: format!( + "Received buy response with an invalid scid {:?}", + result.jit_channel_scid + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + } + None => { + return Err(LightningError { + err: format!( + "Received buy response from unknown peer: {:?}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + } + Ok(()) + } + + fn handle_buy_error( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, _error: ResponseError, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let jit_channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!("Received buy error for an unknown request: {:?}", request_id), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let _jit_channel = peer_state + .inbound_channels_by_id + .remove(&jit_channel_id) + .ok_or(LightningError { + err: format!( + "Received buy error for an unknown channel: {:?}", + jit_channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + Ok(()) + } + None => { + return Err(LightningError { err: format!("Received error response for a buy request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + } + } +} + +impl ProtocolMessageHandler for LSPS2ClientHandler +where + ES::Target: EntropySource, + PM::Target: APeerManager, +{ + type ProtocolMessage = LSPS2Message; + const PROTOCOL_NUMBER: Option = Some(2); + + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + match message { + LSPS2Message::Response(request_id, response) => match response { + LSPS2Response::GetVersions(result) => { + self.handle_get_versions_response(request_id, counterparty_node_id, result) + } + LSPS2Response::GetInfo(result) => { + self.handle_get_info_response(request_id, counterparty_node_id, result) + } + LSPS2Response::GetInfoError(error) => { + self.handle_get_info_error(request_id, counterparty_node_id, error) + } + LSPS2Response::Buy(result) => { + self.handle_buy_response(request_id, counterparty_node_id, result) + } + LSPS2Response::BuyError(error) => { + self.handle_buy_error(request_id, counterparty_node_id, error) + } + }, + _ => { + debug_assert!( + false, + "Client handler received LSPS2 request message. This should never happen." + ); + Err(LightningError { err: format!("Client handler received LSPS2 request message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } + } + } +} + +#[cfg(test)] +mod tests {} diff --git a/src/lsps2/event.rs b/src/lsps2/event.rs index 75d4363..367dfab 100644 --- a/src/lsps2/event.rs +++ b/src/lsps2/event.rs @@ -15,43 +15,21 @@ use crate::prelude::{String, Vec}; use bitcoin::secp256k1::PublicKey; -/// An event which you should probably take some action in response to. +/// An event which an LSPS2 client should take some action in response to. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum LSPS2Event { - /// A request from a client for information about JIT Channel parameters. - /// - /// You must calculate the parameters for this client and pass them to - /// [`LSPS2MessageHandler::opening_fee_params_generated`]. - /// - /// If an unrecognized or stale token is provided you can use - /// `[LSPS2MessageHandler::invalid_token_provided`] to error the request. - /// - /// [`LSPS2MessageHandler::opening_fee_params_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_generated - /// [`LSPS2MessageHandler::invalid_token_provided`]: crate::lsps2::message_handler::LSPS2MessageHandler::invalid_token_provided - GetInfo { - /// An identifier that must be passed to [`LSPS2MessageHandler::opening_fee_params_generated`]. - /// - /// [`LSPS2MessageHandler::opening_fee_params_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_generated - request_id: RequestId, - /// The node id of the client making the information request. - counterparty_node_id: PublicKey, - /// The protocol version they would like to use. - version: u16, - /// An optional token that can be used as an API key, coupon code, etc. - token: Option, - }, +pub enum LSPS2ClientEvent { /// Information from the LSP about their current fee rates and channel parameters. /// - /// You must call [`LSPS2MessageHandler::opening_fee_params_selected`] with the fee parameter + /// You must call [`LSPS2ClientHandler::opening_fee_params_selected`] with the fee parameter /// you want to use if you wish to proceed opening a channel. /// - /// [`LSPS2MessageHandler::opening_fee_params_selected`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_selected + /// [`LSPS2ClientHandler::opening_fee_params_selected`]: crate::lsps2::client::LSPS2ClientHandler::opening_fee_params_selected GetInfoResponse { /// This is a randomly generated identifier used to track the JIT channel state. /// It is not related in anyway to the eventual lightning channel id. - /// It needs to be passed to [`LSPS2MessageHandler::opening_fee_params_selected`]. + /// It needs to be passed to [`LSPS2ClientHandler::opening_fee_params_selected`]. /// - /// [`LSPS2MessageHandler::opening_fee_params_selected`]: crate::lsps2::message_handler::LSPS2MessageHandler::opening_fee_params_selected + /// [`LSPS2ClientHandler::opening_fee_params_selected`]: crate::lsps2::client::LSPS2ClientHandler::opening_fee_params_selected jit_channel_id: u128, /// The node id of the LSP that provided this response. counterparty_node_id: PublicKey, @@ -62,11 +40,58 @@ pub enum LSPS2Event { min_payment_size_msat: u64, /// The max payment size allowed when opening the channel. max_payment_size_msat: u64, - /// The user_channel_id value passed in to [`LSPS2MessageHandler::create_invoice`]. + /// The user_channel_id value passed in to [`LSPS2ClientHandler::create_invoice`]. + /// + /// [`LSPS2ClientHandler::create_invoice`]: crate::lsps2::client::LSPS2ClientHandler::create_invoice + user_channel_id: u128, + }, + /// Use the provided fields to generate an invoice and give to payer. + /// + /// When the invoice is paid the LSP will open a channel to you + /// with the previously agreed upon parameters. + InvoiceGenerationReady { + /// The node id of the LSP. + counterparty_node_id: PublicKey, + /// The short channel id to use in the route hint. + scid: u64, + /// The `cltv_expiry_delta` to use in the route hint. + cltv_expiry_delta: u32, + /// The initial payment size you specified. + payment_size_msat: Option, + /// The trust model the LSP expects. + client_trusts_lsp: bool, + /// The `user_channel_id` value passed in to [`LSPS2ClientHandler::create_invoice`]. /// - /// [`LSPS2MessageHandler::create_invoice`]: crate::lsps2::message_handler::LSPS2MessageHandler::create_invoice + /// [`LSPS2ClientHandler::create_invoice`]: crate::lsps2::client::LSPS2ClientHandler::create_invoice user_channel_id: u128, }, +} + +/// An event which an LSPS2 server should take some action in response to. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS2ServiceEvent { + /// A request from a client for information about JIT Channel parameters. + /// + /// You must calculate the parameters for this client and pass them to + /// [`LSPS2ServiceHandler::opening_fee_params_generated`]. + /// + /// If an unrecognized or stale token is provided you can use + /// `[LSPS2ServiceHandler::invalid_token_provided`] to error the request. + /// + /// [`LSPS2ServiceHandler::opening_fee_params_generated`]: crate::lsps2::service::LSPS2ServiceHandler::opening_fee_params_generated + /// [`LSPS2ServiceHandler::invalid_token_provided`]: crate::lsps2::service::LSPS2ServiceHandler::invalid_token_provided + GetInfo { + /// An identifier that must be passed to [`LSPS2ServiceHandler::opening_fee_params_generated`]. + /// + /// [`LSPS2ServiceHandler::opening_fee_params_generated`]: crate::lsps2::service::LSPS2ServiceHandler::opening_fee_params_generated + request_id: RequestId, + /// The node id of the client making the information request. + counterparty_node_id: PublicKey, + /// The protocol version they would like to use. + version: u16, + /// An optional token that can be used as an API key, coupon code, etc. + token: Option, + }, /// A client has selected a opening fee parameter to use and would like to /// purchase a channel with an optional initial payment size. /// @@ -74,13 +99,13 @@ pub enum LSPS2Event { /// If `payment_size_msat` is [`Option::None`] then the payer cannot use MPP. /// /// You must generate an scid and `cltv_expiry_delta` for them to use - /// and call [`LSPS2MessageHandler::invoice_parameters_generated`]. + /// and call [`LSPS2ServiceHandler::invoice_parameters_generated`]. /// - /// [`LSPS2MessageHandler::invoice_parameters_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::invoice_parameters_generated + /// [`LSPS2ServiceHandler::invoice_parameters_generated`]: crate::lsps2::service::LSPS2ServiceHandler::invoice_parameters_generated BuyRequest { - /// An identifier that must be passed into [`LSPS2MessageHandler::invoice_parameters_generated`]. + /// An identifier that must be passed into [`LSPS2ServiceHandler::invoice_parameters_generated`]. /// - /// [`LSPS2MessageHandler::invoice_parameters_generated`]: crate::lsps2::message_handler::LSPS2MessageHandler::invoice_parameters_generated + /// [`LSPS2ServiceHandler::invoice_parameters_generated`]: crate::lsps2::service::LSPS2ServiceHandler::invoice_parameters_generated request_id: RequestId, /// The client node id that is making this request. counterparty_node_id: PublicKey, @@ -91,26 +116,6 @@ pub enum LSPS2Event { /// The size of the initial payment they would like to receive. payment_size_msat: Option, }, - /// Use the provided fields to generate an invoice and give to payer. - /// - /// When the invoice is paid the LSP will open a channel to you - /// with the previously agreed upon parameters. - InvoiceGenerationReady { - /// The node id of the LSP. - counterparty_node_id: PublicKey, - /// The short channel id to use in the route hint. - scid: u64, - /// The `cltv_expiry_delta` to use in the route hint. - cltv_expiry_delta: u32, - /// The initial payment size you specified. - payment_size_msat: Option, - /// The trust model the LSP expects. - client_trusts_lsp: bool, - /// The `user_channel_id` value passed in to [`LSPS2MessageHandler::create_invoice`]. - /// - /// [`LSPS2MessageHandler::create_invoice`]: crate::lsps2::message_handler::LSPS2MessageHandler::create_invoice - user_channel_id: u128, - }, /// You should open a channel using [`ChannelManager::create_channel`]. /// /// [`ChannelManager::create_channel`]: lightning::ln::channelmanager::ChannelManager::create_channel diff --git a/src/lsps2/mod.rs b/src/lsps2/mod.rs index 3a6cdc7..1ba8173 100644 --- a/src/lsps2/mod.rs +++ b/src/lsps2/mod.rs @@ -9,7 +9,8 @@ //! Implementation of LSPS2: JIT Channel Negotiation specification. +pub mod client; pub mod event; -pub mod message_handler; pub mod msgs; +pub mod service; pub(crate) mod utils; diff --git a/src/lsps2/message_handler.rs b/src/lsps2/service.rs similarity index 55% rename from src/lsps2/message_handler.rs rename to src/lsps2/service.rs index 53803e1..9c0e054 100644 --- a/src/lsps2/message_handler.rs +++ b/src/lsps2/service.rs @@ -7,23 +7,21 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Contains the main LSPS2 object, `LSPS2MessageHandler`. +//! Contains the main LSPS2 server-side object, [`LSPS2ServiceHandler`]. use crate::events::EventQueue; use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RequestId}; -use crate::lsps2::event::LSPS2Event; +use crate::lsps2::event::LSPS2ServiceEvent; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; -use crate::utils; use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::ln::channelmanager::{AChannelManager, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; use lightning::ln::peer_handler::APeerManager; use lightning::ln::ChannelId; -use lightning::sign::EntropySource; use lightning::util::errors::APIError; use lightning::util::logger::Level; @@ -33,9 +31,9 @@ use core::convert::TryInto; use core::ops::Deref; use crate::lsps2::msgs::{ - BuyRequest, BuyResponse, GetInfoRequest, GetInfoResponse, GetVersionsRequest, - GetVersionsResponse, JITChannelScid, LSPS2Message, LSPS2Request, LSPS2Response, - OpeningFeeParams, RawOpeningFeeParams, LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE, + BuyRequest, BuyResponse, GetInfoRequest, GetInfoResponse, GetVersionsResponse, LSPS2Message, + LSPS2Request, LSPS2Response, OpeningFeeParams, RawOpeningFeeParams, + LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE, LSPS2_BUY_REQUEST_INVALID_VERSION_ERROR_CODE, LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE, LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE, @@ -43,8 +41,9 @@ use crate::lsps2::msgs::{ LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE, }; -/// Configuration options for JIT channels. -pub struct LSPS2Config { +/// Server-side configuration options for JIT channels. +#[derive(Clone, Debug)] +pub struct LSPS2ServiceConfig { /// Used to calculate the promise for channel parameters supplied to clients. /// /// Note: If this changes then old promises given out will be considered invalid. @@ -71,135 +70,6 @@ impl From for LightningError { } } -struct InboundJITChannelConfig { - pub user_id: u128, - pub token: Option, - pub payment_size_msat: Option, -} - -#[derive(PartialEq, Debug)] -enum InboundJITChannelState { - VersionsRequested, - MenuRequested { version: u16 }, - PendingMenuSelection { version: u16 }, - BuyRequested { version: u16 }, - PendingPayment { client_trusts_lsp: bool, short_channel_id: JITChannelScid }, -} - -impl InboundJITChannelState { - fn versions_received(&self, versions: Vec) -> Result { - let max_shared_version = versions - .iter() - .filter(|version| SUPPORTED_SPEC_VERSIONS.contains(version)) - .max() - .cloned() - .ok_or(ChannelStateError(format!( - "LSP does not support any of our specification versions. ours = {:?}. theirs = {:?}", - SUPPORTED_SPEC_VERSIONS, versions - )))?; - - match self { - InboundJITChannelState::VersionsRequested => { - Ok(InboundJITChannelState::MenuRequested { version: max_shared_version }) - } - state => Err(ChannelStateError(format!( - "Received unexpected get_versions response. JIT Channel was in state: {:?}", - state - ))), - } - } - - fn info_received(&self) -> Result { - match self { - InboundJITChannelState::MenuRequested { version } => { - Ok(InboundJITChannelState::PendingMenuSelection { version: *version }) - } - state => Err(ChannelStateError(format!( - "Received unexpected get_info response. JIT Channel was in state: {:?}", - state - ))), - } - } - - fn opening_fee_params_selected(&self) -> Result { - match self { - InboundJITChannelState::PendingMenuSelection { version } => { - Ok(InboundJITChannelState::BuyRequested { version: *version }) - } - state => Err(ChannelStateError(format!( - "Opening fee params selected when JIT Channel was in state: {:?}", - state - ))), - } - } - - fn invoice_params_received( - &self, client_trusts_lsp: bool, short_channel_id: JITChannelScid, - ) -> Result { - match self { - InboundJITChannelState::BuyRequested { .. } => { - Ok(InboundJITChannelState::PendingPayment { client_trusts_lsp, short_channel_id }) - } - state => Err(ChannelStateError(format!( - "Invoice params received when JIT Channel was in state: {:?}", - state - ))), - } - } -} - -struct InboundJITChannel { - id: u128, - state: InboundJITChannelState, - config: InboundJITChannelConfig, -} - -impl InboundJITChannel { - fn new(id: u128, user_id: u128, payment_size_msat: Option, token: Option) -> Self { - Self { - id, - config: InboundJITChannelConfig { user_id, payment_size_msat, token }, - state: InboundJITChannelState::VersionsRequested, - } - } - - fn versions_received(&mut self, versions: Vec) -> Result { - self.state = self.state.versions_received(versions)?; - - match self.state { - InboundJITChannelState::MenuRequested { version } => Ok(version), - _ => Err(LightningError { - action: ErrorAction::IgnoreAndLog(Level::Error), - err: "impossible state transition".to_string(), - }), - } - } - - fn info_received(&mut self) -> Result<(), LightningError> { - self.state = self.state.info_received()?; - Ok(()) - } - - fn opening_fee_params_selected(&mut self) -> Result { - self.state = self.state.opening_fee_params_selected()?; - - match self.state { - InboundJITChannelState::BuyRequested { version } => Ok(version), - _ => Err(LightningError { - action: ErrorAction::IgnoreAndLog(Level::Error), - err: "impossible state transition".to_string(), - }), - } - } - - fn invoice_params_received( - &mut self, client_trusts_lsp: bool, jit_channel_scid: JITChannelScid, - ) -> Result<(), LightningError> { - self.state = self.state.invoice_params_received(client_trusts_lsp, jit_channel_scid)?; - Ok(()) - } -} - #[derive(PartialEq, Debug)] enum OutboundJITChannelState { AwaitingPayment { @@ -370,84 +240,59 @@ impl OutboundJITChannel { } struct PeerState { - inbound_channels_by_id: HashMap, outbound_channels_by_scid: HashMap, - request_to_cid: HashMap, pending_requests: HashMap, } impl PeerState { fn new() -> Self { - let inbound_channels_by_id = HashMap::new(); let outbound_channels_by_scid = HashMap::new(); - let request_to_cid = HashMap::new(); let pending_requests = HashMap::new(); - Self { inbound_channels_by_id, outbound_channels_by_scid, request_to_cid, pending_requests } - } - - fn insert_inbound_channel(&mut self, jit_channel_id: u128, channel: InboundJITChannel) { - self.inbound_channels_by_id.insert(jit_channel_id, channel); + Self { outbound_channels_by_scid, pending_requests } } fn insert_outbound_channel(&mut self, scid: u64, channel: OutboundJITChannel) { self.outbound_channels_by_scid.insert(scid, channel); } - fn insert_request(&mut self, request_id: RequestId, jit_channel_id: u128) { - self.request_to_cid.insert(request_id, jit_channel_id); - } - - fn remove_inbound_channel(&mut self, jit_channel_id: u128) { - self.inbound_channels_by_id.remove(&jit_channel_id); - } - fn remove_outbound_channel(&mut self, scid: u64) { self.outbound_channels_by_scid.remove(&scid); } } /// The main object allowing to send and receive LSPS2 messages. -pub struct LSPS2MessageHandler +pub struct LSPS2ServiceHandler where - ES::Target: EntropySource, CM::Target: AChannelManager, PM::Target: APeerManager, { - entropy_source: ES, peer_manager: Mutex>, channel_manager: CM, pending_messages: Arc>>, pending_events: Arc, per_peer_state: RwLock>>, peer_by_scid: RwLock>, - promise_secret: [u8; 32], - min_payment_size_msat: u64, - max_payment_size_msat: u64, + config: LSPS2ServiceConfig, } -impl LSPS2MessageHandler +impl LSPS2ServiceHandler where - ES::Target: EntropySource, CM::Target: AChannelManager, PM::Target: APeerManager, { - /// Constructs a `LSPS2MessageHandler`. + /// Constructs a `LSPS2ServiceHandler`. pub(crate) fn new( - entropy_source: ES, config: &LSPS2Config, pending_messages: Arc>>, - pending_events: Arc, channel_manager: CM, + pending_events: Arc, channel_manager: CM, config: LSPS2ServiceConfig, ) -> Self { Self { - entropy_source, - promise_secret: config.promise_secret, - min_payment_size_msat: config.min_payment_size_msat, - max_payment_size_msat: config.max_payment_size_msat, pending_messages, pending_events, per_peer_state: RwLock::new(HashMap::new()), peer_by_scid: RwLock::new(HashMap::new()), peer_manager: Mutex::new(None), channel_manager, + config, } } @@ -465,55 +310,11 @@ where *self.peer_manager.lock().unwrap() = Some(peer_manager); } - /// Initiate the creation of an invoice that when paid will open a channel - /// with enough inbound liquidity to be able to receive the payment. - /// - /// `counterparty_node_id` is the node_id of the LSP you would like to use. - /// - /// If `payment_size_msat` is [`Option::Some`] then the invoice will be for a fixed amount - /// and MPP can be used to pay it. - /// - /// If `payment_size_msat` is [`Option::None`] then the invoice can be for an arbitrary amount - /// but MPP can no longer be used to pay it. - /// - /// `token` is an optional String that will be provided to the LSP. - /// It can be used by the LSP as an API key, coupon code, or some other way to identify a user. - pub fn create_invoice( - &self, counterparty_node_id: PublicKey, payment_size_msat: Option, - token: Option, user_channel_id: u128, - ) { - let jit_channel_id = self.generate_jit_channel_id(); - let channel = - InboundJITChannel::new(jit_channel_id, user_channel_id, payment_size_msat, token); - - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - let inner_state_lock = - outer_state_lock.entry(counterparty_node_id).or_insert(Mutex::new(PeerState::new())); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - peer_state_lock.insert_inbound_channel(jit_channel_id, channel); - - let request_id = self.generate_request_id(); - peer_state_lock.insert_request(request_id.clone(), jit_channel_id); - - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - counterparty_node_id, - LSPS2Message::Request(request_id, LSPS2Request::GetVersions(GetVersionsRequest {})) - .into(), - )); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } - } - /// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid. /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. + /// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event. /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::event::LSPS2Event::GetInfo + /// [`LSPS2ServiceEvent::GetInfo`]: crate::lsps2::event::LSPS2ServiceEvent::GetInfo pub fn invalid_token_provided( &self, counterparty_node_id: PublicKey, request_id: RequestId, ) -> Result<(), APIError> { @@ -549,9 +350,9 @@ where /// Used by LSP to provide fee parameters to a client requesting a JIT Channel. /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfo`] event. + /// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event. /// - /// [`LSPS2Event::GetInfo`]: crate::lsps2::event::LSPS2Event::GetInfo + /// [`LSPS2ServiceEvent::GetInfo`]: crate::lsps2::event::LSPS2ServiceEvent::GetInfo pub fn opening_fee_params_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, opening_fee_params_menu: Vec, @@ -567,10 +368,12 @@ where let response = LSPS2Response::GetInfo(GetInfoResponse { opening_fee_params_menu: opening_fee_params_menu .into_iter() - .map(|param| param.into_opening_fee_params(&self.promise_secret)) + .map(|param| { + param.into_opening_fee_params(&self.config.promise_secret) + }) .collect(), - min_payment_size_msat: self.min_payment_size_msat, - max_payment_size_msat: self.max_payment_size_msat, + min_payment_size_msat: self.config.min_payment_size_msat, + max_payment_size_msat: self.config.max_payment_size_msat, }); self.enqueue_response(counterparty_node_id, request_id, response); Ok(()) @@ -589,75 +392,11 @@ where } } - /// Used by client to confirm which channel parameters to use for the JIT Channel buy request. - /// The client agrees to paying an opening fee equal to - /// `max(min_fee_msat, proportional*(payment_size_msat/1_000_000))`. - /// - /// Should be called in response to receiving a [`LSPS2Event::GetInfoResponse`] event. - /// - /// [`LSPS2Event::GetInfoResponse`]: crate::lsps2::event::LSPS2Event::GetInfoResponse - pub fn opening_fee_params_selected( - &self, counterparty_node_id: PublicKey, jit_channel_id: u128, - opening_fee_params: OpeningFeeParams, - ) -> Result<(), APIError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(&counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - if let Some(jit_channel) = - peer_state.inbound_channels_by_id.get_mut(&jit_channel_id) - { - let version = match jit_channel.opening_fee_params_selected() { - Ok(version) => version, - Err(e) => { - peer_state.remove_inbound_channel(jit_channel_id); - return Err(APIError::APIMisuseError { err: e.err }); - } - }; - - let request_id = self.generate_request_id(); - let payment_size_msat = jit_channel.config.payment_size_msat; - peer_state.insert_request(request_id.clone(), jit_channel_id); - - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - counterparty_node_id, - LSPS2Message::Request( - request_id, - LSPS2Request::Buy(BuyRequest { - version, - opening_fee_params, - payment_size_msat, - }), - ) - .into(), - )); - } - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } - } else { - return Err(APIError::APIMisuseError { - err: format!("Channel with id {} not found", jit_channel_id), - }); - } - } - None => { - return Err(APIError::APIMisuseError { - err: format!("No existing state with counterparty {}", counterparty_node_id), - }) - } - } - - Ok(()) - } - /// Used by LSP to provide client with the scid and cltv_expiry_delta to use in their invoice. /// - /// Should be called in response to receiving a [`LSPS2Event::BuyRequest`] event. + /// Should be called in response to receiving a [`LSPS2ServiceEvent::BuyRequest`] event. /// - /// [`LSPS2Event::BuyRequest`]: crate::lsps2::event::LSPS2Event::BuyRequest + /// [`LSPS2ServiceEvent::BuyRequest`]: crate::lsps2::event::LSPS2ServiceEvent::BuyRequest pub fn invoice_parameters_generated( &self, counterparty_node_id: PublicKey, request_id: RequestId, scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, @@ -713,13 +452,13 @@ where /// Will fail the intercepted HTLC if the scid matches a payment we are expecting /// but the payment amount is incorrect or the expiry has passed. /// - /// Will generate a [`LSPS2Event::OpenChannel`] event if the scid matches a payment we are expected + /// Will generate a [`LSPS2ServiceEvent::OpenChannel`] event if the scid matches a payment we are expected /// and the payment amount is correct and the offer has not expired. /// /// Will do nothing if the scid does not match any of the ones we gave out. /// /// [`Event::HTLCIntercepted`]: lightning::events::Event::HTLCIntercepted - /// [`LSPS2Event::OpenChannel`]: crate::lsps2::event::LSPS2Event::OpenChannel + /// [`LSPS2ServiceEvent::OpenChannel`]: crate::lsps2::event::LSPS2ServiceEvent::OpenChannel pub fn htlc_intercepted( &self, scid: u64, intercept_id: InterceptId, expected_outbound_amount_msat: u64, ) -> Result<(), APIError> { @@ -733,12 +472,14 @@ where let htlc = InterceptedHTLC { intercept_id, expected_outbound_amount_msat }; match jit_channel.htlc_intercepted(htlc) { Ok(Some((opening_fee_msat, amt_to_forward_msat))) => { - self.enqueue_event(Event::LSPS2(LSPS2Event::OpenChannel { - their_network_key: counterparty_node_id.clone(), - amt_to_forward_msat, - opening_fee_msat, - user_channel_id: scid as u128, - })); + self.enqueue_event(Event::LSPS2Service( + LSPS2ServiceEvent::OpenChannel { + their_network_key: counterparty_node_id.clone(), + amt_to_forward_msat, + opening_fee_msat, + user_channel_id: scid as u128, + }, + )); } Ok(None) => {} Err(e) => { @@ -825,18 +566,6 @@ where Ok(()) } - fn generate_jit_channel_id(&self) -> u128 { - let bytes = self.entropy_source.get_secure_random_bytes(); - let mut id_bytes: [u8; 16] = [0; 16]; - id_bytes.copy_from_slice(&bytes[0..16]); - u128::from_be_bytes(id_bytes) - } - - fn generate_request_id(&self) -> RequestId { - let bytes = self.entropy_source.get_secure_random_bytes(); - RequestId(utils::hex_str(&bytes[0..16])) - } - fn enqueue_response( &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS2Response, ) { @@ -868,77 +597,6 @@ where Ok(()) } - fn handle_get_versions_response( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetVersionsResponse, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - - let jit_channel_id = - peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { - err: format!( - "Received get_versions response for an unknown request: {:?}", - request_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - let jit_channel = peer_state - .inbound_channels_by_id - .get_mut(&jit_channel_id) - .ok_or(LightningError { - err: format!( - "Received get_versions response for an unknown channel: {:?}", - jit_channel_id, - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - let token = jit_channel.config.token.clone(); - - let version = match jit_channel.versions_received(result.versions) { - Ok(version) => version, - Err(e) => { - peer_state.remove_inbound_channel(jit_channel_id); - return Err(e); - } - }; - - let request_id = self.generate_request_id(); - peer_state.insert_request(request_id.clone(), jit_channel_id); - - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - *counterparty_node_id, - LSPS2Message::Request( - request_id, - LSPS2Request::GetInfo(GetInfoRequest { version, token }), - ) - .into(), - )); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } - } - None => { - return Err(LightningError { - err: format!( - "Received get_versions response from unknown peer: {:?}", - counterparty_node_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }) - } - } - - Ok(()) - } - fn handle_get_info_request( &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetInfoRequest, ) -> Result<(), LightningError> { @@ -966,7 +624,7 @@ where .pending_requests .insert(request_id.clone(), LSPS2Request::GetInfo(params.clone())); - self.enqueue_event(Event::LSPS2(LSPS2Event::GetInfo { + self.enqueue_event(Event::LSPS2Service(LSPS2ServiceEvent::GetInfo { request_id, counterparty_node_id: *counterparty_node_id, version: params.version, @@ -975,96 +633,6 @@ where Ok(()) } - fn handle_get_info_response( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetInfoResponse, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - - let jit_channel_id = - peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { - err: format!( - "Received get_info response for an unknown request: {:?}", - request_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - let jit_channel = peer_state - .inbound_channels_by_id - .get_mut(&jit_channel_id) - .ok_or(LightningError { - err: format!( - "Received get_info response for an unknown channel: {:?}", - jit_channel_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - if let Err(e) = jit_channel.info_received() { - peer_state.remove_inbound_channel(jit_channel_id); - return Err(e); - } - - self.enqueue_event(Event::LSPS2(LSPS2Event::GetInfoResponse { - counterparty_node_id: *counterparty_node_id, - opening_fee_params_menu: result.opening_fee_params_menu, - min_payment_size_msat: result.min_payment_size_msat, - max_payment_size_msat: result.max_payment_size_msat, - jit_channel_id, - user_channel_id: jit_channel.config.user_id, - })); - } - None => { - return Err(LightningError { - err: format!( - "Received get_info response from unknown peer: {:?}", - counterparty_node_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }) - } - } - - Ok(()) - } - - fn handle_get_info_error( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, _error: ResponseError, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - - let jit_channel_id = - peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { - err: format!( - "Received get_info error for an unknown request: {:?}", - request_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - peer_state.inbound_channels_by_id.remove(&jit_channel_id).ok_or( - LightningError { - err: format!( - "Received get_info error for an unknown channel: {:?}", - jit_channel_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }, - )?; - Ok(()) - } - None => { - return Err(LightningError { err: format!("Received error response for a get_info request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); - } - } - } - fn handle_buy_request( &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: BuyRequest, ) -> Result<(), LightningError> { @@ -1085,7 +653,7 @@ where } if let Some(payment_size_msat) = params.payment_size_msat { - if payment_size_msat < self.min_payment_size_msat { + if payment_size_msat < self.config.min_payment_size_msat { self.enqueue_response( *counterparty_node_id, request_id, @@ -1102,7 +670,7 @@ where }); } - if payment_size_msat > self.max_payment_size_msat { + if payment_size_msat > self.config.max_payment_size_msat { self.enqueue_response( *counterparty_node_id, request_id, @@ -1162,7 +730,7 @@ where // TODO: if payment_size_msat is specified, make sure our node has sufficient incoming liquidity from public network to receive it. - if !is_valid_opening_fee_params(¶ms.opening_fee_params, &self.promise_secret) { + if !is_valid_opening_fee_params(¶ms.opening_fee_params, &self.config.promise_secret) { self.enqueue_response( *counterparty_node_id, request_id, @@ -1186,7 +754,7 @@ where .pending_requests .insert(request_id.clone(), LSPS2Request::Buy(params.clone())); - self.enqueue_event(Event::LSPS2(LSPS2Event::BuyRequest { + self.enqueue_event(Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest { request_id, version: params.version, counterparty_node_id: *counterparty_node_id, @@ -1196,112 +764,10 @@ where Ok(()) } - - fn handle_buy_response( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: BuyResponse, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - - let jit_channel_id = - peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { - err: format!( - "Received buy response for an unknown request: {:?}", - request_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - let jit_channel = peer_state - .inbound_channels_by_id - .get_mut(&jit_channel_id) - .ok_or(LightningError { - err: format!( - "Received buy response for an unknown channel: {:?}", - jit_channel_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - if let Err(e) = jit_channel.invoice_params_received( - result.client_trusts_lsp, - result.jit_channel_scid.clone(), - ) { - peer_state.remove_inbound_channel(jit_channel_id); - return Err(e); - } - - if let Ok(scid) = result.jit_channel_scid.to_scid() { - self.enqueue_event(Event::LSPS2(LSPS2Event::InvoiceGenerationReady { - counterparty_node_id: *counterparty_node_id, - scid, - cltv_expiry_delta: result.lsp_cltv_expiry_delta, - payment_size_msat: jit_channel.config.payment_size_msat, - client_trusts_lsp: result.client_trusts_lsp, - user_channel_id: jit_channel.config.user_id, - })); - } else { - return Err(LightningError { - err: format!( - "Received buy response with an invalid scid {:?}", - result.jit_channel_scid - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - } - None => { - return Err(LightningError { - err: format!( - "Received buy response from unknown peer: {:?}", - counterparty_node_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - } - Ok(()) - } - - fn handle_buy_error( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, _error: ResponseError, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state = inner_state_lock.lock().unwrap(); - - let jit_channel_id = - peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { - err: format!("Received buy error for an unknown request: {:?}", request_id), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - let _jit_channel = peer_state - .inbound_channels_by_id - .remove(&jit_channel_id) - .ok_or(LightningError { - err: format!( - "Received buy error for an unknown channel: {:?}", - jit_channel_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - Ok(()) - } - None => { - return Err(LightningError { err: format!("Received error response for a buy request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); - } - } - } } -impl ProtocolMessageHandler - for LSPS2MessageHandler +impl ProtocolMessageHandler for LSPS2ServiceHandler where - ES::Target: EntropySource, CM::Target: AChannelManager, PM::Target: APeerManager, { @@ -1323,23 +789,13 @@ where self.handle_buy_request(request_id, counterparty_node_id, params) } }, - LSPS2Message::Response(request_id, response) => match response { - LSPS2Response::GetVersions(result) => { - self.handle_get_versions_response(request_id, counterparty_node_id, result) - } - LSPS2Response::GetInfo(result) => { - self.handle_get_info_response(request_id, counterparty_node_id, result) - } - LSPS2Response::GetInfoError(error) => { - self.handle_get_info_error(request_id, counterparty_node_id, error) - } - LSPS2Response::Buy(result) => { - self.handle_buy_response(request_id, counterparty_node_id, result) - } - LSPS2Response::BuyError(error) => { - self.handle_buy_error(request_id, counterparty_node_id, error) - } - }, + _ => { + debug_assert!( + false, + "Service handler received LSPS2 response message. This should never happen." + ); + Err(LightningError { err: format!("Service handler received LSPS2 response message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } } } } diff --git a/src/manager.rs b/src/manager.rs index f11e257..66f58c5 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -6,7 +6,9 @@ use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; #[cfg(lsps1)] use crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}; -use crate::lsps2::message_handler::{LSPS2Config, LSPS2MessageHandler}; +use crate::lsps2::client::{LSPS2ClientConfig, LSPS2ClientHandler}; +use crate::lsps2::msgs::LSPS2Message; +use crate::lsps2::service::{LSPS2ServiceConfig, LSPS2ServiceHandler}; use crate::prelude::{HashMap, String, Vec}; use crate::sync::{Arc, Mutex, RwLock}; @@ -35,9 +37,11 @@ pub struct LiquidityProviderConfig { /// LSPS1 Configuration #[cfg(lsps1)] pub lsps1_config: Option, - /// Optional configuration for JIT channels + /// Optional client-side configuration for JIT channels. + pub lsps2_client_config: Option, + /// Optional server-side configuration for JIT channels /// should you want to support them. - pub lsps2_config: Option, + pub lsps2_service_config: Option, } /// The main interface into LSP functionality. @@ -52,8 +56,8 @@ pub struct LiquidityProviderConfig { /// Users need to continually poll [`LiquidityManager::get_and_clear_pending_events`] in order to surface /// [`Event`]'s that likely need to be handled. /// -/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`LSPS2MessageHandler::htlc_intercepted`] -/// and the [`Event::ChannelReady`] event parameters to [`LSPS2MessageHandler::channel_ready`]. +/// If configured, users must forward the [`Event::HTLCIntercepted`] event parameters to [`LSPS2ServiceHandler::htlc_intercepted`] +/// and the [`Event::ChannelReady`] event parameters to [`LSPS2ServiceHandler::channel_ready`]. /// /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager /// [`MessageHandler`]: lightning::ln::peer_handler::MessageHandler @@ -76,7 +80,8 @@ pub struct LiquidityManager< lsps0_message_handler: LSPS0MessageHandler, #[cfg(lsps1)] lsps1_message_handler: Option>, - lsps2_message_handler: Option>, + lsps2_service_handler: Option>, + lsps2_client_handler: Option>, provider_config: Option, channel_manager: CM, chain_source: Option, @@ -109,14 +114,23 @@ where { Arc::clone(&pending_messages), ); - let lsps2_message_handler = provider_config.as_ref().and_then(|config| { - config.lsps2_config.as_ref().map(|config| { - LSPS2MessageHandler::new( + let lsps2_client_handler = provider_config.as_ref().and_then(|config| { + config.lsps2_client_config.map(|config| { + LSPS2ClientHandler::new( entropy_source.clone(), - config, + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + config.clone(), + ) + }) + }); + let lsps2_service_handler = provider_config.as_ref().and_then(|config| { + config.lsps2_service_config.as_ref().map(|config| { + LSPS2ServiceHandler::new( Arc::clone(&pending_messages), Arc::clone(&pending_events), channel_manager.clone(), + config.clone(), ) }) }); @@ -142,7 +156,8 @@ where { lsps0_message_handler, #[cfg(lsps1)] lsps1_message_handler, - lsps2_message_handler, + lsps2_client_handler, + lsps2_service_handler, provider_config, channel_manager, chain_source, @@ -163,9 +178,14 @@ where { self.lsps1_message_handler.as_ref() } - /// Returns a reference to the LSPS2 message handler. - pub fn lsps2_message_handler(&self) -> Option<&LSPS2MessageHandler> { - self.lsps2_message_handler.as_ref() + /// Returns a reference to the LSPS2 client-side handler. + pub fn lsps2_client_handler(&self) -> Option<&LSPS2ClientHandler> { + self.lsps2_client_handler.as_ref() + } + + /// Returns a reference to the LSPS2 server-side handler. + pub fn lsps2_service_handler(&self) -> Option<&LSPS2ServiceHandler> { + self.lsps2_service_handler.as_ref() } /// Blocks the current thread until next event is ready and returns it. @@ -205,8 +225,11 @@ where { if let Some(lsps1_message_handler) = &self.lsps1_message_handler { lsps1_message_handler.set_peer_manager(peer_manager.clone()); } - if let Some(lsps2_message_handler) = &self.lsps2_message_handler { - lsps2_message_handler.set_peer_manager(peer_manager); + if let Some(lsps2_client_handler) = &self.lsps2_client_handler { + lsps2_client_handler.set_peer_manager(peer_manager.clone()); + } + if let Some(lsps2_service_handler) = &self.lsps2_service_handler { + lsps2_service_handler.set_peer_manager(peer_manager); } } @@ -229,14 +252,26 @@ where { return Err(LightningError { err: format!("Received LSPS1 message without LSPS1 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); } }, - LSPSMessage::LSPS2(msg) => match &self.lsps2_message_handler { - Some(lsps2_message_handler) => { - lsps2_message_handler.handle_message(msg, sender_node_id)?; + LSPSMessage::LSPS2(msg @ LSPS2Message::Response(..)) => { + match &self.lsps2_client_handler { + Some(lsps2_client_handler) => { + lsps2_client_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS2 response message without LSPS2 client handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } } - None => { - return Err(LightningError { err: format!("Received LSPS2 message without LSPS2 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + LSPSMessage::LSPS2(msg @ LSPS2Message::Request(..)) => { + match &self.lsps2_service_handler { + Some(lsps2_service_handler) => { + lsps2_service_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS2 request message without LSPS2 service handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } } - }, + } } Ok(()) } From f45eec712d19e875246c67b6664b63d1e8d20ce4 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 14 Dec 2023 13:45:32 +0100 Subject: [PATCH 14/18] Split LSPS1 in client/server handler modules --- src/events.rs | 9 +- src/lib.rs | 2 +- src/lsps1/{message_handler.rs => client.rs} | 386 ++------------- src/lsps1/event.rs | 33 +- src/lsps1/mod.rs | 3 +- src/lsps1/service.rs | 494 ++++++++++++++++++++ src/manager.rs | 130 ++++-- 7 files changed, 631 insertions(+), 426 deletions(-) rename src/lsps1/{message_handler.rs => client.rs} (64%) create mode 100644 src/lsps1/service.rs diff --git a/src/events.rs b/src/events.rs index 6c8a943..8665e89 100644 --- a/src/events.rs +++ b/src/events.rs @@ -78,11 +78,14 @@ impl EventQueue { /// An event which you should probably take some action in response to. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { + /// An LSPS1 (Channel Request) client event. + #[cfg(lsps1)] + LSPS1Client(lsps1::event::LSPS1ClientEvent), + /// An LSPS1 (Channel Request) server event. + #[cfg(lsps1)] + LSPS1Service(lsps1::event::LSPS1ServiceEvent), /// An LSPS2 (JIT Channel) client event. LSPS2Client(lsps2::event::LSPS2ClientEvent), /// An LSPS2 (JIT Channel) server event. LSPS2Service(lsps2::event::LSPS2ServiceEvent), - /// An LSPS1 protocol event. - #[cfg(lsps1)] - LSPS1(lsps1::event::Event), } diff --git a/src/lib.rs b/src/lib.rs index 9ac32ad..e7092d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,4 +47,4 @@ mod manager; mod sync; mod utils; -pub use manager::{LiquidityManager, LiquidityProviderConfig}; +pub use manager::{LiquidityClientConfig, LiquidityManager, LiquidityServiceConfig}; diff --git a/src/lsps1/message_handler.rs b/src/lsps1/client.rs similarity index 64% rename from src/lsps1/message_handler.rs rename to src/lsps1/client.rs index a1ca7cf..0b6fa7f 100644 --- a/src/lsps1/message_handler.rs +++ b/src/lsps1/client.rs @@ -7,14 +7,13 @@ // You may not use this file except in accordance with one or both of these // licenses. -//! Contains the main LSPS1 object, `LSPS1MessageHandler`. +//! Contains the main LSPS1 client object, [`LSPS1ClientHandler`]. +use super::event::LSPS1ClientEvent; use super::msgs::{ - ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, - GetOrderRequest, GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, - OrderId, OrderParams, OrderPayment, OrderState, - LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, - LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, + CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, GetOrderRequest, + GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, OrderId, + OrderParams, }; use super::utils::is_valid; @@ -36,21 +35,15 @@ use lightning::util::logger::Level; use bitcoin::secp256k1::PublicKey; -use chrono::Utc; use core::ops::Deref; const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; -/// Configuration options for LSPS1 channel requests. -pub struct LSPS1Config { - /// A token to be send with each channel request. - pub token: Option, +/// Client-side configuration options for LSPS1 channel requests. +#[derive(Clone, Debug)] +pub struct LSPS1ClientConfig { /// The maximally allowed channel fees. pub max_channel_fees_msat: Option, - /// The options supported by the LSP. - pub options_supported: Option, - /// The LSP's website. - pub website: Option, } struct ChannelStateError(String); @@ -203,65 +196,9 @@ impl InboundCRChannel { } } -#[derive(PartialEq, Debug)] -enum OutboundRequestState { - OrderCreated { order_id: OrderId }, - WaitingPayment { order_id: OrderId }, - Ready, -} - -impl OutboundRequestState { - fn create_payment_invoice(&self) -> Result { - match self { - OutboundRequestState::OrderCreated { order_id } => { - Ok(OutboundRequestState::WaitingPayment { order_id: order_id.clone() }) - } - state => Err(ChannelStateError(format!( - "Received unexpected get_versions response. JIT Channel was in state: {:?}", - state - ))), - } - } -} - -struct OutboundLSPS1Config { - order: OrderParams, - created_at: chrono::DateTime, - expires_at: chrono::DateTime, - payment: OrderPayment, -} - -struct OutboundCRChannel { - state: OutboundRequestState, - config: OutboundLSPS1Config, -} - -impl OutboundCRChannel { - fn new( - order: OrderParams, created_at: chrono::DateTime, expires_at: chrono::DateTime, - order_id: OrderId, payment: OrderPayment, - ) -> Self { - Self { - state: OutboundRequestState::OrderCreated { order_id }, - config: OutboundLSPS1Config { order, created_at, expires_at, payment }, - } - } - fn create_payment_invoice(&mut self) -> Result<(), LightningError> { - self.state = self.state.create_payment_invoice()?; - Ok(()) - } - - fn check_order_validity(&self, options_supported: &OptionsSupported) -> bool { - let order = &self.config.order; - - is_valid(order, options_supported) - } -} - #[derive(Default)] struct PeerState { inbound_channels_by_id: HashMap, - outbound_channels_by_order_id: HashMap, request_to_cid: HashMap, pending_requests: HashMap, } @@ -271,10 +208,6 @@ impl PeerState { self.inbound_channels_by_id.insert(id, channel); } - fn insert_outbound_channel(&mut self, order_id: OrderId, channel: OutboundCRChannel) { - self.outbound_channels_by_order_id.insert(order_id, channel); - } - fn insert_request(&mut self, request_id: RequestId, channel_id: u128) { self.request_to_cid.insert(request_id, channel_id); } @@ -282,14 +215,10 @@ impl PeerState { fn remove_inbound_channel(&mut self, id: u128) { self.inbound_channels_by_id.remove(&id); } - - fn remove_outbound_channel(&mut self, order_id: OrderId) { - self.outbound_channels_by_order_id.remove(&order_id); - } } /// The main object allowing to send and receive LSPS1 messages. -pub struct LSPS1MessageHandler +pub struct LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -303,12 +232,10 @@ where pending_messages: Arc>>, pending_events: Arc, per_peer_state: RwLock>>, - options_config: Option, - website: Option, - max_channel_fees_msat: Option, + config: LSPS1ClientConfig, } -impl LSPS1MessageHandler +impl LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -317,9 +244,9 @@ where ES::Target: EntropySource, { pub(crate) fn new( - entropy_source: ES, config: &LSPS1Config, - pending_messages: Arc>>, + entropy_source: ES, pending_messages: Arc>>, pending_events: Arc, channel_manager: CM, chain_source: Option, + config: LSPS1ClientConfig, ) -> Self { Self { entropy_source, @@ -329,9 +256,7 @@ where pending_messages, pending_events, per_peer_state: RwLock::new(HashMap::new()), - options_config: config.options_supported.clone(), - website: config.website.clone(), - max_channel_fees_msat: config.max_channel_fees_msat, + config, } } @@ -375,26 +300,6 @@ where } } - fn handle_get_info_request( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, - ) -> Result<(), LightningError> { - let response = GetInfoResponse { - supported_versions: SUPPORTED_SPEC_VERSIONS.to_vec(), - website: self.website.clone().unwrap().to_string(), - options: self - .options_config - .clone() - .ok_or(LightningError { - err: format!("Configuration for LSP server not set."), - action: ErrorAction::IgnoreAndLog(Level::Info), - }) - .unwrap(), - }; - - self.enqueue_response(*counterparty_node_id, request_id, LSPS1Response::GetInfo(response)); - Ok(()) - } - fn handle_get_info_response( &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetInfoResponse, ) -> Result<(), LightningError> { @@ -434,7 +339,7 @@ where } }; - self.enqueue_event(Event::LSPS1(super::event::Event::GetInfoResponse { + self.enqueue_event(Event::LSPS1Client(LSPS1ClientEvent::GetInfoResponse { id: channel_id, request_id, counterparty_node_id: *counterparty_node_id, @@ -507,122 +412,6 @@ where Ok(()) } - fn handle_create_order_request( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: CreateOrderRequest, - ) -> Result<(), LightningError> { - if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { - self.enqueue_response( - *counterparty_node_id, - request_id, - LSPS1Response::CreateOrderError(ResponseError { - code: LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, - message: format!("version {} is not supported", params.version), - data: Some(format!("Supported versions are {:?}", SUPPORTED_SPEC_VERSIONS)), - }), - ); - return Err(LightningError { - err: format!("client requested unsupported version {}", params.version), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - - if !is_valid(¶ms.order, &self.options_config.as_ref().unwrap()) { - self.enqueue_response( - *counterparty_node_id, - request_id, - LSPS1Response::CreateOrderError(ResponseError { - code: LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, - message: format!("Order does not match options supported by LSP server"), - data: Some(format!( - "Supported options are {:?}", - &self.options_config.as_ref().unwrap() - )), - }), - ); - return Err(LightningError { - err: format!("client requested unsupported version {}", params.version), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - - let mut outer_state_lock = self.per_peer_state.write().unwrap(); - - let inner_state_lock = outer_state_lock - .entry(*counterparty_node_id) - .or_insert(Mutex::new(PeerState::default())); - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - peer_state_lock - .pending_requests - .insert(request_id.clone(), LSPS1Request::CreateOrder(params.clone())); - - self.enqueue_event(Event::LSPS1(super::event::Event::CreateInvoice { - request_id, - counterparty_node_id: *counterparty_node_id, - order: params.order, - })); - - Ok(()) - } - - fn send_invoice_for_order( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: OrderPayment, - created_at: chrono::DateTime, expires_at: chrono::DateTime, - ) -> Result<(), APIError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - - match outer_state_lock.get(counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - match peer_state_lock.pending_requests.remove(&request_id) { - Some(LSPS1Request::CreateOrder(params)) => { - let order_id = self.generate_order_id(); - let channel = OutboundCRChannel::new( - params.order.clone(), - created_at.clone(), - expires_at.clone(), - order_id.clone(), - payment.clone(), - ); - - peer_state_lock.insert_outbound_channel(order_id.clone(), channel); - - self.enqueue_response( - *counterparty_node_id, - request_id, - LSPS1Response::CreateOrder(CreateOrderResponse { - order: params.order, - order_id, - order_state: OrderState::Created, - created_at, - expires_at, - payment, - channel: None, - }), - ); - } - - _ => { - return Err(APIError::APIMisuseError { - err: format!("No pending buy request for request_id: {:?}", request_id), - }) - } - } - } - None => { - return Err(APIError::APIMisuseError { - err: format!( - "No state for the counterparty exists: {:?}", - counterparty_node_id - ), - }) - } - } - - Ok(()) - } - fn handle_create_order_response( &self, request_id: RequestId, counterparty_node_id: &PublicKey, response: CreateOrderResponse, @@ -660,12 +449,12 @@ where } let total_fees = response.payment.fee_total_sat + response.order.client_balance_sat; - let max_channel_fees_msat = self.max_channel_fees_msat.unwrap_or(u64::MAX); + let max_channel_fees_msat = self.config.max_channel_fees_msat.unwrap_or(u64::MAX); if total_fees == response.payment.order_total_sat && total_fees < max_channel_fees_msat { - self.enqueue_event(Event::LSPS1(super::event::Event::DisplayOrder { + self.enqueue_event(Event::LSPS1Client(LSPS1ClientEvent::DisplayOrder { id: channel_id, counterparty_node_id: *counterparty_node_id, order: response.order, @@ -780,101 +569,6 @@ where Ok(()) } - fn handle_get_order_request( - &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetOrderRequest, - ) -> Result<(), LightningError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(&counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - let outbound_channel = peer_state_lock - .outbound_channels_by_order_id - .get_mut(¶ms.order_id) - .ok_or(LightningError { - err: format!( - "Received get order request for unknown order id {:?}", - params.order_id - ), - action: ErrorAction::IgnoreAndLog(Level::Info), - })?; - - if let Err(e) = outbound_channel.create_payment_invoice() { - peer_state_lock.outbound_channels_by_order_id.remove(¶ms.order_id); - self.enqueue_event(Event::LSPS1(super::event::Event::Refund { - request_id, - counterparty_node_id: *counterparty_node_id, - order_id: params.order_id, - })); - return Err(e); - } - - peer_state_lock - .pending_requests - .insert(request_id.clone(), LSPS1Request::GetOrder(params.clone())); - - self.enqueue_event(Event::LSPS1(super::event::Event::CheckPaymentConfirmation { - request_id, - counterparty_node_id: *counterparty_node_id, - order_id: params.order_id, - })); - } - None => { - return Err(LightningError { - err: format!("Received error response for a create order request from an unknown counterparty ({:?})",counterparty_node_id), - action: ErrorAction::IgnoreAndLog(Level::Info), - }); - } - } - - Ok(()) - } - - fn update_order_status( - &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, - order_state: OrderState, channel: Option, - ) -> Result<(), APIError> { - let outer_state_lock = self.per_peer_state.read().unwrap(); - - match outer_state_lock.get(&counterparty_node_id) { - Some(inner_state_lock) => { - let mut peer_state_lock = inner_state_lock.lock().unwrap(); - - if let Some(outbound_channel) = - peer_state_lock.outbound_channels_by_order_id.get_mut(&order_id) - { - let config = &outbound_channel.config; - - self.enqueue_response( - counterparty_node_id, - request_id, - LSPS1Response::GetOrder(GetOrderResponse { - response: CreateOrderResponse { - order_id, - order: config.order.clone(), - order_state, - created_at: config.created_at, - expires_at: config.expires_at, - payment: config.payment.clone(), - channel, - }, - }), - ) - } else { - return Err(APIError::APIMisuseError { - err: format!("Channel with order_id {} not found", order_id.0), - }); - } - } - None => { - return Err(APIError::APIMisuseError { - err: format!("No existing state with counterparty {}", counterparty_node_id), - }) - } - } - Ok(()) - } - fn handle_get_order_response( &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetOrderResponse, ) -> Result<(), LightningError> { @@ -952,20 +646,6 @@ where } } - fn enqueue_response( - &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS1Response, - ) { - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages - .push((counterparty_node_id, LSPS1Message::Response(request_id, response).into())); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } - } - fn enqueue_event(&self, event: Event) { self.pending_events.enqueue(event); } @@ -989,7 +669,7 @@ where } impl ProtocolMessageHandler - for LSPS1MessageHandler + for LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, @@ -997,40 +677,36 @@ where C::Target: Filter, { type ProtocolMessage = LSPS1Message; - const PROTOCOL_NUMBER: Option = Some(2); + const PROTOCOL_NUMBER: Option = Some(1); fn handle_message( &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { match message { - LSPS1Message::Request(request_id, request) => match request { - super::msgs::LSPS1Request::GetInfo(_) => { - self.handle_get_info_request(request_id, counterparty_node_id) - } - super::msgs::LSPS1Request::CreateOrder(params) => { - self.handle_create_order_request(request_id, counterparty_node_id, params) - } - super::msgs::LSPS1Request::GetOrder(params) => { - self.handle_get_order_request(request_id, counterparty_node_id, params) - } - }, LSPS1Message::Response(request_id, response) => match response { - super::msgs::LSPS1Response::GetInfo(params) => { + LSPS1Response::GetInfo(params) => { self.handle_get_info_response(request_id, counterparty_node_id, params) } - super::msgs::LSPS1Response::CreateOrder(params) => { + LSPS1Response::CreateOrder(params) => { self.handle_create_order_response(request_id, counterparty_node_id, params) } - super::msgs::LSPS1Response::CreateOrderError(params) => { + LSPS1Response::CreateOrderError(params) => { self.handle_create_order_error(request_id, counterparty_node_id, params) } - super::msgs::LSPS1Response::GetOrder(params) => { + LSPS1Response::GetOrder(params) => { self.handle_get_order_response(request_id, counterparty_node_id, params) } - super::msgs::LSPS1Response::GetOrderError(error) => { + LSPS1Response::GetOrderError(error) => { self.handle_get_order_error(request_id, counterparty_node_id, error) } }, + _ => { + debug_assert!( + false, + "Client handler received LSPS1 request message. This should never happen." + ); + Err(LightningError { err: format!("Client handler received LSPS1 request message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } } } } diff --git a/src/lsps1/event.rs b/src/lsps1/event.rs index b0657e0..432b928 100644 --- a/src/lsps1/event.rs +++ b/src/lsps1/event.rs @@ -7,9 +7,9 @@ use crate::prelude::String; use bitcoin::secp256k1::PublicKey; -/// An event which you should probably take some action in response to. +/// An event which an LSPS1 client should take some action in response to. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Event { +pub enum LSPS1ClientEvent { /// TODO GetInfoResponse { /// TODO @@ -26,15 +26,6 @@ pub enum Event { options_supported: OptionsSupported, }, /// TODO - CreateInvoice { - /// TODO - request_id: RequestId, - /// TODO - counterparty_node_id: PublicKey, - /// TODO - order: OrderParams, - }, - /// TODO DisplayOrder { /// TODO id: u128, @@ -47,18 +38,19 @@ pub enum Event { /// TODO channel: Option, }, +} + +/// An event which an LSPS1 server should take some action in response to. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS1ServiceEvent { /// TODO - PayforChannel { + CreateInvoice { /// TODO request_id: RequestId, /// TODO counterparty_node_id: PublicKey, /// TODO order: OrderParams, - /// TODO - payment: OrderPayment, - /// TODO - channel: Option, }, /// TODO CheckPaymentConfirmation { @@ -70,15 +62,6 @@ pub enum Event { order_id: OrderId, }, /// TODO - OpenChannel { - /// TODO - request_id: RequestId, - /// TODO - counterparty_node_id: PublicKey, - /// TODO - order_id: OrderId, - }, - /// TODO Refund { /// TODO request_id: RequestId, diff --git a/src/lsps1/mod.rs b/src/lsps1/mod.rs index dc7c14f..06d5e75 100644 --- a/src/lsps1/mod.rs +++ b/src/lsps1/mod.rs @@ -9,7 +9,8 @@ //! Types and primitives that implement the LSPS1: Channel Request specification. +pub mod client; pub mod event; -pub mod message_handler; pub mod msgs; +pub mod service; pub(crate) mod utils; diff --git a/src/lsps1/service.rs b/src/lsps1/service.rs new file mode 100644 index 0000000..e405c66 --- /dev/null +++ b/src/lsps1/service.rs @@ -0,0 +1,494 @@ +// This file is Copyright its original authors, visible in version contror +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Contains the main LSPS1 server object, [`LSPS1ServiceHandler`]. + +use super::event::LSPS1ServiceEvent; +use super::msgs::{ + ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoResponse, GetOrderRequest, + GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, OrderId, + OrderParams, OrderPayment, OrderState, LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, + LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, +}; +use super::utils::is_valid; + +use crate::events::EventQueue; +use crate::lsps0::message_handler::ProtocolMessageHandler; +use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::prelude::{HashMap, String, ToString, Vec}; +use crate::sync::{Arc, Mutex, RwLock}; +use crate::utils; +use crate::{events::Event, lsps0::msgs::ResponseError}; + +use lightning::chain::Filter; +use lightning::ln::channelmanager::AChannelManager; +use lightning::ln::msgs::{ErrorAction, LightningError}; +use lightning::ln::peer_handler::APeerManager; +use lightning::sign::EntropySource; +use lightning::util::errors::APIError; +use lightning::util::logger::Level; + +use bitcoin::secp256k1::PublicKey; + +use chrono::Utc; +use core::ops::Deref; + +const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; + +/// Server-side configuration options for LSPS1 channel requests. +#[derive(Clone, Debug)] +pub struct LSPS1ServiceConfig { + /// A token to be send with each channel request. + pub token: Option, + /// The options supported by the LSP. + pub options_supported: Option, + /// The LSP's website. + pub website: Option, +} + +struct ChannelStateError(String); + +impl From for LightningError { + fn from(value: ChannelStateError) -> Self { + LightningError { err: value.0, action: ErrorAction::IgnoreAndLog(Level::Info) } + } +} + +#[derive(PartialEq, Debug)] +enum OutboundRequestState { + OrderCreated { order_id: OrderId }, + WaitingPayment { order_id: OrderId }, + Ready, +} + +impl OutboundRequestState { + fn create_payment_invoice(&self) -> Result { + match self { + OutboundRequestState::OrderCreated { order_id } => { + Ok(OutboundRequestState::WaitingPayment { order_id: order_id.clone() }) + } + state => Err(ChannelStateError(format!( + "Received unexpected get_versions response. JIT Channel was in state: {:?}", + state + ))), + } + } +} + +struct OutboundLSPS1Config { + order: OrderParams, + created_at: chrono::DateTime, + expires_at: chrono::DateTime, + payment: OrderPayment, +} + +struct OutboundCRChannel { + state: OutboundRequestState, + config: OutboundLSPS1Config, +} + +impl OutboundCRChannel { + fn new( + order: OrderParams, created_at: chrono::DateTime, expires_at: chrono::DateTime, + order_id: OrderId, payment: OrderPayment, + ) -> Self { + Self { + state: OutboundRequestState::OrderCreated { order_id }, + config: OutboundLSPS1Config { order, created_at, expires_at, payment }, + } + } + fn create_payment_invoice(&mut self) -> Result<(), LightningError> { + self.state = self.state.create_payment_invoice()?; + Ok(()) + } + + fn check_order_validity(&self, options_supported: &OptionsSupported) -> bool { + let order = &self.config.order; + + is_valid(order, options_supported) + } +} + +#[derive(Default)] +struct PeerState { + outbound_channels_by_order_id: HashMap, + request_to_cid: HashMap, + pending_requests: HashMap, +} + +impl PeerState { + fn insert_outbound_channel(&mut self, order_id: OrderId, channel: OutboundCRChannel) { + self.outbound_channels_by_order_id.insert(order_id, channel); + } + + fn insert_request(&mut self, request_id: RequestId, channel_id: u128) { + self.request_to_cid.insert(request_id, channel_id); + } + + fn remove_outbound_channel(&mut self, order_id: OrderId) { + self.outbound_channels_by_order_id.remove(&order_id); + } +} + +/// The main object allowing to send and receive LSPS1 messages. +pub struct LSPS1ServiceHandler +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + entropy_source: ES, + channel_manager: CM, + peer_manager: Mutex>, + chain_source: Option, + pending_messages: Arc>>, + pending_events: Arc, + per_peer_state: RwLock>>, + config: LSPS1ServiceConfig, +} + +impl LSPS1ServiceHandler +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, + ES::Target: EntropySource, +{ + pub(crate) fn new( + entropy_source: ES, pending_messages: Arc>>, + pending_events: Arc, channel_manager: CM, chain_source: Option, + config: LSPS1ServiceConfig, + ) -> Self { + Self { + entropy_source, + channel_manager, + peer_manager: Mutex::new(None), + chain_source, + pending_messages, + pending_events, + per_peer_state: RwLock::new(HashMap::new()), + config, + } + } + + /// Set a [`PeerManager`] reference for the message handler. + /// + /// This allows the message handler to wake the [`PeerManager`] by calling + /// [`PeerManager::process_events`] after enqueing messages to be sent. + /// + /// Without this the messages will be sent based on whatever polling interval + /// your background processor uses. + /// + /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager + /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events + pub fn set_peer_manager(&self, peer_manager: PM) { + *self.peer_manager.lock().unwrap() = Some(peer_manager); + } + + fn handle_get_info_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + let response = GetInfoResponse { + supported_versions: SUPPORTED_SPEC_VERSIONS.to_vec(), + website: self.config.website.clone().unwrap().to_string(), + options: self + .config + .options_supported + .clone() + .ok_or(LightningError { + err: format!("Configuration for LSP server not set."), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + .unwrap(), + }; + + self.enqueue_response(*counterparty_node_id, request_id, LSPS1Response::GetInfo(response)); + Ok(()) + } + + fn handle_create_order_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: CreateOrderRequest, + ) -> Result<(), LightningError> { + if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrderError(ResponseError { + code: LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, + message: format!("version {} is not supported", params.version), + data: Some(format!("Supported versions are {:?}", SUPPORTED_SPEC_VERSIONS)), + }), + ); + return Err(LightningError { + err: format!("client requested unsupported version {}", params.version), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + + if !is_valid(¶ms.order, &self.config.options_supported.as_ref().unwrap()) { + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrderError(ResponseError { + code: LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, + message: format!("Order does not match options supported by LSP server"), + data: Some(format!( + "Supported options are {:?}", + &self.config.options_supported.as_ref().unwrap() + )), + }), + ); + return Err(LightningError { + err: format!("client requested unsupported version {}", params.version), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + + let inner_state_lock = outer_state_lock + .entry(*counterparty_node_id) + .or_insert(Mutex::new(PeerState::default())); + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + peer_state_lock + .pending_requests + .insert(request_id.clone(), LSPS1Request::CreateOrder(params.clone())); + + self.enqueue_event(Event::LSPS1Service(LSPS1ServiceEvent::CreateInvoice { + request_id, + counterparty_node_id: *counterparty_node_id, + order: params.order, + })); + + Ok(()) + } + + fn send_invoice_for_order( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: OrderPayment, + created_at: chrono::DateTime, expires_at: chrono::DateTime, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + match peer_state_lock.pending_requests.remove(&request_id) { + Some(LSPS1Request::CreateOrder(params)) => { + let order_id = self.generate_order_id(); + let channel = OutboundCRChannel::new( + params.order.clone(), + created_at.clone(), + expires_at.clone(), + order_id.clone(), + payment.clone(), + ); + + peer_state_lock.insert_outbound_channel(order_id.clone(), channel); + + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrder(CreateOrderResponse { + order: params.order, + order_id, + order_state: OrderState::Created, + created_at, + expires_at, + payment, + channel: None, + }), + ); + } + + _ => { + return Err(APIError::APIMisuseError { + err: format!("No pending buy request for request_id: {:?}", request_id), + }) + } + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!( + "No state for the counterparty exists: {:?}", + counterparty_node_id + ), + }) + } + } + + Ok(()) + } + + fn handle_get_order_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetOrderRequest, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + let outbound_channel = peer_state_lock + .outbound_channels_by_order_id + .get_mut(¶ms.order_id) + .ok_or(LightningError { + err: format!( + "Received get order request for unknown order id {:?}", + params.order_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + if let Err(e) = outbound_channel.create_payment_invoice() { + peer_state_lock.outbound_channels_by_order_id.remove(¶ms.order_id); + self.enqueue_event(Event::LSPS1Service(LSPS1ServiceEvent::Refund { + request_id, + counterparty_node_id: *counterparty_node_id, + order_id: params.order_id, + })); + return Err(e); + } + + peer_state_lock + .pending_requests + .insert(request_id.clone(), LSPS1Request::GetOrder(params.clone())); + + self.enqueue_event(Event::LSPS1Service( + LSPS1ServiceEvent::CheckPaymentConfirmation { + request_id, + counterparty_node_id: *counterparty_node_id, + order_id: params.order_id, + }, + )); + } + None => { + return Err(LightningError { + err: format!("Received error response for a create order request from an unknown counterparty ({:?})",counterparty_node_id), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + } + + Ok(()) + } + + fn update_order_status( + &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, + order_state: OrderState, channel: Option, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state_lock = inner_state_lock.lock().unwrap(); + + if let Some(outbound_channel) = + peer_state_lock.outbound_channels_by_order_id.get_mut(&order_id) + { + let config = &outbound_channel.config; + + self.enqueue_response( + counterparty_node_id, + request_id, + LSPS1Response::GetOrder(GetOrderResponse { + response: CreateOrderResponse { + order_id, + order: config.order.clone(), + order_state, + created_at: config.created_at, + expires_at: config.expires_at, + payment: config.payment.clone(), + channel, + }, + }), + ) + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with order_id {} not found", order_id.0), + }); + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }) + } + } + Ok(()) + } + + fn enqueue_response( + &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS1Response, + ) { + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages + .push((counterparty_node_id, LSPS1Message::Response(request_id, response).into())); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.as_ref().process_events(); + } + } + + fn enqueue_event(&self, event: Event) { + self.pending_events.enqueue(event); + } + + fn generate_request_id(&self) -> RequestId { + let bytes = self.entropy_source.get_secure_random_bytes(); + RequestId(utils::hex_str(&bytes[0..16])) + } + + fn generate_order_id(&self) -> OrderId { + let bytes = self.entropy_source.get_secure_random_bytes(); + OrderId(utils::hex_str(&bytes[0..16])) + } +} + +impl ProtocolMessageHandler + for LSPS1ServiceHandler +where + ES::Target: EntropySource, + CM::Target: AChannelManager, + PM::Target: APeerManager, + C::Target: Filter, +{ + type ProtocolMessage = LSPS1Message; + const PROTOCOL_NUMBER: Option = Some(1); + + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + match message { + LSPS1Message::Request(request_id, request) => match request { + LSPS1Request::GetInfo(_) => { + self.handle_get_info_request(request_id, counterparty_node_id) + } + LSPS1Request::CreateOrder(params) => { + self.handle_create_order_request(request_id, counterparty_node_id, params) + } + LSPS1Request::GetOrder(params) => { + self.handle_get_order_request(request_id, counterparty_node_id, params) + } + }, + _ => { + debug_assert!( + false, + "Service handler received LSPS1 response message. This should never happen." + ); + Err(LightningError { err: format!("Service handler received LSPS1 response message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } + } + } +} diff --git a/src/manager.rs b/src/manager.rs index 66f58c5..2991297 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -4,7 +4,11 @@ use crate::lsps0::message_handler::ProtocolMessageHandler; use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; #[cfg(lsps1)] -use crate::lsps1::message_handler::{LSPS1Config, LSPS1MessageHandler}; +use crate::lsps1::client::{LSPS1ClientConfig, LSPS1ClientHandler}; +#[cfg(lsps1)] +use crate::lsps1::msgs::LSPS1Message; +#[cfg(lsps1)] +use crate::lsps1::service::{LSPS1ServiceConfig, LSPS1ServiceHandler}; use crate::lsps2::client::{LSPS2ClientConfig, LSPS2ClientHandler}; use crate::lsps2::msgs::LSPS2Message; @@ -22,28 +26,36 @@ use lightning::sign::EntropySource; use lightning::util::logger::Level; use lightning::util::ser::Readable; -use bitcoin::blockdata::constants::genesis_block; use bitcoin::secp256k1::PublicKey; -use bitcoin::BlockHash; use core::ops::Deref; const LSPS_FEATURE_BIT: usize = 729; -/// A configuration for [`LiquidityManager`]. +/// A server-side configuration for [`LiquidityManager`]. /// -/// Allows end-user to configure options when using the [`LiquidityManager`] +/// Allows end-users to configure options when using the [`LiquidityManager`] /// to provide liquidity services to clients. -pub struct LiquidityProviderConfig { - /// LSPS1 Configuration +pub struct LiquidityServiceConfig { + /// Optional server-side configuration for LSPS1 channel requests. #[cfg(lsps1)] - pub lsps1_config: Option, - /// Optional client-side configuration for JIT channels. - pub lsps2_client_config: Option, + pub lsps1_service_config: Option, /// Optional server-side configuration for JIT channels /// should you want to support them. pub lsps2_service_config: Option, } +/// A client-side configuration for [`LiquidityManager`]. +/// +/// Allows end-user to configure options when using the [`LiquidityManager`] +/// to access liquidity services from a provider. +pub struct LiquidityClientConfig { + /// Optional client-side configuration for LSPS1 channel requests. + #[cfg(lsps1)] + pub lsps1_client_config: Option, + /// Optional client-side configuration for JIT channels. + pub lsps2_client_config: Option, +} + /// The main interface into LSP functionality. /// /// Should be used as a [`CustomMessageHandler`] for your @@ -79,14 +91,15 @@ pub struct LiquidityManager< request_id_to_method_map: Mutex>, lsps0_message_handler: LSPS0MessageHandler, #[cfg(lsps1)] - lsps1_message_handler: Option>, + lsps1_service_handler: Option>, + #[cfg(lsps1)] + lsps1_client_handler: Option>, lsps2_service_handler: Option>, lsps2_client_handler: Option>, - provider_config: Option, - channel_manager: CM, - chain_source: Option, - genesis_hash: Option, + service_config: Option, + _client_config: Option, best_block: Option>, + _chain_source: Option, } impl @@ -99,10 +112,12 @@ where { /// Constructor for the [`LiquidityManager`]. /// - /// Sets up the required protocol message handlers based on the given [`LiquidityProviderConfig`]. + /// Sets up the required protocol message handlers based on the given + /// [`LiquidityClientConfig`] and [`LiquidityServiceConfig`]. pub fn new( - entropy_source: ES, provider_config: Option, channel_manager: CM, - chain_source: Option, chain_params: Option, + entropy_source: ES, channel_manager: CM, chain_source: Option, + chain_params: Option, service_config: Option, + client_config: Option, ) -> Self where { let pending_messages = Arc::new(Mutex::new(vec![])); @@ -114,7 +129,7 @@ where { Arc::clone(&pending_messages), ); - let lsps2_client_handler = provider_config.as_ref().and_then(|config| { + let lsps2_client_handler = client_config.as_ref().and_then(|config| { config.lsps2_client_config.map(|config| { LSPS2ClientHandler::new( entropy_source.clone(), @@ -124,7 +139,7 @@ where { ) }) }); - let lsps2_service_handler = provider_config.as_ref().and_then(|config| { + let lsps2_service_handler = service_config.as_ref().and_then(|config| { config.lsps2_service_config.as_ref().map(|config| { LSPS2ServiceHandler::new( Arc::clone(&pending_messages), @@ -136,15 +151,29 @@ where { }); #[cfg(lsps1)] - let lsps1_message_handler = provider_config.as_ref().and_then(|config| { - config.lsps1_config.as_ref().map(|lsps1_config| { - LSPS1MessageHandler::new( + let lsps1_client_handler = client_config.as_ref().and_then(|config| { + config.lsps1_client_config.as_ref().map(|config| { + LSPS1ClientHandler::new( + entropy_source.clone(), + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + channel_manager.clone(), + chain_source.clone(), + config.clone(), + ) + }) + }); + + #[cfg(lsps1)] + let lsps1_service_handler = service_config.as_ref().and_then(|config| { + config.lsps1_service_config.as_ref().map(|config| { + LSPS1ServiceHandler::new( entropy_source.clone(), - lsps1_config, Arc::clone(&pending_messages), Arc::clone(&pending_events), channel_manager.clone(), chain_source.clone(), + config.clone(), ) }) }); @@ -155,15 +184,15 @@ where { request_id_to_method_map: Mutex::new(HashMap::new()), lsps0_message_handler, #[cfg(lsps1)] - lsps1_message_handler, + lsps1_client_handler, + #[cfg(lsps1)] + lsps1_service_handler, lsps2_client_handler, lsps2_service_handler, - provider_config, - channel_manager, - chain_source, - genesis_hash: chain_params - .map(|chain_params| genesis_block(chain_params.network).header.block_hash()), + service_config, + _client_config: client_config, best_block: chain_params.map(|chain_params| RwLock::new(chain_params.best_block)), + _chain_source: chain_source, } } @@ -172,10 +201,16 @@ where { &self.lsps0_message_handler } - /// Returns a reference to the LSPS1 message handler. + /// Returns a reference to the LSPS1 client-side handler. + #[cfg(lsps1)] + pub fn lsps1_client_handler(&self) -> Option<&LSPS1ClientHandler> { + self.lsps1_client_handler.as_ref() + } + + /// Returns a reference to the LSPS1 server-side handler. #[cfg(lsps1)] - pub fn lsps1_message_handler(&self) -> Option<&LSPS1MessageHandler> { - self.lsps1_message_handler.as_ref() + pub fn lsps1_service_handler(&self) -> Option<&LSPS1ServiceHandler> { + self.lsps1_service_handler.as_ref() } /// Returns a reference to the LSPS2 client-side handler. @@ -222,8 +257,12 @@ where { /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events pub fn set_peer_manager(&self, peer_manager: PM) { #[cfg(lsps1)] - if let Some(lsps1_message_handler) = &self.lsps1_message_handler { - lsps1_message_handler.set_peer_manager(peer_manager.clone()); + if let Some(lsps1_client_handler) = &self.lsps1_client_handler { + lsps1_client_handler.set_peer_manager(peer_manager.clone()); + } + #[cfg(lsps1)] + if let Some(lsps1_service_handler) = &self.lsps1_service_handler { + lsps1_service_handler.set_peer_manager(peer_manager.clone()); } if let Some(lsps2_client_handler) = &self.lsps2_client_handler { lsps2_client_handler.set_peer_manager(peer_manager.clone()); @@ -244,12 +283,21 @@ where { self.lsps0_message_handler.handle_message(msg, sender_node_id)?; } #[cfg(lsps1)] - LSPSMessage::LSPS1(msg) => match &self.lsps1_message_handler { - Some(lsps1_message_handler) => { - lsps1_message_handler.handle_message(msg, sender_node_id)?; + LSPSMessage::LSPS1(msg @ LSPS1Message::Response(..)) => match &self.lsps1_client_handler { + Some(lsps1_client_handler) => { + lsps1_client_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS1 response message without LSPS1 client handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + }, + #[cfg(lsps1)] + LSPSMessage::LSPS1(msg @ LSPS1Message::Request(..)) => match &self.lsps1_service_handler { + Some(lsps1_service_handler) => { + lsps1_service_handler.handle_message(msg, sender_node_id)?; } None => { - return Err(LightningError { err: format!("Received LSPS1 message without LSPS1 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + return Err(LightningError { err: format!("Received LSPS1 request message without LSPS1 service handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); } }, LSPSMessage::LSPS2(msg @ LSPS2Message::Response(..)) => { @@ -348,7 +396,7 @@ where fn provided_node_features(&self) -> NodeFeatures { let mut features = NodeFeatures::empty(); - if self.provider_config.is_some() { + if self.service_config.is_some() { features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); } @@ -358,7 +406,7 @@ where fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { let mut features = InitFeatures::empty(); - if self.provider_config.is_some() { + if self.service_config.is_some() { features.set_optional_custom_bit(LSPS_FEATURE_BIT).unwrap(); } From 83181ae16c809495aeea4ca0ce90d9aaac770afd Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 14 Dec 2023 15:07:03 +0100 Subject: [PATCH 15/18] DRY up and clean up helper methods --- src/lsps1/client.rs | 45 +++++++++++++------------------------------- src/lsps1/service.rs | 15 +++------------ src/lsps2/client.rs | 35 ++++++++++++++-------------------- 3 files changed, 30 insertions(+), 65 deletions(-) diff --git a/src/lsps1/client.rs b/src/lsps1/client.rs index 0b6fa7f..a1c2cb6 100644 --- a/src/lsps1/client.rs +++ b/src/lsps1/client.rs @@ -284,7 +284,7 @@ where let mut peer_state_lock = inner_state_lock.lock().unwrap(); peer_state_lock.insert_inbound_channel(channel_id, channel); - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); { @@ -339,7 +339,7 @@ where } }; - self.enqueue_event(Event::LSPS1Client(LSPS1ClientEvent::GetInfoResponse { + self.pending_events.enqueue(Event::LSPS1Client(LSPS1ClientEvent::GetInfoResponse { id: channel_id, request_id, counterparty_node_id: *counterparty_node_id, @@ -385,7 +385,7 @@ where } }; - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); { @@ -454,13 +454,15 @@ where if total_fees == response.payment.order_total_sat && total_fees < max_channel_fees_msat { - self.enqueue_event(Event::LSPS1Client(LSPS1ClientEvent::DisplayOrder { - id: channel_id, - counterparty_node_id: *counterparty_node_id, - order: response.order, - payment: response.payment, - channel: response.channel, - })); + self.pending_events.enqueue(Event::LSPS1Client( + LSPS1ClientEvent::DisplayOrder { + id: channel_id, + counterparty_node_id: *counterparty_node_id, + order: response.order, + payment: response.payment, + channel: response.channel, + }, + )); } else { peer_state_lock.remove_inbound_channel(channel_id); return Err(LightningError { @@ -534,7 +536,7 @@ where return Err(APIError::APIMisuseError { err: e.err }); } - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); { @@ -645,27 +647,6 @@ where } } } - - fn enqueue_event(&self, event: Event) { - self.pending_events.enqueue(event); - } - - fn generate_channel_id(&self) -> u128 { - let bytes = self.entropy_source.get_secure_random_bytes(); - let mut id_bytes: [u8; 16] = [0; 16]; - id_bytes.copy_from_slice(&bytes[0..16]); - u128::from_be_bytes(id_bytes) - } - - fn generate_request_id(&self) -> RequestId { - let bytes = self.entropy_source.get_secure_random_bytes(); - RequestId(utils::hex_str(&bytes[0..16])) - } - - fn generate_order_id(&self) -> OrderId { - let bytes = self.entropy_source.get_secure_random_bytes(); - OrderId(utils::hex_str(&bytes[0..16])) - } } impl ProtocolMessageHandler diff --git a/src/lsps1/service.rs b/src/lsps1/service.rs index e405c66..50bd26b 100644 --- a/src/lsps1/service.rs +++ b/src/lsps1/service.rs @@ -263,7 +263,7 @@ where .pending_requests .insert(request_id.clone(), LSPS1Request::CreateOrder(params.clone())); - self.enqueue_event(Event::LSPS1Service(LSPS1ServiceEvent::CreateInvoice { + self.pending_events.enqueue(Event::LSPS1Service(LSPS1ServiceEvent::CreateInvoice { request_id, counterparty_node_id: *counterparty_node_id, order: params.order, @@ -351,7 +351,7 @@ where if let Err(e) = outbound_channel.create_payment_invoice() { peer_state_lock.outbound_channels_by_order_id.remove(¶ms.order_id); - self.enqueue_event(Event::LSPS1Service(LSPS1ServiceEvent::Refund { + self.pending_events.enqueue(Event::LSPS1Service(LSPS1ServiceEvent::Refund { request_id, counterparty_node_id: *counterparty_node_id, order_id: params.order_id, @@ -363,7 +363,7 @@ where .pending_requests .insert(request_id.clone(), LSPS1Request::GetOrder(params.clone())); - self.enqueue_event(Event::LSPS1Service( + self.pending_events.enqueue(Event::LSPS1Service( LSPS1ServiceEvent::CheckPaymentConfirmation { request_id, counterparty_node_id: *counterparty_node_id, @@ -441,15 +441,6 @@ where } } - fn enqueue_event(&self, event: Event) { - self.pending_events.enqueue(event); - } - - fn generate_request_id(&self) -> RequestId { - let bytes = self.entropy_source.get_secure_random_bytes(); - RequestId(utils::hex_str(&bytes[0..16])) - } - fn generate_order_id(&self) -> OrderId { let bytes = self.entropy_source.get_secure_random_bytes(); OrderId(utils::hex_str(&bytes[0..16])) diff --git a/src/lsps2/client.rs b/src/lsps2/client.rs index 281dc49..7afc4cb 100644 --- a/src/lsps2/client.rs +++ b/src/lsps2/client.rs @@ -277,7 +277,7 @@ where let mut peer_state_lock = inner_state_lock.lock().unwrap(); peer_state_lock.insert_inbound_channel(jit_channel_id, channel); - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), jit_channel_id); { @@ -320,7 +320,7 @@ where } }; - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); let payment_size_msat = jit_channel.config.payment_size_msat; peer_state.insert_request(request_id.clone(), jit_channel_id); @@ -365,15 +365,6 @@ where u128::from_be_bytes(id_bytes) } - fn generate_request_id(&self) -> RequestId { - let bytes = self.entropy_source.get_secure_random_bytes(); - RequestId(utils::hex_str(&bytes[0..16])) - } - - fn enqueue_event(&self, event: Event) { - self.pending_events.enqueue(event); - } - fn handle_get_versions_response( &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetVersionsResponse, ) -> Result<(), LightningError> { @@ -412,7 +403,7 @@ where } }; - let request_id = self.generate_request_id(); + let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state.insert_request(request_id.clone(), jit_channel_id); { @@ -478,14 +469,16 @@ where return Err(e); } - self.enqueue_event(Event::LSPS2Client(LSPS2ClientEvent::GetInfoResponse { - counterparty_node_id: *counterparty_node_id, - opening_fee_params_menu: result.opening_fee_params_menu, - min_payment_size_msat: result.min_payment_size_msat, - max_payment_size_msat: result.max_payment_size_msat, - jit_channel_id, - user_channel_id: jit_channel.config.user_id, - })); + self.pending_events.enqueue(Event::LSPS2Client( + LSPS2ClientEvent::GetInfoResponse { + counterparty_node_id: *counterparty_node_id, + opening_fee_params_menu: result.opening_fee_params_menu, + min_payment_size_msat: result.min_payment_size_msat, + max_payment_size_msat: result.max_payment_size_msat, + jit_channel_id, + user_channel_id: jit_channel.config.user_id, + }, + )); } None => { return Err(LightningError { @@ -572,7 +565,7 @@ where } if let Ok(scid) = result.jit_channel_scid.to_scid() { - self.enqueue_event(Event::LSPS2Client( + self.pending_events.enqueue(Event::LSPS2Client( LSPS2ClientEvent::InvoiceGenerationReady { counterparty_node_id: *counterparty_node_id, scid, From 332741ff74f8bd8e2ad54421409fd009ac4ff0c7 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 15 Dec 2023 11:03:08 +0100 Subject: [PATCH 16/18] Split LSPS0 in client/server handler modules --- src/lsps0/{message_handler.rs => client.rs} | 104 +++------------- src/lsps0/mod.rs | 3 +- src/lsps0/msgs.rs | 16 +++ src/lsps0/service.rs | 128 ++++++++++++++++++++ src/lsps1/client.rs | 4 +- src/lsps1/service.rs | 3 +- src/lsps2/client.rs | 4 +- src/lsps2/service.rs | 3 +- src/manager.rs | 52 +++++--- 9 files changed, 207 insertions(+), 110 deletions(-) rename src/lsps0/{message_handler.rs => client.rs} (54%) create mode 100644 src/lsps0/service.rs diff --git a/src/lsps0/message_handler.rs b/src/lsps0/client.rs similarity index 54% rename from src/lsps0/message_handler.rs rename to src/lsps0/client.rs index 9d79c15..550e3a0 100644 --- a/src/lsps0/message_handler.rs +++ b/src/lsps0/client.rs @@ -1,4 +1,4 @@ -//! Contains the logic to handle LSPS0 protocol messages. +//! Contains the main LSPS2 client-side object, [`LSPS0ClientHandler`]. //! //! Please refer to the [LSPS0 //! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more @@ -6,7 +6,7 @@ use crate::lsps0::msgs::{ LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, - ListProtocolsResponse, RequestId, ResponseError, + ListProtocolsResponse, ProtocolMessageHandler, ResponseError, }; use crate::prelude::Vec; use crate::sync::{Arc, Mutex}; @@ -20,39 +20,24 @@ use bitcoin::secp256k1::PublicKey; use core::ops::Deref; -/// A trait used to implement a specific LSPS protocol. -/// -/// The messages the protocol uses need to be able to be mapped -/// from and into [`LSPSMessage`]. -pub(crate) trait ProtocolMessageHandler { - type ProtocolMessage: TryFrom + Into; - const PROTOCOL_NUMBER: Option; - - fn handle_message( - &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, - ) -> Result<(), LightningError>; -} - /// A message handler capable of sending and handling LSPS0 messages. -pub struct LSPS0MessageHandler +pub struct LSPS0ClientHandler where ES::Target: EntropySource, { entropy_source: ES, pending_messages: Arc>>, - protocols: Vec, } -impl LSPS0MessageHandler +impl LSPS0ClientHandler where ES::Target: EntropySource, { - /// Returns a new instance of [`LSPS0MessageHandler`]. - pub fn new( - entropy_source: ES, protocols: Vec, - pending_messages: Arc>>, + /// Returns a new instance of [`LSPS0ClientHandler`]. + pub(crate) fn new( + entropy_source: ES, pending_messages: Arc>>, ) -> Self { - Self { entropy_source, protocols, pending_messages } + Self { entropy_source, pending_messages } } /// Calls LSPS0's `list_protocols`. @@ -73,23 +58,6 @@ where self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); } - fn handle_request( - &self, request_id: RequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, - ) -> Result<(), lightning::ln::msgs::LightningError> { - match request { - LSPS0Request::ListProtocols(_) => { - let msg = LSPS0Message::Response( - request_id, - LSPS0Response::ListProtocols(ListProtocolsResponse { - protocols: self.protocols.clone(), - }), - ); - self.enqueue_message(*counterparty_node_id, msg); - Ok(()) - } - } - } - fn handle_response( &self, response: LSPS0Response, _counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { @@ -108,7 +76,7 @@ where } } -impl ProtocolMessageHandler for LSPS0MessageHandler +impl ProtocolMessageHandler for LSPS0ClientHandler where ES::Target: EntropySource, { @@ -119,12 +87,16 @@ where &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { match message { - LSPS0Message::Request(request_id, request) => { - self.handle_request(request_id, request, counterparty_node_id) - } LSPS0Message::Response(_, response) => { self.handle_response(response, counterparty_node_id) } + LSPS0Message::Request(..) => { + debug_assert!( + false, + "Client handler received LSPS0 request message. This should never happen." + ); + Err(LightningError { err: format!("Client handler received LSPS0 request message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } } } } @@ -135,6 +107,8 @@ mod tests { use alloc::string::ToString; use alloc::sync::Arc; + use crate::lsps0::msgs::RequestId; + use super::*; struct TestEntropy {} @@ -144,50 +118,12 @@ mod tests { } } - #[test] - fn test_handle_list_protocols_request() { - let entropy = Arc::new(TestEntropy {}); - let protocols: Vec = vec![]; - let pending_messages = Arc::new(Mutex::new(vec![])); - - let lsps0_handler = - Arc::new(LSPS0MessageHandler::new(entropy, protocols, pending_messages.clone())); - - let list_protocols_request = LSPS0Message::Request( - RequestId("xyz123".to_string()), - LSPS0Request::ListProtocols(ListProtocolsRequest {}), - ); - let counterparty_node_id = utils::parse_pubkey( - "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", - ) - .unwrap(); - - lsps0_handler.handle_message(list_protocols_request, &counterparty_node_id).unwrap(); - let pending_messages = pending_messages.lock().unwrap(); - - assert_eq!(pending_messages.len(), 1); - - let (pubkey, message) = &pending_messages[0]; - - assert_eq!(*pubkey, counterparty_node_id); - assert_eq!( - *message, - LSPSMessage::LSPS0(LSPS0Message::Response( - RequestId("xyz123".to_string()), - LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: vec![] }) - )) - ); - } - #[test] fn test_list_protocols() { let pending_messages = Arc::new(Mutex::new(vec![])); - let lsps0_handler = Arc::new(LSPS0MessageHandler::new( - Arc::new(TestEntropy {}), - vec![1, 2, 3], - pending_messages.clone(), - )); + let lsps0_handler = + Arc::new(LSPS0ClientHandler::new(Arc::new(TestEntropy {}), pending_messages.clone())); let counterparty_node_id = utils::parse_pubkey( "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", diff --git a/src/lsps0/mod.rs b/src/lsps0/mod.rs index 9843fd8..e8c701c 100644 --- a/src/lsps0/mod.rs +++ b/src/lsps0/mod.rs @@ -9,5 +9,6 @@ //! Types and primitives that implement the LSPS0: Transport Layer specification. -pub mod message_handler; +pub mod client; pub mod msgs; +pub mod service; diff --git a/src/lsps0/msgs.rs b/src/lsps0/msgs.rs index 7b659ef..2d51b69 100644 --- a/src/lsps0/msgs.rs +++ b/src/lsps0/msgs.rs @@ -14,8 +14,11 @@ use crate::lsps2::msgs::{ use crate::prelude::{HashMap, String, ToString, Vec}; use lightning::impl_writeable_msg; +use lightning::ln::msgs::LightningError; use lightning::ln::wire; +use bitcoin::secp256k1::PublicKey; + use serde::de; use serde::de::{MapAccess, Visitor}; use serde::ser::SerializeStruct; @@ -40,6 +43,19 @@ const LSPS0_LISTPROTOCOLS_METHOD_NAME: &str = "lsps0.list_protocols"; /// The Lightning message type id for LSPS messages. pub const LSPS_MESSAGE_TYPE_ID: u16 = 37913; +/// A trait used to implement a specific LSPS protocol. +/// +/// The messages the protocol uses need to be able to be mapped +/// from and into [`LSPSMessage`]. +pub(crate) trait ProtocolMessageHandler { + type ProtocolMessage: TryFrom + Into; + const PROTOCOL_NUMBER: Option; + + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError>; +} + /// Lightning message type used by LSPS protocols. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RawLSPSMessage { diff --git a/src/lsps0/service.rs b/src/lsps0/service.rs new file mode 100644 index 0000000..8fdf47e --- /dev/null +++ b/src/lsps0/service.rs @@ -0,0 +1,128 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Contains the main LSPS0 server-side object, [`LSPS0ServiceHandler`]. +//! +//! Please refer to the [LSPS0 +//! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more +//! information. + +use crate::lsps0::msgs::{ + LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsResponse, + ProtocolMessageHandler, RequestId, +}; +use crate::prelude::Vec; +use crate::sync::{Arc, Mutex}; + +use lightning::ln::msgs::{ErrorAction, LightningError}; +use lightning::util::logger::Level; + +use bitcoin::secp256k1::PublicKey; + +/// The main server-side object allowing to send and receive LSPS0 messages. +pub struct LSPS0ServiceHandler { + pending_messages: Arc>>, + protocols: Vec, +} + +impl LSPS0ServiceHandler { + /// Returns a new instance of [`LSPS0ServiceHandler`]. + pub(crate) fn new( + protocols: Vec, pending_messages: Arc>>, + ) -> Self { + Self { protocols, pending_messages } + } + + fn enqueue_message(&self, counterparty_node_id: PublicKey, message: LSPS0Message) { + self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); + } + + fn handle_request( + &self, request_id: RequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, + ) -> Result<(), lightning::ln::msgs::LightningError> { + match request { + LSPS0Request::ListProtocols(_) => { + let msg = LSPS0Message::Response( + request_id, + LSPS0Response::ListProtocols(ListProtocolsResponse { + protocols: self.protocols.clone(), + }), + ); + self.enqueue_message(*counterparty_node_id, msg); + Ok(()) + } + } + } +} + +impl ProtocolMessageHandler for LSPS0ServiceHandler { + type ProtocolMessage = LSPS0Message; + const PROTOCOL_NUMBER: Option = None; + + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + match message { + LSPS0Message::Request(request_id, request) => { + self.handle_request(request_id, request, counterparty_node_id) + } + LSPS0Message::Response(..) => { + debug_assert!( + false, + "Service handler received LSPS0 response message. This should never happen." + ); + Err(LightningError { err: format!("Service handler received LSPS0 response message from node {:?}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}) + } + } + } +} + +#[cfg(test)] +mod tests { + + use crate::lsps0::msgs::ListProtocolsRequest; + use crate::utils; + use alloc::string::ToString; + use alloc::sync::Arc; + + use super::*; + + #[test] + fn test_handle_list_protocols_request() { + let protocols: Vec = vec![]; + let pending_messages = Arc::new(Mutex::new(vec![])); + + let lsps0_handler = Arc::new(LSPS0ServiceHandler::new(protocols, pending_messages.clone())); + + let list_protocols_request = LSPS0Message::Request( + RequestId("xyz123".to_string()), + LSPS0Request::ListProtocols(ListProtocolsRequest {}), + ); + let counterparty_node_id = utils::parse_pubkey( + "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", + ) + .unwrap(); + + lsps0_handler.handle_message(list_protocols_request, &counterparty_node_id).unwrap(); + let pending_messages = pending_messages.lock().unwrap(); + + assert_eq!(pending_messages.len(), 1); + + let (pubkey, message) = &pending_messages[0]; + + assert_eq!(*pubkey, counterparty_node_id); + assert_eq!( + *message, + LSPSMessage::LSPS0(LSPS0Message::Response( + RequestId("xyz123".to_string()), + LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: vec![] }) + )) + ); + } +} diff --git a/src/lsps1/client.rs b/src/lsps1/client.rs index a1c2cb6..087ca9d 100644 --- a/src/lsps1/client.rs +++ b/src/lsps1/client.rs @@ -18,11 +18,9 @@ use super::msgs::{ use super::utils::is_valid; use crate::events::EventQueue; -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; -use crate::utils; use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::chain::Filter; diff --git a/src/lsps1/service.rs b/src/lsps1/service.rs index 50bd26b..564aef8 100644 --- a/src/lsps1/service.rs +++ b/src/lsps1/service.rs @@ -19,8 +19,7 @@ use super::msgs::{ use super::utils::is_valid; use crate::events::EventQueue; -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; use crate::utils; diff --git a/src/lsps2/client.rs b/src/lsps2/client.rs index 7afc4cb..436be22 100644 --- a/src/lsps2/client.rs +++ b/src/lsps2/client.rs @@ -10,12 +10,10 @@ //! Contains the main LSPS2 client object, [`LSPS2ClientHandler`]. use crate::events::EventQueue; -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; use crate::lsps2::event::LSPS2ClientEvent; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; -use crate::utils; use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::ln::msgs::{ErrorAction, LightningError}; diff --git a/src/lsps2/service.rs b/src/lsps2/service.rs index 9c0e054..caad820 100644 --- a/src/lsps2/service.rs +++ b/src/lsps2/service.rs @@ -10,8 +10,7 @@ //! Contains the main LSPS2 server-side object, [`LSPS2ServiceHandler`]. use crate::events::EventQueue; -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{LSPSMessage, RequestId}; +use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; use crate::lsps2::event::LSPS2ServiceEvent; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; use crate::prelude::{HashMap, String, ToString, Vec}; diff --git a/src/manager.rs b/src/manager.rs index 2991297..3b2f802 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,7 +1,9 @@ use crate::events::{Event, EventQueue}; -use crate::lsps0::message_handler::LSPS0MessageHandler; -use crate::lsps0::message_handler::ProtocolMessageHandler; -use crate::lsps0::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; +use crate::lsps0::client::LSPS0ClientHandler; +use crate::lsps0::msgs::{ + LSPS0Message, LSPSMessage, ProtocolMessageHandler, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID, +}; +use crate::lsps0::service::LSPS0ServiceHandler; #[cfg(lsps1)] use crate::lsps1::client::{LSPS1ClientConfig, LSPS1ClientHandler}; @@ -89,7 +91,8 @@ pub struct LiquidityManager< pending_messages: Arc>>, pending_events: Arc, request_id_to_method_map: Mutex>, - lsps0_message_handler: LSPS0MessageHandler, + lsps0_client_handler: LSPS0ClientHandler, + lsps0_service_handler: Option, #[cfg(lsps1)] lsps1_service_handler: Option>, #[cfg(lsps1)] @@ -123,11 +126,14 @@ where { let pending_messages = Arc::new(Mutex::new(vec![])); let pending_events = Arc::new(EventQueue::new()); - let lsps0_message_handler = LSPS0MessageHandler::new( - entropy_source.clone().clone(), - vec![], - Arc::clone(&pending_messages), - ); + let lsps0_client_handler = + LSPS0ClientHandler::new(entropy_source.clone(), Arc::clone(&pending_messages)); + + let lsps0_service_handler = if service_config.is_some() { + Some(LSPS0ServiceHandler::new(vec![], Arc::clone(&pending_messages))) + } else { + None + }; let lsps2_client_handler = client_config.as_ref().and_then(|config| { config.lsps2_client_config.map(|config| { @@ -182,7 +188,8 @@ where { pending_messages, pending_events, request_id_to_method_map: Mutex::new(HashMap::new()), - lsps0_message_handler, + lsps0_client_handler, + lsps0_service_handler, #[cfg(lsps1)] lsps1_client_handler, #[cfg(lsps1)] @@ -196,9 +203,14 @@ where { } } - /// Returns a reference to the LSPS0 message handler. - pub fn lsps0_message_handler(&self) -> &LSPS0MessageHandler { - &self.lsps0_message_handler + /// Returns a reference to the LSPS0 client-side handler. + pub fn lsps0_client_handler(&self) -> &LSPS0ClientHandler { + &self.lsps0_client_handler + } + + /// Returns a reference to the LSPS0 server-side handler. + pub fn lsps0_service_handler(&self) -> Option<&LSPS0ServiceHandler> { + self.lsps0_service_handler.as_ref() } /// Returns a reference to the LSPS1 client-side handler. @@ -279,8 +291,18 @@ where { LSPSMessage::Invalid => { return Err(LightningError { err: format!("{} did not understand a message we previously sent, maybe they don't support a protocol we are trying to use?", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Error)}); } - LSPSMessage::LSPS0(msg) => { - self.lsps0_message_handler.handle_message(msg, sender_node_id)?; + LSPSMessage::LSPS0(msg @ LSPS0Message::Response(..)) => { + self.lsps0_client_handler.handle_message(msg, sender_node_id)?; + } + LSPSMessage::LSPS0(msg @ LSPS0Message::Request(..)) => { + match &self.lsps0_service_handler { + Some(lsps0_service_handler) => { + lsps0_service_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS0 request message without LSPS0 service handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + } } #[cfg(lsps1)] LSPSMessage::LSPS1(msg @ LSPS1Message::Response(..)) => match &self.lsps1_client_handler { From 1f11274bb277cbf101dda99eb77ca71ed381002d Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 15 Dec 2023 11:40:42 +0100 Subject: [PATCH 17/18] Expose `ListProtocolsResponse` event --- src/events.rs | 3 +++ src/lsps0/client.rs | 29 +++++++++++++++++++++++------ src/lsps0/event.rs | 25 +++++++++++++++++++++++++ src/lsps0/mod.rs | 1 + src/lsps2/client.rs | 5 ++--- src/manager.rs | 7 +++++-- 6 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/lsps0/event.rs diff --git a/src/events.rs b/src/events.rs index 8665e89..72ab850 100644 --- a/src/events.rs +++ b/src/events.rs @@ -15,6 +15,7 @@ //! //! [`LiquidityManager::get_and_clear_pending_events`]: crate::LiquidityManager::get_and_clear_pending_events +use crate::lsps0; #[cfg(lsps1)] use crate::lsps1; use crate::lsps2; @@ -78,6 +79,8 @@ impl EventQueue { /// An event which you should probably take some action in response to. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { + /// An LSPS0 client event. + LSPS0Client(lsps0::event::LSPS0ClientEvent), /// An LSPS1 (Channel Request) client event. #[cfg(lsps1)] LSPS1Client(lsps1::event::LSPS1ClientEvent), diff --git a/src/lsps0/client.rs b/src/lsps0/client.rs index 550e3a0..3f5679d 100644 --- a/src/lsps0/client.rs +++ b/src/lsps0/client.rs @@ -4,6 +4,8 @@ //! specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0) for more //! information. +use crate::events::{Event, EventQueue}; +use crate::lsps0::event::LSPS0ClientEvent; use crate::lsps0::msgs::{ LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, ListProtocolsResponse, ProtocolMessageHandler, ResponseError, @@ -27,6 +29,7 @@ where { entropy_source: ES, pending_messages: Arc>>, + pending_events: Arc, } impl LSPS0ClientHandler @@ -36,8 +39,9 @@ where /// Returns a new instance of [`LSPS0ClientHandler`]. pub(crate) fn new( entropy_source: ES, pending_messages: Arc>>, + pending_events: Arc, ) -> Self { - Self { entropy_source, pending_messages } + Self { entropy_source, pending_messages, pending_events } } /// Calls LSPS0's `list_protocols`. @@ -59,10 +63,18 @@ where } fn handle_response( - &self, response: LSPS0Response, _counterparty_node_id: &PublicKey, + &self, response: LSPS0Response, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { match response { - LSPS0Response::ListProtocols(ListProtocolsResponse { protocols: _ }) => Ok(()), + LSPS0Response::ListProtocols(ListProtocolsResponse { protocols }) => { + self.pending_events.enqueue(Event::LSPS0Client( + LSPS0ClientEvent::ListProtocolsResponse { + counterparty_node_id: *counterparty_node_id, + protocols, + }, + )); + Ok(()) + } LSPS0Response::ListProtocolsError(ResponseError { code, message, data, .. }) => { Err(LightningError { err: format!( @@ -121,9 +133,14 @@ mod tests { #[test] fn test_list_protocols() { let pending_messages = Arc::new(Mutex::new(vec![])); - - let lsps0_handler = - Arc::new(LSPS0ClientHandler::new(Arc::new(TestEntropy {}), pending_messages.clone())); + let entropy_source = Arc::new(TestEntropy {}); + let event_queue = Arc::new(EventQueue::new()); + + let lsps0_handler = Arc::new(LSPS0ClientHandler::new( + entropy_source, + Arc::clone(&pending_messages), + event_queue, + )); let counterparty_node_id = utils::parse_pubkey( "027100442c3b79f606f80f322d98d499eefcb060599efc5d4ecb00209c2cb54190", diff --git a/src/lsps0/event.rs b/src/lsps0/event.rs new file mode 100644 index 0000000..163114d --- /dev/null +++ b/src/lsps0/event.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Contains LSPS0 event types + +use crate::prelude::Vec; +use bitcoin::secp256k1::PublicKey; + +/// An event which an LSPS0 client may want to take some action in response to. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS0ClientEvent { + /// Information from the LSP about the protocols they support. + ListProtocolsResponse { + /// The node id of the LSP. + counterparty_node_id: PublicKey, + /// A list of supported protocols. + protocols: Vec, + }, +} diff --git a/src/lsps0/mod.rs b/src/lsps0/mod.rs index e8c701c..f4f8a56 100644 --- a/src/lsps0/mod.rs +++ b/src/lsps0/mod.rs @@ -10,5 +10,6 @@ //! Types and primitives that implement the LSPS0: Transport Layer specification. pub mod client; +pub mod event; pub mod msgs; pub mod service; diff --git a/src/lsps2/client.rs b/src/lsps2/client.rs index 436be22..b37c494 100644 --- a/src/lsps2/client.rs +++ b/src/lsps2/client.rs @@ -9,12 +9,11 @@ //! Contains the main LSPS2 client object, [`LSPS2ClientHandler`]. -use crate::events::EventQueue; -use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; +use crate::events::{Event, EventQueue}; +use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId, ResponseError}; use crate::lsps2::event::LSPS2ClientEvent; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; -use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::ln::msgs::{ErrorAction, LightningError}; use lightning::ln::peer_handler::APeerManager; diff --git a/src/manager.rs b/src/manager.rs index 3b2f802..53205fe 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -126,8 +126,11 @@ where { let pending_messages = Arc::new(Mutex::new(vec![])); let pending_events = Arc::new(EventQueue::new()); - let lsps0_client_handler = - LSPS0ClientHandler::new(entropy_source.clone(), Arc::clone(&pending_messages)); + let lsps0_client_handler = LSPS0ClientHandler::new( + entropy_source.clone(), + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + ); let lsps0_service_handler = if service_config.is_some() { Some(LSPS0ServiceHandler::new(vec![], Arc::clone(&pending_messages))) From 9861f57cd148afd06d7624b9c33f7b176f61b958 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 15 Dec 2023 15:29:31 +0100 Subject: [PATCH 18/18] Introduce `MessageQueue` trait to DRY up enqueing and PM triggering --- src/lib.rs | 3 ++ src/lsps0/client.rs | 46 ++++++++---------- src/lsps0/service.rs | 42 +++++++++-------- src/lsps1/client.rs | 102 +++++++++++++--------------------------- src/lsps1/service.rs | 66 +++++++++----------------- src/lsps2/client.rs | 108 +++++++++++++++---------------------------- src/lsps2/service.rs | 81 ++++++++++++-------------------- src/manager.rs | 68 ++++++++++++--------------- src/message_queue.rs | 66 ++++++++++++++++++++++++++ src/tests/mod.rs | 1 + src/tests/utils.rs | 39 ++++++++++++++++ 11 files changed, 300 insertions(+), 322 deletions(-) create mode 100644 src/message_queue.rs create mode 100644 src/tests/mod.rs create mode 100644 src/tests/utils.rs diff --git a/src/lib.rs b/src/lib.rs index e7092d7..58689dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,10 @@ pub mod lsps0; pub mod lsps1; pub mod lsps2; mod manager; +pub mod message_queue; mod sync; +#[cfg(test)] +mod tests; mod utils; pub use manager::{LiquidityClientConfig, LiquidityManager, LiquidityServiceConfig}; diff --git a/src/lsps0/client.rs b/src/lsps0/client.rs index 3f5679d..a8df49e 100644 --- a/src/lsps0/client.rs +++ b/src/lsps0/client.rs @@ -7,11 +7,11 @@ use crate::events::{Event, EventQueue}; use crate::lsps0::event::LSPS0ClientEvent; use crate::lsps0::msgs::{ - LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsRequest, - ListProtocolsResponse, ProtocolMessageHandler, ResponseError, + LSPS0Message, LSPS0Request, LSPS0Response, ListProtocolsRequest, ListProtocolsResponse, + ProtocolMessageHandler, ResponseError, }; -use crate::prelude::Vec; -use crate::sync::{Arc, Mutex}; +use crate::message_queue::MessageQueue; +use crate::sync::Arc; use crate::utils; use lightning::ln::msgs::{ErrorAction, LightningError}; @@ -23,23 +23,24 @@ use bitcoin::secp256k1::PublicKey; use core::ops::Deref; /// A message handler capable of sending and handling LSPS0 messages. -pub struct LSPS0ClientHandler +pub struct LSPS0ClientHandler where ES::Target: EntropySource, + MQ::Target: MessageQueue, { entropy_source: ES, - pending_messages: Arc>>, + pending_messages: MQ, pending_events: Arc, } -impl LSPS0ClientHandler +impl LSPS0ClientHandler where ES::Target: EntropySource, + MQ::Target: MessageQueue, { /// Returns a new instance of [`LSPS0ClientHandler`]. pub(crate) fn new( - entropy_source: ES, pending_messages: Arc>>, - pending_events: Arc, + entropy_source: ES, pending_messages: MQ, pending_events: Arc, ) -> Self { Self { entropy_source, pending_messages, pending_events } } @@ -49,17 +50,13 @@ where /// Please refer to the [LSPS0 /// specifcation](https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS0#lsps-specification-support-query) /// for more information. - pub fn list_protocols(&self, counterparty_node_id: PublicKey) { + pub fn list_protocols(&self, counterparty_node_id: &PublicKey) { let msg = LSPS0Message::Request( utils::generate_request_id(&self.entropy_source), LSPS0Request::ListProtocols(ListProtocolsRequest {}), ); - self.enqueue_message(counterparty_node_id, msg); - } - - fn enqueue_message(&self, counterparty_node_id: PublicKey, message: LSPS0Message) { - self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); + self.pending_messages.enqueue(counterparty_node_id, msg.into()); } fn handle_response( @@ -88,9 +85,10 @@ where } } -impl ProtocolMessageHandler for LSPS0ClientHandler +impl ProtocolMessageHandler for LSPS0ClientHandler where ES::Target: EntropySource, + MQ::Target: MessageQueue, { type ProtocolMessage = LSPS0Message; const PROTOCOL_NUMBER: Option = None; @@ -119,20 +117,14 @@ mod tests { use alloc::string::ToString; use alloc::sync::Arc; - use crate::lsps0::msgs::RequestId; + use crate::lsps0::msgs::{LSPSMessage, RequestId}; + use crate::tests::utils::{TestEntropy, TestMessageQueue}; use super::*; - struct TestEntropy {} - impl EntropySource for TestEntropy { - fn get_secure_random_bytes(&self) -> [u8; 32] { - [0; 32] - } - } - #[test] fn test_list_protocols() { - let pending_messages = Arc::new(Mutex::new(vec![])); + let pending_messages = Arc::new(TestMessageQueue::new()); let entropy_source = Arc::new(TestEntropy {}); let event_queue = Arc::new(EventQueue::new()); @@ -147,8 +139,8 @@ mod tests { ) .unwrap(); - lsps0_handler.list_protocols(counterparty_node_id); - let pending_messages = pending_messages.lock().unwrap(); + lsps0_handler.list_protocols(&counterparty_node_id); + let pending_messages = pending_messages.get_and_clear_pending_msgs(); assert_eq!(pending_messages.len(), 1); diff --git a/src/lsps0/service.rs b/src/lsps0/service.rs index 8fdf47e..403033e 100644 --- a/src/lsps0/service.rs +++ b/src/lsps0/service.rs @@ -14,35 +14,37 @@ //! information. use crate::lsps0::msgs::{ - LSPS0Message, LSPS0Request, LSPS0Response, LSPSMessage, ListProtocolsResponse, - ProtocolMessageHandler, RequestId, + LSPS0Message, LSPS0Request, LSPS0Response, ListProtocolsResponse, ProtocolMessageHandler, + RequestId, }; +use crate::message_queue::MessageQueue; use crate::prelude::Vec; -use crate::sync::{Arc, Mutex}; use lightning::ln::msgs::{ErrorAction, LightningError}; use lightning::util::logger::Level; use bitcoin::secp256k1::PublicKey; +use core::ops::Deref; + /// The main server-side object allowing to send and receive LSPS0 messages. -pub struct LSPS0ServiceHandler { - pending_messages: Arc>>, +pub struct LSPS0ServiceHandler +where + MQ::Target: MessageQueue, +{ + pending_messages: MQ, protocols: Vec, } -impl LSPS0ServiceHandler { +impl LSPS0ServiceHandler +where + MQ::Target: MessageQueue, +{ /// Returns a new instance of [`LSPS0ServiceHandler`]. - pub(crate) fn new( - protocols: Vec, pending_messages: Arc>>, - ) -> Self { + pub(crate) fn new(protocols: Vec, pending_messages: MQ) -> Self { Self { protocols, pending_messages } } - fn enqueue_message(&self, counterparty_node_id: PublicKey, message: LSPS0Message) { - self.pending_messages.lock().unwrap().push((counterparty_node_id, message.into())); - } - fn handle_request( &self, request_id: RequestId, request: LSPS0Request, counterparty_node_id: &PublicKey, ) -> Result<(), lightning::ln::msgs::LightningError> { @@ -54,14 +56,17 @@ impl LSPS0ServiceHandler { protocols: self.protocols.clone(), }), ); - self.enqueue_message(*counterparty_node_id, msg); + self.pending_messages.enqueue(counterparty_node_id, msg.into()); Ok(()) } } } } -impl ProtocolMessageHandler for LSPS0ServiceHandler { +impl ProtocolMessageHandler for LSPS0ServiceHandler +where + MQ::Target: MessageQueue, +{ type ProtocolMessage = LSPS0Message; const PROTOCOL_NUMBER: Option = None; @@ -86,7 +91,8 @@ impl ProtocolMessageHandler for LSPS0ServiceHandler { #[cfg(test)] mod tests { - use crate::lsps0::msgs::ListProtocolsRequest; + use crate::lsps0::msgs::{LSPSMessage, ListProtocolsRequest}; + use crate::tests::utils::TestMessageQueue; use crate::utils; use alloc::string::ToString; use alloc::sync::Arc; @@ -96,7 +102,7 @@ mod tests { #[test] fn test_handle_list_protocols_request() { let protocols: Vec = vec![]; - let pending_messages = Arc::new(Mutex::new(vec![])); + let pending_messages = Arc::new(TestMessageQueue::new()); let lsps0_handler = Arc::new(LSPS0ServiceHandler::new(protocols, pending_messages.clone())); @@ -110,7 +116,7 @@ mod tests { .unwrap(); lsps0_handler.handle_message(list_protocols_request, &counterparty_node_id).unwrap(); - let pending_messages = pending_messages.lock().unwrap(); + let pending_messages = pending_messages.get_and_clear_pending_msgs(); assert_eq!(pending_messages.len(), 1); diff --git a/src/lsps1/client.rs b/src/lsps1/client.rs index 087ca9d..6b0081f 100644 --- a/src/lsps1/client.rs +++ b/src/lsps1/client.rs @@ -16,9 +16,10 @@ use super::msgs::{ OrderParams, }; use super::utils::is_valid; +use crate::message_queue::MessageQueue; use crate::events::EventQueue; -use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; +use crate::lsps0::msgs::{ProtocolMessageHandler, RequestId}; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; use crate::{events::Event, lsps0::msgs::ResponseError}; @@ -26,7 +27,6 @@ use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::chain::Filter; use lightning::ln::channelmanager::AChannelManager; use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::ln::peer_handler::APeerManager; use lightning::sign::EntropySource; use lightning::util::errors::APIError; use lightning::util::logger::Level; @@ -216,40 +216,37 @@ impl PeerState { } /// The main object allowing to send and receive LSPS1 messages. -pub struct LSPS1ClientHandler +pub struct LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, { entropy_source: ES, channel_manager: CM, - peer_manager: Mutex>, chain_source: Option, - pending_messages: Arc>>, + pending_messages: MQ, pending_events: Arc, per_peer_state: RwLock>>, config: LSPS1ClientConfig, } -impl LSPS1ClientHandler +impl LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, ES::Target: EntropySource, { pub(crate) fn new( - entropy_source: ES, pending_messages: Arc>>, - pending_events: Arc, channel_manager: CM, chain_source: Option, - config: LSPS1ClientConfig, + entropy_source: ES, pending_messages: MQ, pending_events: Arc, + channel_manager: CM, chain_source: Option, config: LSPS1ClientConfig, ) -> Self { Self { entropy_source, channel_manager, - peer_manager: Mutex::new(None), chain_source, pending_messages, pending_events, @@ -258,20 +255,6 @@ where } } - /// Set a [`PeerManager`] reference for the message handler. - /// - /// This allows the message handler to wake the [`PeerManager`] by calling - /// [`PeerManager::process_events`] after enqueing messages to be sent. - /// - /// Without this the messages will be sent based on whatever polling interval - /// your background processor uses. - /// - /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager - /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events - pub fn set_peer_manager(&self, peer_manager: PM) { - *self.peer_manager.lock().unwrap() = Some(peer_manager); - } - fn request_for_info(&self, counterparty_node_id: PublicKey, channel_id: u128) { let channel = InboundCRChannel::new(channel_id); @@ -285,17 +268,10 @@ where let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - counterparty_node_id, - LSPS1Message::Request(request_id, LSPS1Request::GetInfo(GetInfoRequest {})).into(), - )); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + &counterparty_node_id, + LSPS1Message::Request(request_id, LSPS1Request::GetInfo(GetInfoRequest {})).into(), + ); } fn handle_get_info_response( @@ -386,20 +362,14 @@ where let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - *counterparty_node_id, - LSPS1Message::Request( - request_id, - LSPS1Request::CreateOrder(CreateOrderRequest { order, version }), - ) - .into(), - )); - } - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + counterparty_node_id, + LSPS1Message::Request( + request_id, + LSPS1Request::CreateOrder(CreateOrderRequest { order, version }), + ) + .into(), + ); } None => { return Err(APIError::APIMisuseError { @@ -537,22 +507,14 @@ where let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - *counterparty_node_id, - LSPS1Message::Request( - request_id, - LSPS1Request::GetOrder(GetOrderRequest { - order_id: order_id.clone(), - }), - ) - .into(), - )); - } - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + counterparty_node_id, + LSPS1Message::Request( + request_id, + LSPS1Request::GetOrder(GetOrderRequest { order_id: order_id.clone() }), + ) + .into(), + ); } else { return Err(APIError::APIMisuseError { err: format!("Channel with id {} not found", channel_id), @@ -647,12 +609,12 @@ where } } -impl ProtocolMessageHandler - for LSPS1ClientHandler +impl ProtocolMessageHandler + for LSPS1ClientHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, { type ProtocolMessage = LSPS1Message; diff --git a/src/lsps1/service.rs b/src/lsps1/service.rs index 564aef8..8211046 100644 --- a/src/lsps1/service.rs +++ b/src/lsps1/service.rs @@ -17,10 +17,11 @@ use super::msgs::{ LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, }; use super::utils::is_valid; +use crate::message_queue::MessageQueue; use crate::events::EventQueue; -use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; -use crate::prelude::{HashMap, String, ToString, Vec}; +use crate::lsps0::msgs::{ProtocolMessageHandler, RequestId}; +use crate::prelude::{HashMap, String, ToString}; use crate::sync::{Arc, Mutex, RwLock}; use crate::utils; use crate::{events::Event, lsps0::msgs::ResponseError}; @@ -28,7 +29,6 @@ use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::chain::Filter; use lightning::ln::channelmanager::AChannelManager; use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::ln::peer_handler::APeerManager; use lightning::sign::EntropySource; use lightning::util::errors::APIError; use lightning::util::logger::Level; @@ -136,40 +136,37 @@ impl PeerState { } /// The main object allowing to send and receive LSPS1 messages. -pub struct LSPS1ServiceHandler +pub struct LSPS1ServiceHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, { entropy_source: ES, channel_manager: CM, - peer_manager: Mutex>, chain_source: Option, - pending_messages: Arc>>, + pending_messages: MQ, pending_events: Arc, per_peer_state: RwLock>>, config: LSPS1ServiceConfig, } -impl LSPS1ServiceHandler +impl LSPS1ServiceHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, ES::Target: EntropySource, { pub(crate) fn new( - entropy_source: ES, pending_messages: Arc>>, - pending_events: Arc, channel_manager: CM, chain_source: Option, - config: LSPS1ServiceConfig, + entropy_source: ES, pending_messages: MQ, pending_events: Arc, + channel_manager: CM, chain_source: Option, config: LSPS1ServiceConfig, ) -> Self { Self { entropy_source, channel_manager, - peer_manager: Mutex::new(None), chain_source, pending_messages, pending_events, @@ -178,20 +175,6 @@ where } } - /// Set a [`PeerManager`] reference for the message handler. - /// - /// This allows the message handler to wake the [`PeerManager`] by calling - /// [`PeerManager::process_events`] after enqueing messages to be sent. - /// - /// Without this the messages will be sent based on whatever polling interval - /// your background processor uses. - /// - /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager - /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events - pub fn set_peer_manager(&self, peer_manager: PM) { - *self.peer_manager.lock().unwrap() = Some(peer_manager); - } - fn handle_get_info_request( &self, request_id: RequestId, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { @@ -209,7 +192,7 @@ where .unwrap(), }; - self.enqueue_response(*counterparty_node_id, request_id, LSPS1Response::GetInfo(response)); + self.enqueue_response(counterparty_node_id, request_id, LSPS1Response::GetInfo(response)); Ok(()) } @@ -218,7 +201,7 @@ where ) -> Result<(), LightningError> { if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS1Response::CreateOrderError(ResponseError { code: LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, @@ -234,7 +217,7 @@ where if !is_valid(¶ms.order, &self.config.options_supported.as_ref().unwrap()) { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS1Response::CreateOrderError(ResponseError { code: LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, @@ -295,7 +278,7 @@ where peer_state_lock.insert_outbound_channel(order_id.clone(), channel); self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS1Response::CreateOrder(CreateOrderResponse { order: params.order, @@ -397,7 +380,7 @@ where let config = &outbound_channel.config; self.enqueue_response( - counterparty_node_id, + &counterparty_node_id, request_id, LSPS1Response::GetOrder(GetOrderResponse { response: CreateOrderResponse { @@ -427,17 +410,10 @@ where } fn enqueue_response( - &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS1Response, + &self, counterparty_node_id: &PublicKey, request_id: RequestId, response: LSPS1Response, ) { - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages - .push((counterparty_node_id, LSPS1Message::Response(request_id, response).into())); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages + .enqueue(counterparty_node_id, LSPS1Message::Response(request_id, response).into()); } fn generate_order_id(&self) -> OrderId { @@ -446,12 +422,12 @@ where } } -impl ProtocolMessageHandler - for LSPS1ServiceHandler +impl ProtocolMessageHandler + for LSPS1ServiceHandler where ES::Target: EntropySource, CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, C::Target: Filter, { type ProtocolMessage = LSPS1Message; diff --git a/src/lsps2/client.rs b/src/lsps2/client.rs index b37c494..dfb0be4 100644 --- a/src/lsps2/client.rs +++ b/src/lsps2/client.rs @@ -10,13 +10,13 @@ //! Contains the main LSPS2 client object, [`LSPS2ClientHandler`]. use crate::events::{Event, EventQueue}; -use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId, ResponseError}; +use crate::lsps0::msgs::{ProtocolMessageHandler, RequestId, ResponseError}; use crate::lsps2::event::LSPS2ClientEvent; +use crate::message_queue::MessageQueue; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::ln::peer_handler::APeerManager; use lightning::sign::EntropySource; use lightning::util::errors::APIError; use lightning::util::logger::Level; @@ -200,53 +200,37 @@ impl PeerState { } /// The main object allowing to send and receive LSPS2 messages. -pub struct LSPS2ClientHandler +pub struct LSPS2ClientHandler where ES::Target: EntropySource, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { entropy_source: ES, - peer_manager: Mutex>, - pending_messages: Arc>>, + pending_messages: MQ, pending_events: Arc, per_peer_state: RwLock>>, _config: LSPS2ClientConfig, } -impl LSPS2ClientHandler +impl LSPS2ClientHandler where ES::Target: EntropySource, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { /// Constructs an `LSPS2ClientHandler`. pub(crate) fn new( - entropy_source: ES, pending_messages: Arc>>, - pending_events: Arc, config: LSPS2ClientConfig, + entropy_source: ES, pending_messages: MQ, pending_events: Arc, + config: LSPS2ClientConfig, ) -> Self { Self { entropy_source, pending_messages, pending_events, per_peer_state: RwLock::new(HashMap::new()), - peer_manager: Mutex::new(None), _config: config, } } - /// Set a [`PeerManager`] reference for the message handler. - /// - /// This allows the message handler to wake the [`PeerManager`] by calling - /// [`PeerManager::process_events`] after enqueing messages to be sent. - /// - /// Without this the messages will be sent based on whatever polling interval - /// your background processor uses. - /// - /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager - /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events - pub fn set_peer_manager(&self, peer_manager: PM) { - *self.peer_manager.lock().unwrap() = Some(peer_manager); - } - /// Initiate the creation of an invoice that when paid will open a channel /// with enough inbound liquidity to be able to receive the payment. /// @@ -277,18 +261,11 @@ where let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state_lock.insert_request(request_id.clone(), jit_channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - counterparty_node_id, - LSPS2Message::Request(request_id, LSPS2Request::GetVersions(GetVersionsRequest {})) - .into(), - )); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + &counterparty_node_id, + LSPS2Message::Request(request_id, LSPS2Request::GetVersions(GetVersionsRequest {})) + .into(), + ); } /// Used by client to confirm which channel parameters to use for the JIT Channel buy request. @@ -321,24 +298,18 @@ where let payment_size_msat = jit_channel.config.payment_size_msat; peer_state.insert_request(request_id.clone(), jit_channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - counterparty_node_id, - LSPS2Message::Request( - request_id, - LSPS2Request::Buy(BuyRequest { - version, - opening_fee_params, - payment_size_msat, - }), - ) - .into(), - )); - } - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + &counterparty_node_id, + LSPS2Message::Request( + request_id, + LSPS2Request::Buy(BuyRequest { + version, + opening_fee_params, + payment_size_msat, + }), + ) + .into(), + ); } else { return Err(APIError::APIMisuseError { err: format!("Channel with id {} not found", jit_channel_id), @@ -403,21 +374,14 @@ where let request_id = crate::utils::generate_request_id(&self.entropy_source); peer_state.insert_request(request_id.clone(), jit_channel_id); - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(( - *counterparty_node_id, - LSPS2Message::Request( - request_id, - LSPS2Request::GetInfo(GetInfoRequest { version, token }), - ) - .into(), - )); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages.enqueue( + counterparty_node_id, + LSPS2Message::Request( + request_id, + LSPS2Request::GetInfo(GetInfoRequest { version, token }), + ) + .into(), + ); } None => { return Err(LightningError { @@ -628,10 +592,10 @@ where } } -impl ProtocolMessageHandler for LSPS2ClientHandler +impl ProtocolMessageHandler for LSPS2ClientHandler where ES::Target: EntropySource, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { type ProtocolMessage = LSPS2Message; const PROTOCOL_NUMBER: Option = Some(2); diff --git a/src/lsps2/service.rs b/src/lsps2/service.rs index caad820..706d8ab 100644 --- a/src/lsps2/service.rs +++ b/src/lsps2/service.rs @@ -10,16 +10,16 @@ //! Contains the main LSPS2 server-side object, [`LSPS2ServiceHandler`]. use crate::events::EventQueue; -use crate::lsps0::msgs::{LSPSMessage, ProtocolMessageHandler, RequestId}; +use crate::lsps0::msgs::{ProtocolMessageHandler, RequestId}; use crate::lsps2::event::LSPS2ServiceEvent; use crate::lsps2::utils::{compute_opening_fee, is_valid_opening_fee_params}; +use crate::message_queue::MessageQueue; use crate::prelude::{HashMap, String, ToString, Vec}; use crate::sync::{Arc, Mutex, RwLock}; use crate::{events::Event, lsps0::msgs::ResponseError}; use lightning::ln::channelmanager::{AChannelManager, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; -use lightning::ln::peer_handler::APeerManager; use lightning::ln::ChannelId; use lightning::util::errors::APIError; use lightning::util::logger::Level; @@ -260,66 +260,50 @@ impl PeerState { } /// The main object allowing to send and receive LSPS2 messages. -pub struct LSPS2ServiceHandler +pub struct LSPS2ServiceHandler where CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { - peer_manager: Mutex>, channel_manager: CM, - pending_messages: Arc>>, + pending_messages: MQ, pending_events: Arc, per_peer_state: RwLock>>, peer_by_scid: RwLock>, config: LSPS2ServiceConfig, } -impl LSPS2ServiceHandler +impl LSPS2ServiceHandler where CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { /// Constructs a `LSPS2ServiceHandler`. pub(crate) fn new( - pending_messages: Arc>>, - pending_events: Arc, channel_manager: CM, config: LSPS2ServiceConfig, + pending_messages: MQ, pending_events: Arc, channel_manager: CM, + config: LSPS2ServiceConfig, ) -> Self { Self { pending_messages, pending_events, per_peer_state: RwLock::new(HashMap::new()), peer_by_scid: RwLock::new(HashMap::new()), - peer_manager: Mutex::new(None), channel_manager, config, } } - /// Set a [`PeerManager`] reference for the message handler. - /// - /// This allows the message handler to wake the [`PeerManager`] by calling - /// [`PeerManager::process_events`] after enqueing messages to be sent. - /// - /// Without this the messages will be sent based on whatever polling interval - /// your background processor uses. - /// - /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager - /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events - pub fn set_peer_manager(&self, peer_manager: PM) { - *self.peer_manager.lock().unwrap() = Some(peer_manager); - } - /// Used by LSP to inform a client requesting a JIT Channel the token they used is invalid. /// /// Should be called in response to receiving a [`LSPS2ServiceEvent::GetInfo`] event. /// /// [`LSPS2ServiceEvent::GetInfo`]: crate::lsps2::event::LSPS2ServiceEvent::GetInfo pub fn invalid_token_provided( - &self, counterparty_node_id: PublicKey, request_id: RequestId, + &self, counterparty_node_id: &PublicKey, request_id: RequestId, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(&counterparty_node_id) { + match outer_state_lock.get(counterparty_node_id) { Some(inner_state_lock) => { let mut peer_state = inner_state_lock.lock().unwrap(); @@ -353,12 +337,12 @@ where /// /// [`LSPS2ServiceEvent::GetInfo`]: crate::lsps2::event::LSPS2ServiceEvent::GetInfo pub fn opening_fee_params_generated( - &self, counterparty_node_id: PublicKey, request_id: RequestId, + &self, counterparty_node_id: &PublicKey, request_id: RequestId, opening_fee_params_menu: Vec, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(&counterparty_node_id) { + match outer_state_lock.get(counterparty_node_id) { Some(inner_state_lock) => { let mut peer_state = inner_state_lock.lock().unwrap(); @@ -397,12 +381,12 @@ where /// /// [`LSPS2ServiceEvent::BuyRequest`]: crate::lsps2::event::LSPS2ServiceEvent::BuyRequest pub fn invoice_parameters_generated( - &self, counterparty_node_id: PublicKey, request_id: RequestId, scid: u64, + &self, counterparty_node_id: &PublicKey, request_id: RequestId, scid: u64, cltv_expiry_delta: u32, client_trusts_lsp: bool, ) -> Result<(), APIError> { let outer_state_lock = self.per_peer_state.read().unwrap(); - match outer_state_lock.get(&counterparty_node_id) { + match outer_state_lock.get(counterparty_node_id) { Some(inner_state_lock) => { let mut peer_state = inner_state_lock.lock().unwrap(); @@ -410,7 +394,7 @@ where Some(LSPS2Request::Buy(buy_request)) => { { let mut peer_by_scid = self.peer_by_scid.write().unwrap(); - peer_by_scid.insert(scid, counterparty_node_id); + peer_by_scid.insert(scid, *counterparty_node_id); } let outbound_jit_channel = OutboundJITChannel::new( @@ -566,17 +550,10 @@ where } fn enqueue_response( - &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS2Response, + &self, counterparty_node_id: &PublicKey, request_id: RequestId, response: LSPS2Response, ) { - { - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages - .push((counterparty_node_id, LSPS2Message::Response(request_id, response).into())); - } - - if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { - peer_manager.as_ref().process_events(); - } + self.pending_messages + .enqueue(counterparty_node_id, LSPS2Message::Response(request_id, response).into()); } fn enqueue_event(&self, event: Event) { @@ -587,7 +564,7 @@ where &self, request_id: RequestId, counterparty_node_id: &PublicKey, ) -> Result<(), LightningError> { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::GetVersions(GetVersionsResponse { versions: SUPPORTED_SPEC_VERSIONS.to_vec(), @@ -601,7 +578,7 @@ where ) -> Result<(), LightningError> { if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::GetInfoError(ResponseError { code: LSPS2_GET_INFO_REQUEST_INVALID_VERSION_ERROR_CODE, @@ -637,7 +614,7 @@ where ) -> Result<(), LightningError> { if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_INVALID_VERSION_ERROR_CODE, @@ -654,7 +631,7 @@ where if let Some(payment_size_msat) = params.payment_size_msat { if payment_size_msat < self.config.min_payment_size_msat { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE, @@ -671,7 +648,7 @@ where if payment_size_msat > self.config.max_payment_size_msat { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE, @@ -694,7 +671,7 @@ where Some(opening_fee) => { if opening_fee >= payment_size_msat { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE, @@ -711,7 +688,7 @@ where } None => { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE, @@ -731,7 +708,7 @@ where if !is_valid_opening_fee_params(¶ms.opening_fee_params, &self.config.promise_secret) { self.enqueue_response( - *counterparty_node_id, + counterparty_node_id, request_id, LSPS2Response::BuyError(ResponseError { code: LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE, @@ -765,10 +742,10 @@ where } } -impl ProtocolMessageHandler for LSPS2ServiceHandler +impl ProtocolMessageHandler for LSPS2ServiceHandler where CM::Target: AChannelManager, - PM::Target: APeerManager, + MQ::Target: MessageQueue, { type ProtocolMessage = LSPS2Message; const PROTOCOL_NUMBER: Option = Some(2); diff --git a/src/manager.rs b/src/manager.rs index 53205fe..355e88f 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -4,6 +4,7 @@ use crate::lsps0::msgs::{ LSPS0Message, LSPSMessage, ProtocolMessageHandler, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID, }; use crate::lsps0::service::LSPS0ServiceHandler; +use crate::message_queue::{DefaultMessageQueue, MessageQueue}; #[cfg(lsps1)] use crate::lsps1::client::{LSPS1ClientConfig, LSPS1ClientHandler}; @@ -88,17 +89,17 @@ pub struct LiquidityManager< PM::Target: APeerManager, C::Target: Filter, { - pending_messages: Arc>>, + pending_messages: Arc>, pending_events: Arc, request_id_to_method_map: Mutex>, - lsps0_client_handler: LSPS0ClientHandler, - lsps0_service_handler: Option, + lsps0_client_handler: LSPS0ClientHandler>>, + lsps0_service_handler: Option>>>, #[cfg(lsps1)] - lsps1_service_handler: Option>, + lsps1_service_handler: Option>, C>>, #[cfg(lsps1)] - lsps1_client_handler: Option>, - lsps2_service_handler: Option>, - lsps2_client_handler: Option>, + lsps1_client_handler: Option>, C>>, + lsps2_service_handler: Option>>>, + lsps2_client_handler: Option>>>, service_config: Option, _client_config: Option, best_block: Option>, @@ -123,7 +124,7 @@ where client_config: Option, ) -> Self where { - let pending_messages = Arc::new(Mutex::new(vec![])); + let pending_messages = Arc::new(DefaultMessageQueue::new()); let pending_events = Arc::new(EventQueue::new()); let lsps0_client_handler = LSPS0ClientHandler::new( @@ -207,34 +208,44 @@ where { } /// Returns a reference to the LSPS0 client-side handler. - pub fn lsps0_client_handler(&self) -> &LSPS0ClientHandler { + pub fn lsps0_client_handler(&self) -> &LSPS0ClientHandler>> { &self.lsps0_client_handler } /// Returns a reference to the LSPS0 server-side handler. - pub fn lsps0_service_handler(&self) -> Option<&LSPS0ServiceHandler> { + pub fn lsps0_service_handler( + &self, + ) -> Option<&LSPS0ServiceHandler>>> { self.lsps0_service_handler.as_ref() } /// Returns a reference to the LSPS1 client-side handler. #[cfg(lsps1)] - pub fn lsps1_client_handler(&self) -> Option<&LSPS1ClientHandler> { + pub fn lsps1_client_handler( + &self, + ) -> Option<&LSPS1ClientHandler>, C>> { self.lsps1_client_handler.as_ref() } /// Returns a reference to the LSPS1 server-side handler. #[cfg(lsps1)] - pub fn lsps1_service_handler(&self) -> Option<&LSPS1ServiceHandler> { + pub fn lsps1_service_handler( + &self, + ) -> Option<&LSPS1ServiceHandler>, C>> { self.lsps1_service_handler.as_ref() } /// Returns a reference to the LSPS2 client-side handler. - pub fn lsps2_client_handler(&self) -> Option<&LSPS2ClientHandler> { + pub fn lsps2_client_handler( + &self, + ) -> Option<&LSPS2ClientHandler>>> { self.lsps2_client_handler.as_ref() } /// Returns a reference to the LSPS2 server-side handler. - pub fn lsps2_service_handler(&self) -> Option<&LSPS2ServiceHandler> { + pub fn lsps2_service_handler( + &self, + ) -> Option<&LSPS2ServiceHandler>>> { self.lsps2_service_handler.as_ref() } @@ -271,20 +282,7 @@ where { /// [`PeerManager`]: lightning::ln::peer_handler::PeerManager /// [`PeerManager::process_events`]: lightning::ln::peer_handler::PeerManager::process_events pub fn set_peer_manager(&self, peer_manager: PM) { - #[cfg(lsps1)] - if let Some(lsps1_client_handler) = &self.lsps1_client_handler { - lsps1_client_handler.set_peer_manager(peer_manager.clone()); - } - #[cfg(lsps1)] - if let Some(lsps1_service_handler) = &self.lsps1_service_handler { - lsps1_service_handler.set_peer_manager(peer_manager.clone()); - } - if let Some(lsps2_client_handler) = &self.lsps2_client_handler { - lsps2_client_handler.set_peer_manager(peer_manager.clone()); - } - if let Some(lsps2_service_handler) = &self.lsps2_service_handler { - lsps2_service_handler.set_peer_manager(peer_manager); - } + self.pending_messages.set_peer_manager(peer_manager); } fn handle_lsps_message( @@ -348,11 +346,6 @@ where { } Ok(()) } - - fn enqueue_message(&self, node_id: PublicKey, msg: LSPSMessage) { - let mut pending_msgs = self.pending_messages.lock().unwrap(); - pending_msgs.push((node_id, msg)); - } } impl @@ -394,7 +387,7 @@ where match message { Ok(msg) => self.handle_lsps_message(msg, sender_node_id), Err(_) => { - self.enqueue_message(*sender_node_id, LSPSMessage::Invalid); + self.pending_messages.enqueue(sender_node_id, LSPSMessage::Invalid); Ok(()) } } @@ -403,15 +396,14 @@ where fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { let mut request_id_to_method_map = self.request_id_to_method_map.lock().unwrap(); self.pending_messages - .lock() - .unwrap() - .drain(..) + .get_and_clear_pending_msgs() + .iter() .map(|(public_key, lsps_message)| { if let Some((request_id, method_name)) = lsps_message.get_request_id_and_method() { request_id_to_method_map.insert(request_id, method_name); } ( - public_key, + *public_key, RawLSPSMessage { payload: serde_json::to_string(&lsps_message).unwrap() }, ) }) diff --git a/src/message_queue.rs b/src/message_queue.rs new file mode 100644 index 0000000..9cc420e --- /dev/null +++ b/src/message_queue.rs @@ -0,0 +1,66 @@ +//! Holds types and traits used to implement message queues for [`LSPSMessage`]s. + +use crate::lsps0::msgs::LSPSMessage; +use crate::prelude::{Vec, VecDeque}; +use crate::sync::Mutex; + +use lightning::ln::peer_handler::APeerManager; + +use bitcoin::secp256k1::PublicKey; + +use core::ops::Deref; + +/// Represents a simple message queue that the LSPS message handlers use to send messages to a given counterparty. +pub trait MessageQueue { + /// Enqueues an LSPS message to be sent to the counterparty with the given node id. + /// + /// Implementations need to take care of message delivery to the counterparty via the + /// LSPS0/BOLT8 transport protocol. + fn enqueue(&self, counterparty_node_id: &PublicKey, msg: LSPSMessage); +} + +/// The default [`MessageQueue`] Implementation used by [`LiquidityManager`]. +/// +/// [`LiquidityManager`]: crate::LiquidityManager +pub struct DefaultMessageQueue +where + PM::Target: APeerManager, +{ + queue: Mutex>, + peer_manager: Mutex>, +} + +impl DefaultMessageQueue +where + PM::Target: APeerManager, +{ + pub(crate) fn new() -> Self { + let queue = Mutex::new(VecDeque::new()); + let peer_manager = Mutex::new(None); + Self { queue, peer_manager } + } + + pub(crate) fn get_and_clear_pending_msgs(&self) -> Vec<(PublicKey, LSPSMessage)> { + self.queue.lock().unwrap().drain(..).collect() + } + + pub(crate) fn set_peer_manager(&self, peer_manager: PM) { + *self.peer_manager.lock().unwrap() = Some(peer_manager); + } +} + +impl MessageQueue for DefaultMessageQueue +where + PM::Target: APeerManager, +{ + fn enqueue(&self, counterparty_node_id: &PublicKey, msg: LSPSMessage) { + { + let mut queue = self.queue.lock().unwrap(); + queue.push_back((*counterparty_node_id, msg)); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.as_ref().process_events(); + } + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..b5614dd --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1 @@ +pub mod utils; diff --git a/src/tests/utils.rs b/src/tests/utils.rs new file mode 100644 index 0000000..4110e74 --- /dev/null +++ b/src/tests/utils.rs @@ -0,0 +1,39 @@ +use crate::lsps0::msgs::LSPSMessage; +use crate::message_queue::MessageQueue; +use crate::prelude::{Vec, VecDeque}; +use crate::sync::Mutex; + +use lightning::sign::EntropySource; + +use bitcoin::secp256k1::PublicKey; + +pub(crate) struct TestMessageQueue { + queue: Mutex>, +} + +impl TestMessageQueue { + pub(crate) fn new() -> Self { + let queue = Mutex::new(VecDeque::new()); + Self { queue } + } + + pub(crate) fn get_and_clear_pending_msgs(&self) -> Vec<(PublicKey, LSPSMessage)> { + self.queue.lock().unwrap().drain(..).collect() + } +} + +impl MessageQueue for TestMessageQueue { + fn enqueue(&self, counterparty_node_id: &PublicKey, msg: LSPSMessage) { + { + let mut queue = self.queue.lock().unwrap(); + queue.push_back((*counterparty_node_id, msg)); + } + } +} + +pub struct TestEntropy {} +impl EntropySource for TestEntropy { + fn get_secure_random_bytes(&self) -> [u8; 32] { + [0; 32] + } +}