From 92b4c427ea2d930a71d876d1affa1bf4f12aece7 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 19 Dec 2023 16:34:53 +0100 Subject: [PATCH] WIP --- Cargo.toml | 11 ++++ bindings/ldk_node.udl | 1 + src/builder.rs | 83 +++++++++++++++++++++++++--- src/lib.rs | 16 ++++++ src/liquidity.rs | 122 ++++++++++++++++++++++++++++++++++++++++++ src/types.rs | 15 ++++-- 6 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 src/liquidity.rs diff --git a/Cargo.toml b/Cargo.toml index b831c1d75..490370205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ lightning-persister = { version = "0.0.119" } lightning-background-processor = { version = "0.0.119", features = ["futures"] } lightning-rapid-gossip-sync = { version = "0.0.119" } lightning-transaction-sync = { version = "0.0.119", features = ["esplora-async-https"] } +#lightning-liquidity = { git = "https://github.com/tnull/lightning-liquidity", branch="2023-12-upgrade-to-LDK-0.0.119", features = ["std"] } +lightning-liquidity = { git = "https://github.com/tnull/lightning-liquidity", branch="2024-01-async-event-queue", features = ["std"] } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std"] } #lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } @@ -44,6 +46,15 @@ lightning-transaction-sync = { version = "0.0.119", features = ["esplora-async-h #lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } #lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["esplora-async"] } +# lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std"] } +# lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } +# lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } +# lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } +# lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["futures"] } +# lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } +# lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["esplora-async"] } +# lightning-liquidity = { git = "https://github.com/lightningdevkit/lightning-liquidity", branch="main", features = ["std"] } + #lightning = { path = "../rust-lightning/lightning", features = ["std"] } #lightning-invoice = { path = "../rust-lightning/lightning-invoice" } #lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 2a961b29a..f1730f4c3 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -27,6 +27,7 @@ interface Builder { void set_esplora_server(string esplora_server_url); void set_gossip_source_p2p(); void set_gossip_source_rgs(string rgs_server_url); + void set_liquidity_source_lsps2(string service_url, PublicKey service_node_id, string token); void set_storage_dir_path(string storage_dir_path); void set_network(Network network); [Throws=BuildError] diff --git a/src/builder.rs b/src/builder.rs index b60123844..b38331d2f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,6 +3,7 @@ use crate::fee_estimator::OnchainFeeEstimator; use crate::gossip::GossipSource; use crate::io; use crate::io::sqlite_store::SqliteStore; +use crate::liquidity::LiquiditySource; use crate::logger::{log_error, FilesystemLogger, Logger}; use crate::payment_store::PaymentStore; use crate::peer_store::PeerStore; @@ -40,6 +41,9 @@ use lightning_persister::fs_store::FilesystemStore; use lightning_transaction_sync::EsploraSyncClient; +use lightning_liquidity::lsps2::client::LSPS2ClientConfig; +use lightning_liquidity::{LiquidityClientConfig, LiquidityManager}; + #[cfg(any(vss, vss_test))] use crate::io::vss_store::VssStore; use bdk::bitcoin::secp256k1::Secp256k1; @@ -49,6 +53,7 @@ use bdk::template::Bip84; use bip39::Mnemonic; +use bitcoin::secp256k1::PublicKey; use bitcoin::BlockHash; use std::convert::TryInto; @@ -78,6 +83,11 @@ enum GossipSourceConfig { RapidGossipSync(String), } +#[derive(Debug, Clone)] +enum LiquiditySourceConfig { + LSPS2Service { service_url: String, service_node_id: PublicKey, token: String }, +} + /// An error encountered during building a [`Node`]. /// /// [`Node`]: crate::Node @@ -144,16 +154,14 @@ pub struct NodeBuilder { entropy_source_config: Option, chain_data_source_config: Option, gossip_source_config: Option, + liquidity_source_config: Option, } impl NodeBuilder { /// Creates a new builder instance with the default configuration. pub fn new() -> Self { let config = Config::default(); - let entropy_source_config = None; - let chain_data_source_config = None; - let gossip_source_config = None; - Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } + Self::from_config(config) } /// Creates a new builder instance from an [`Config`]. @@ -162,7 +170,14 @@ impl NodeBuilder { let entropy_source_config = None; let chain_data_source_config = None; let gossip_source_config = None; - Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } + let liquidity_source_config = None; + Self { + config, + entropy_source_config, + chain_data_source_config, + gossip_source_config, + liquidity_source_config, + } } /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. @@ -216,6 +231,19 @@ impl NodeBuilder { self } + /// Configures the [`Node`] instance to source its inbound liquidity from the given + /// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md) + /// service. + /// + /// The given `token` will be used by the LSP to authenticate the user. + pub fn set_liquidity_source_lsps2( + &mut self, service_url: String, service_node_id: PublicKey, token: String, + ) -> &mut Self { + self.liquidity_source_config = + Some(LiquiditySourceConfig::LSPS2Service { service_url, service_node_id, token }); + self + } + /// Sets the used storage directory path. pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self { self.config.storage_dir_path = storage_dir_path; @@ -307,6 +335,7 @@ impl NodeBuilder { config, self.chain_data_source_config.as_ref(), self.gossip_source_config.as_ref(), + self.liquidity_source_config.as_ref(), seed_bytes, logger, kv_store, @@ -380,6 +409,17 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_gossip_source_rgs(rgs_server_url); } + /// Configures the [`Node`] instance to source its inbound liquidity from the given + /// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md) + /// service. + /// + /// The given `token` will be used by the LSP to authenticate the user. + pub fn set_liquidity_source_lsps2( + &self, service_url: String, service_node_id: PublicKey, token: String, + ) { + self.inner.write().unwrap().set_liquidity_source_lsps2(service_url, service_node_id, token); + } + /// Sets the used storage directory path. pub fn set_storage_dir_path(&self, storage_dir_path: String) { self.inner.write().unwrap().set_storage_dir_path(storage_dir_path); @@ -430,7 +470,8 @@ impl ArcedNodeBuilder { /// Builds a [`Node`] instance according to the options previously configured. fn build_with_store_internal( config: Arc, chain_data_source_config: Option<&ChainDataSourceConfig>, - gossip_source_config: Option<&GossipSourceConfig>, seed_bytes: [u8; 64], + gossip_source_config: Option<&GossipSourceConfig>, + liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64], logger: Arc, kv_store: Arc, ) -> Result, BuildError> { // Initialize the on-chain wallet and chain access @@ -707,20 +748,43 @@ fn build_with_store_internal( } }; + let liquidity_source = match liquidity_source_config { + Some(LiquiditySourceConfig::LSPS2Service { service_url, service_node_id, token }) => { + let lsps2_client_config = Some(LSPS2ClientConfig {}); + let liquidity_client_config = Some(LiquidityClientConfig { lsps2_client_config }); + + let liquidity_manager = Arc::new(LiquidityManager::new( + Arc::clone(&keys_manager), + Arc::clone(&channel_manager), + Some(Arc::clone(&tx_sync)), + None, + None, + liquidity_client_config, + )); + Arc::new(LiquiditySource::new_lsps2( + Arc::clone(&liquidity_manager), + service_url.clone(), + *service_node_id, + token.clone(), + )) + } + None => Arc::new(LiquiditySource::new_none()), + }; + let msg_handler = match gossip_source.as_gossip_sync() { GossipSync::P2P(p2p_gossip_sync) => MessageHandler { chan_handler: Arc::clone(&channel_manager), route_handler: Arc::clone(&p2p_gossip_sync) as Arc, onion_message_handler: onion_messenger, - custom_message_handler: IgnoringMessageHandler {}, + custom_message_handler: Arc::clone(&liquidity_source), }, GossipSync::Rapid(_) => MessageHandler { chan_handler: Arc::clone(&channel_manager), route_handler: Arc::new(IgnoringMessageHandler {}) as Arc, onion_message_handler: onion_messenger, - custom_message_handler: IgnoringMessageHandler {}, + custom_message_handler: Arc::clone(&liquidity_source), }, GossipSync::None => { unreachable!("We must always have a gossip sync!"); @@ -743,6 +807,8 @@ fn build_with_store_internal( Arc::clone(&keys_manager), )); + liquidity_source.set_peer_manager(Arc::clone(&peer_manager)); + // Init payment info storage let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) { Ok(payments) => { @@ -814,6 +880,7 @@ fn build_with_store_internal( keys_manager, network_graph, gossip_source, + liquidity_source, kv_store, logger, _router: router, diff --git a/src/lib.rs b/src/lib.rs index 3d51ef984..bd7911803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ mod fee_estimator; mod gossip; mod hex_utils; pub mod io; +mod liquidity; mod logger; mod payment_store; mod peer_store; @@ -115,6 +116,7 @@ pub use builder::NodeBuilder as Builder; use event::{EventHandler, EventQueue}; use gossip::GossipSource; +use liquidity::LiquiditySource; use payment_store::PaymentStore; pub use payment_store::{PaymentDetails, PaymentDirection, PaymentStatus}; use peer_store::{PeerInfo, PeerStore}; @@ -297,6 +299,7 @@ pub struct Node { keys_manager: Arc, network_graph: Arc, gossip_source: Arc, + liquidity_source: Arc>, kv_store: Arc, logger: Arc, _router: Arc, @@ -750,6 +753,19 @@ impl Node { }); }); + let mut stop_liquidity_handler = self.stop_receiver.clone(); + let liquidity_handler = Arc::clone(&self.liquidity_source); + runtime.spawn(async move { + loop { + tokio::select! { + _ = stop_liquidity_handler.changed() => { + return; + } + _ = liquidity_handler.handle_next_event() => {} + } + } + }); + *runtime_lock = Some(runtime); log_info!(self.logger, "Startup complete."); diff --git a/src/liquidity.rs b/src/liquidity.rs new file mode 100644 index 000000000..9b9271367 --- /dev/null +++ b/src/liquidity.rs @@ -0,0 +1,122 @@ +use crate::types::{LiquidityManager, PeerManager}; + +use lightning::ln::features::{InitFeatures, NodeFeatures}; +use lightning::ln::peer_handler::CustomMessageHandler; +use lightning::ln::wire::CustomMessageReader; +use lightning::util::persist::KVStore; +use lightning_liquidity::events::Event; +use lightning_liquidity::lsps0::msgs::RawLSPSMessage; +use lightning_liquidity::lsps2::event::LSPS2ClientEvent; + +use bitcoin::secp256k1::PublicKey; + +use std::sync::Arc; + +pub(crate) enum LiquiditySource { + None, + LSPS2 { + liquidity_manager: Arc>, + service_url: String, + service_node_id: PublicKey, + token: String, + }, +} + +impl LiquiditySource { + pub(crate) fn new_lsps2( + liquidity_manager: Arc>, service_url: String, + service_node_id: PublicKey, token: String, + ) -> Self { + Self::LSPS2 { liquidity_manager, service_url, service_node_id, token } + } + + pub(crate) fn new_none() -> Self { + Self::None + } + + pub(crate) fn set_peer_manager(&self, peer_manager: Arc>) { + match self { + Self::LSPS2 { liquidity_manager, .. } => { + let process_msgs_callback = move || peer_manager.process_events(); + liquidity_manager.set_process_msgs_callback(process_msgs_callback); + } + Self::None => {} + } + } + + pub(crate) async fn handle_next_event(&self) { + match self { + Self::LSPS2 { liquidity_manager, service_url, service_node_id, token } => { + match liquidity_manager.next_event_async().await { + Event::LSPS2Client(LSPS2ClientEvent::GetInfoResponse { + jit_channel_id: _, + counterparty_node_id: _, + opening_fee_params_menu: _, + min_payment_size_msat: _, + max_payment_size_msat: _, + user_channel_id: _, + }) => {} + Event::LSPS2Client(LSPS2ClientEvent::InvoiceGenerationReady { + counterparty_node_id: _, + scid: _, + cltv_expiry_delta: _, + payment_size_msat: _, + client_trusts_lsp: _, + user_channel_id: _, + }) => {} + _ => {} + } + } + Self::None => {} + } + } +} + +impl CustomMessageReader for LiquiditySource { + type CustomMessage = RawLSPSMessage; + + fn read( + &self, message_type: u16, buffer: &mut RD, + ) -> Result, lightning::ln::msgs::DecodeError> { + match self { + Self::LSPS2 { liquidity_manager, .. } => liquidity_manager.read(message_type, buffer), + Self::None => Ok(None), + } + } +} + +impl CustomMessageHandler for LiquiditySource { + fn handle_custom_message( + &self, msg: Self::CustomMessage, sender_node_id: &PublicKey, + ) -> Result<(), lightning::ln::msgs::LightningError> { + match self { + Self::LSPS2 { liquidity_manager, .. } => { + liquidity_manager.handle_custom_message(msg, sender_node_id) + } + Self::None => Ok(()), // Should be unreachable!() as the reader will return `None` + } + } + + fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { + match self { + Self::LSPS2 { liquidity_manager, .. } => liquidity_manager.get_and_clear_pending_msg(), + Self::None => Vec::new(), + } + } + + fn provided_node_features(&self) -> NodeFeatures { + match self { + Self::LSPS2 { liquidity_manager, .. } => liquidity_manager.provided_node_features(), + Self::None => NodeFeatures::empty(), + } + } + + fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures { + match self { + Self::LSPS2 { liquidity_manager, .. } => { + liquidity_manager.provided_init_features(their_node_id) + } + Self::None => InitFeatures::empty(), + } + } +} diff --git a/src/types.rs b/src/types.rs index b5926cb99..178451dc2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use crate::liquidity::LiquiditySource; use crate::logger::FilesystemLogger; use crate::sweep::OutputSweeper; @@ -27,7 +28,7 @@ use std::sync::{Arc, Mutex, RwLock}; pub(crate) type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, - Arc>>, + Arc, Arc, Arc, Arc, @@ -40,8 +41,16 @@ pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< Arc, Arc, Arc, - IgnoringMessageHandler, + Arc>, + Arc, +>; + +pub(crate) type ChainSource = EsploraSyncClient>; + +pub(crate) type LiquidityManager = lightning_liquidity::LiquidityManager< Arc, + Arc>, + Arc, >; pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< @@ -134,7 +143,7 @@ impl lightning::onion_message::MessageRouter for FakeMessageRouter { pub(crate) type Sweeper = OutputSweeper< Arc, Arc, - Arc>>, + Arc, Arc, Arc, >;