From fa7d279741a3619437a7bebde3504bc49741541d Mon Sep 17 00:00:00 2001 From: G8XSU <3442979+G8XSU@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:05:38 -0700 Subject: [PATCH 1/3] Add experimental LSPS2 support. --- ldk-server/Cargo.toml | 3 + ldk-server/ldk-server-config.toml | 34 ++++++++- ldk-server/src/main.rs | 9 ++- ldk-server/src/util/config.rs | 122 ++++++++++++++++++++++++++---- 4 files changed, 151 insertions(+), 17 deletions(-) diff --git a/ldk-server/Cargo.toml b/ldk-server/Cargo.toml index ded2046..fb3527a 100644 --- a/ldk-server/Cargo.toml +++ b/ldk-server/Cargo.toml @@ -26,6 +26,9 @@ lapin = { version = "2.4.0", features = ["rustls"], default-features = false, op default = [] events-rabbitmq = ["dep:lapin"] +# Experimental Features. +experimental-lsps2-support = [] + # Feature-flags related to integration tests. integration-tests-events-rabbitmq = ["events-rabbitmq"] diff --git a/ldk-server/ldk-server-config.toml b/ldk-server/ldk-server-config.toml index 78a5534..7f5e7ba 100644 --- a/ldk-server/ldk-server-config.toml +++ b/ldk-server/ldk-server-config.toml @@ -17,4 +17,36 @@ rpc_password = "polarpass" # RPC password # RabbitMQ settings (only required if using events-rabbitmq feature) [rabbitmq] connection_string = "" # RabbitMQ connection string -exchange_name = "" # RabbitMQ exchange name +exchange_name = "" + +# Experimental LSPS2 Service Support +# CAUTION: LSPS2 support is highly experimental and for testing purposes only. +[liquidity.lsps2_service] +# Indicates whether the LSPS service will be announced via the gossip network. +advertise_service = false + +# The fee we withhold for the channel open from the initial payment. +channel_opening_fee_ppm = 1000 # 0.1% fee + +# The proportional overprovisioning for the channel. +channel_over_provisioning_ppm = 500000 # 50% extra capacity + +# The minimum fee required for opening a channel. +min_channel_opening_fee_msat = 10000000 # 10,000 satoshis + +# The minimum number of blocks after confirmation we promise to keep the channel open. +min_channel_lifetime = 4320 # ~30 days + +# The maximum number of blocks that the client is allowed to set its `to_self_delay` parameter. +max_client_to_self_delay = 1440 # ~10 days + +# The minimum payment size that we will accept when opening a channel. +min_payment_size_msat = 10000000 # 10,000 satoshis + +# The maximum payment size that we will accept when opening a channel. +max_payment_size_msat = 25000000000 # 0.25 BTC + +# Optional token for clients (uncomment and set if required) +## A token we may require to be sent by the clients. +## If set, only requests matching this token will be accepted. (uncomment and set if required) +# require_token = "" diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs index 3f8fe06..f3ad228 100644 --- a/ldk-server/src/main.rs +++ b/ldk-server/src/main.rs @@ -29,7 +29,8 @@ use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto}; use hex::DisplayHex; use ldk_node::config::Config; use ldk_node::lightning::ln::channelmanager::PaymentId; -use ldk_node::logger::LogLevel; +#[cfg(feature = "experimental-lsps2-support")] +use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_server_protos::events; use ldk_server_protos::events::{event_envelope, EventEnvelope}; use ldk_server_protos::types::Payment; @@ -81,6 +82,12 @@ fn main() { config_file.bitcoind_rpc_password, ); + // LSPS2 support is highly experimental and for testing purposes only. + #[cfg(feature = "experimental-lsps2-support")] + builder.set_liquidity_provider_lsps2( + config_file.lsps2_service_config.expect("Missing liquidity.lsps2_server config"), + ); + let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { Ok(runtime) => Arc::new(runtime), Err(e) => { diff --git a/ldk-server/src/util/config.rs b/ldk-server/src/util/config.rs index 377801f..110e499 100644 --- a/ldk-server/src/util/config.rs +++ b/ldk-server/src/util/config.rs @@ -1,5 +1,6 @@ use ldk_node::bitcoin::Network; use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::liquidity::LSPS2ServiceConfig; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use std::path::Path; @@ -7,7 +8,7 @@ use std::str::FromStr; use std::{fs, io}; /// Configuration for LDK Server. -#[derive(PartialEq, Eq, Debug)] +#[derive(Debug)] pub struct Config { pub listening_addr: SocketAddress, pub network: Network, @@ -18,6 +19,7 @@ pub struct Config { pub bitcoind_rpc_password: String, pub rabbitmq_connection_string: String, pub rabbitmq_exchange_name: String, + pub lsps2_service_config: Option, } impl TryFrom for Config { @@ -61,6 +63,17 @@ impl TryFrom for Config { (rabbitmq.connection_string, rabbitmq.exchange_name) }; + #[cfg(not(feature = "experimental-lsps2-support"))] + let lsps2_service_config: Option = None; + #[cfg(feature = "experimental-lsps2-support")] + let lsps2_service_config = Some(toml_config.liquidity + .and_then(|l| l.lsps2_service) + .ok_or_else(|| io::Error::new( + io::ErrorKind::InvalidInput, + "`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature." + ))? + .into()); + Ok(Config { listening_addr, network: toml_config.node.network, @@ -71,6 +84,7 @@ impl TryFrom for Config { bitcoind_rpc_password: toml_config.bitcoind.rpc_password, rabbitmq_connection_string, rabbitmq_exchange_name, + lsps2_service_config, }) } } @@ -82,6 +96,7 @@ pub struct TomlConfig { storage: StorageConfig, bitcoind: BitcoindConfig, rabbitmq: Option, + liquidity: Option, } #[derive(Deserialize, Serialize)] @@ -114,6 +129,52 @@ struct RabbitmqConfig { exchange_name: String, } +#[derive(Deserialize, Serialize)] +struct LiquidityConfig { + lsps2_service: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +struct LSPS2ServiceTomlConfig { + advertise_service: bool, + channel_opening_fee_ppm: u32, + channel_over_provisioning_ppm: u32, + min_channel_opening_fee_msat: u64, + min_channel_lifetime: u32, + max_client_to_self_delay: u32, + min_payment_size_msat: u64, + max_payment_size_msat: u64, + require_token: Option, +} + +impl Into for LSPS2ServiceTomlConfig { + fn into(self) -> LSPS2ServiceConfig { + match self { + LSPS2ServiceTomlConfig { + advertise_service, + channel_opening_fee_ppm, + channel_over_provisioning_ppm, + min_channel_opening_fee_msat, + min_channel_lifetime, + max_client_to_self_delay, + min_payment_size_msat, + max_payment_size_msat, + require_token, + } => LSPS2ServiceConfig { + advertise_service, + channel_opening_fee_ppm, + channel_over_provisioning_ppm, + min_channel_opening_fee_msat, + min_channel_lifetime, + min_payment_size_msat, + max_client_to_self_delay, + max_payment_size_msat, + require_token, + }, + } + } +} + /// Loads the configuration from a TOML file at the given path. pub fn load_config>(config_path: P) -> io::Result { let file_contents = fs::read_to_string(config_path.as_ref()).map_err(|e| { @@ -160,23 +221,54 @@ mod tests { [rabbitmq] connection_string = "rabbitmq_connection_string" exchange_name = "rabbitmq_exchange_name" + + [liquidity.lsps2_service] + advertise_service = false + channel_opening_fee_ppm = 1000 # 0.1% fee + channel_over_provisioning_ppm = 500000 # 50% extra capacity + min_channel_opening_fee_msat = 10000000 # 10,000 satoshis + min_channel_lifetime = 4320 # ~30 days + max_client_to_self_delay = 1440 # ~10 days + min_payment_size_msat = 10000000 # 10,000 satoshis + max_payment_size_msat = 25000000000 # 0.25 BTC "#; fs::write(storage_path.join(config_file_name), toml_config).unwrap(); - assert_eq!( - load_config(storage_path.join(config_file_name)).unwrap(), - Config { - listening_addr: SocketAddress::from_str("localhost:3001").unwrap(), - network: Network::Regtest, - rest_service_addr: SocketAddr::from_str("127.0.0.1:3002").unwrap(), - storage_dir_path: "/tmp".to_string(), - bitcoind_rpc_addr: SocketAddr::from_str("127.0.0.1:8332").unwrap(), - bitcoind_rpc_user: "bitcoind-testuser".to_string(), - bitcoind_rpc_password: "bitcoind-testpassword".to_string(), - rabbitmq_connection_string: "rabbitmq_connection_string".to_string(), - rabbitmq_exchange_name: "rabbitmq_exchange_name".to_string(), - } - ) + let config = load_config(storage_path.join(config_file_name)).unwrap(); + let expected = Config { + listening_addr: SocketAddress::from_str("localhost:3001").unwrap(), + network: Network::Regtest, + rest_service_addr: SocketAddr::from_str("127.0.0.1:3002").unwrap(), + storage_dir_path: "/tmp".to_string(), + bitcoind_rpc_addr: SocketAddr::from_str("127.0.0.1:8332").unwrap(), + bitcoind_rpc_user: "bitcoind-testuser".to_string(), + bitcoind_rpc_password: "bitcoind-testpassword".to_string(), + rabbitmq_connection_string: "rabbitmq_connection_string".to_string(), + rabbitmq_exchange_name: "rabbitmq_exchange_name".to_string(), + lsps2_service_config: Some(LSPS2ServiceConfig { + require_token: None, + advertise_service: false, + channel_opening_fee_ppm: 1000, + channel_over_provisioning_ppm: 500000, + min_channel_opening_fee_msat: 10000000, + min_channel_lifetime: 4320, + max_client_to_self_delay: 1440, + min_payment_size_msat: 10000000, + max_payment_size_msat: 25000000000, + }), + }; + + assert_eq!(config.listening_addr, expected.listening_addr); + assert_eq!(config.network, expected.network); + assert_eq!(config.rest_service_addr, expected.rest_service_addr); + assert_eq!(config.storage_dir_path, expected.storage_dir_path); + assert_eq!(config.bitcoind_rpc_addr, expected.bitcoind_rpc_addr); + assert_eq!(config.bitcoind_rpc_user, expected.bitcoind_rpc_user); + assert_eq!(config.bitcoind_rpc_password, expected.bitcoind_rpc_password); + assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string); + assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name); + #[cfg(feature = "experimental-lsps2-support")] + assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some()); } } From d6a3fbb6c36d2583cbde3340381c3ea9cea67f8b Mon Sep 17 00:00:00 2001 From: G8XSU <3442979+G8XSU@users.noreply.github.com> Date: Fri, 21 Mar 2025 18:07:20 -0700 Subject: [PATCH 2/3] Remove unused ldk-server.config --- ldk-server/ldk-server.config | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 ldk-server/ldk-server.config diff --git a/ldk-server/ldk-server.config b/ldk-server/ldk-server.config deleted file mode 100644 index d1bead2..0000000 --- a/ldk-server/ldk-server.config +++ /dev/null @@ -1,29 +0,0 @@ -{ - // The addresses on which the lightning node will listen for incoming connections. - "listening_address": "localhost:3001", - - // The Bitcoin network to use. - "network": "regtest", - - // The address on which LDK Server will accept incoming requests. - "rest_service_address": "127.0.0.1:3002", - - // The path where the underlying LDK and BDK persist their data. - "storage_dir_path": "/tmp/ldk-server/", - - // Bitcoin Core's RPC endpoint. - "bitcoind_rpc_address": "127.0.0.1:18444", - - // Bitcoin Core's RPC user. - "bitcoind_rpc_user": "polaruser", - - // Bitcoin Core's RPC password. - "bitcoind_rpc_password": "polarpass", - - // RabbitMQ connection string. (only required if using RabbitMQ based events using `events-rabbitmq` feature) - "rabbitmq_connection_string": "", - - // RabbitMQ exchange name. (only required if using RabbitMQ based events using `events-rabbitmq` feature) - "rabbitmq_exchange_name": "" - -} From 551015ae43f0d303efc71aaf43ab14d557d7dfa5 Mon Sep 17 00:00:00 2001 From: G8XSU <3442979+G8XSU@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:43:38 -0700 Subject: [PATCH 3/3] Log and exit in case of invalid configuration. --- ldk-server/src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs index f3ad228..8421530 100644 --- a/ldk-server/src/main.rs +++ b/ldk-server/src/main.rs @@ -64,7 +64,13 @@ fn main() { } let mut ldk_node_config = Config::default(); - let config_file = load_config(Path::new(arg)).expect("Invalid configuration file."); + let config_file = match load_config(Path::new(arg)) { + Ok(config) => config, + Err(e) => { + eprintln!("Invalid configuration file: {}", e); + std::process::exit(-1); + }, + }; ldk_node_config.storage_dir_path = config_file.storage_dir_path.clone(); ldk_node_config.listening_addresses = Some(vec![config_file.listening_addr]);