Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental LSPS2 support. #60

Merged
merged 3 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ldk-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
34 changes: 33 additions & 1 deletion ldk-server/ldk-server-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
29 changes: 0 additions & 29 deletions ldk-server/ldk-server.config

This file was deleted.

17 changes: 15 additions & 2 deletions ldk-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,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]);
Expand All @@ -81,6 +88,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) => {
Expand Down
122 changes: 107 additions & 15 deletions ldk-server/src/util/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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;
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,
Expand All @@ -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<LSPS2ServiceConfig>,
}

impl TryFrom<TomlConfig> for Config {
Expand Down Expand Up @@ -61,6 +63,17 @@ impl TryFrom<TomlConfig> for Config {
(rabbitmq.connection_string, rabbitmq.exchange_name)
};

#[cfg(not(feature = "experimental-lsps2-support"))]
let lsps2_service_config: Option<LSPS2ServiceConfig> = 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,
Expand All @@ -71,6 +84,7 @@ impl TryFrom<TomlConfig> for Config {
bitcoind_rpc_password: toml_config.bitcoind.rpc_password,
rabbitmq_connection_string,
rabbitmq_exchange_name,
lsps2_service_config,
})
}
}
Expand All @@ -82,6 +96,7 @@ pub struct TomlConfig {
storage: StorageConfig,
bitcoind: BitcoindConfig,
rabbitmq: Option<RabbitmqConfig>,
liquidity: Option<LiquidityConfig>,
}

#[derive(Deserialize, Serialize)]
Expand Down Expand Up @@ -114,6 +129,52 @@ struct RabbitmqConfig {
exchange_name: String,
}

#[derive(Deserialize, Serialize)]
struct LiquidityConfig {
lsps2_service: Option<LSPS2ServiceTomlConfig>,
}

#[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<String>,
}

impl Into<LSPS2ServiceConfig> 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<P: AsRef<Path>>(config_path: P) -> io::Result<Config> {
let file_contents = fs::read_to_string(config_path.as_ref()).map_err(|e| {
Expand Down Expand Up @@ -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());
}
}