diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index a45ec6e..d1de29c 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -11,7 +11,8 @@ use ldk_server_client::ldk_server_protos::api::{ OpenChannelRequest, }; use ldk_server_client::ldk_server_protos::types::{ - bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment, + bolt11_invoice_description, channel_config, Bolt11InvoiceDescription, ChannelConfig, PageToken, + Payment, }; use std::fmt::Debug; @@ -101,6 +102,22 @@ enum Commands { push_to_counterparty_msat: Option, #[arg(long)] announce_channel: bool, + // Channel config options + #[arg( + long, + help = "Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound over the channel. This can be updated by using update-channel-config." + )] + forwarding_fee_proportional_millionths: Option, + #[arg( + long, + help = "Amount (in milli-satoshi) charged for payments forwarded outbound over the channel, in excess of forwarding_fee_proportional_millionths. This can be updated by using update-channel-config." + )] + forwarding_fee_base_msat: Option, + #[arg( + long, + help = "The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded over the channel. This can be updated by using update-channel-config." + )] + cltv_expiry_delta: Option, }, ListChannels, ListPayments { @@ -213,7 +230,16 @@ async fn main() { channel_amount_sats, push_to_counterparty_msat, announce_channel, + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, } => { + let channel_config = build_open_channel_config( + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, + ); + handle_response_result( client .open_channel(OpenChannelRequest { @@ -221,7 +247,7 @@ async fn main() { address, channel_amount_sats, push_to_counterparty_msat, - channel_config: None, + channel_config, announce_channel, }) .await, @@ -236,6 +262,28 @@ async fn main() { } } +fn build_open_channel_config( + forwarding_fee_proportional_millionths: Option, forwarding_fee_base_msat: Option, + cltv_expiry_delta: Option, +) -> Option { + // Only create a config if at least one field is set + if forwarding_fee_proportional_millionths.is_none() + && forwarding_fee_base_msat.is_none() + && cltv_expiry_delta.is_none() + { + return None; + } + + Some(ChannelConfig { + forwarding_fee_proportional_millionths, + forwarding_fee_base_msat, + cltv_expiry_delta, + force_close_avoidance_max_fee_satoshis: None, + accept_underpaying_htlcs: None, + max_dust_htlc_exposure: None, + }) +} + async fn list_n_payments( client: LdkServerClient, number_of_payments: Option, ) -> Result, LdkServerError> { diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index c1b0fa2..834c7e4 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -1,3 +1,8 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; +use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; + pub(crate) mod bolt11_receive; pub(crate) mod bolt11_send; pub(crate) mod bolt12_receive; @@ -14,3 +19,47 @@ pub(crate) mod onchain_receive; pub(crate) mod onchain_send; pub(crate) mod open_channel; pub(crate) mod update_channel_config; + +pub(crate) fn build_channel_config_from_proto( + default_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, +) -> Result { + let max_dust_htlc_exposure = proto_channel_config + .max_dust_htlc_exposure + .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { + MaxDustHtlcExposure::FixedLimitMsat(limit_msat) => { + MaxDustHTLCExposure::FixedLimit { limit_msat } + }, + MaxDustHtlcExposure::FeeRateMultiplier(multiplier) => { + MaxDustHTLCExposure::FeeRateMultiplier { multiplier } + }, + }) + .unwrap_or(default_config.max_dust_htlc_exposure); + + let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { + Some(c) => Some(u16::try_from(c).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid cltv_expiry_delta, must be between 0 and {}", u16::MAX), + ) + })?), + None => None, + } + .unwrap_or(default_config.cltv_expiry_delta); + + Ok(ChannelConfig { + forwarding_fee_proportional_millionths: proto_channel_config + .forwarding_fee_proportional_millionths + .unwrap_or(default_config.forwarding_fee_proportional_millionths), + forwarding_fee_base_msat: proto_channel_config + .forwarding_fee_base_msat + .unwrap_or(default_config.forwarding_fee_base_msat), + cltv_expiry_delta, + max_dust_htlc_exposure, + force_close_avoidance_max_fee_satoshis: proto_channel_config + .force_close_avoidance_max_fee_satoshis + .unwrap_or(default_config.force_close_avoidance_max_fee_satoshis), + accept_underpaying_htlcs: proto_channel_config + .accept_underpaying_htlcs + .unwrap_or(default_config.accept_underpaying_htlcs), + }) +} diff --git a/ldk-server/src/api/open_channel.rs b/ldk-server/src/api/open_channel.rs index 96fdbf2..c26dab0 100644 --- a/ldk-server/src/api/open_channel.rs +++ b/ldk-server/src/api/open_channel.rs @@ -1,6 +1,8 @@ +use crate::api::build_channel_config_from_proto; use crate::api::error::LdkServerError; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::config::ChannelConfig; use ldk_node::lightning::ln::msgs::SocketAddress; use ldk_server_protos::api::{OpenChannelRequest, OpenChannelResponse}; use std::str::FromStr; @@ -15,14 +17,18 @@ pub(crate) fn handle_open_channel( let address = SocketAddress::from_str(&request.address) .map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?; + let channel_config = request + .channel_config + .map(|proto_config| build_channel_config_from_proto(ChannelConfig::default(), proto_config)) + .transpose()?; + let user_channel_id = if request.announce_channel { context.node.open_announced_channel( node_id, address, request.channel_amount_sats, request.push_to_counterparty_msat, - // TODO: Allow setting ChannelConfig in open-channel. - None, + channel_config, )? } else { context.node.open_channel( @@ -30,7 +36,7 @@ pub(crate) fn handle_open_channel( address, request.channel_amount_sats, request.push_to_counterparty_msat, - None, + channel_config, )? }; diff --git a/ldk-server/src/api/update_channel_config.rs b/ldk-server/src/api/update_channel_config.rs index 2a01a54..e5905c2 100644 --- a/ldk-server/src/api/update_channel_config.rs +++ b/ldk-server/src/api/update_channel_config.rs @@ -1,11 +1,10 @@ +use crate::api::build_channel_config_from_proto; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::{InvalidRequestError, LightningError}; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; use ldk_node::UserChannelId; use ldk_server_protos::api::{UpdateChannelConfigRequest, UpdateChannelConfigResponse}; -use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; use std::str::FromStr; pub(crate) const UPDATE_CHANNEL_CONFIG_PATH: &str = "UpdateChannelConfig"; @@ -29,7 +28,7 @@ pub(crate) fn handle_update_channel_config_request( })? .config; - let updated_channel_config = build_updated_channel_config( + let updated_channel_config = build_channel_config_from_proto( current_config, request.channel_config.ok_or_else(|| { LdkServerError::new(InvalidRequestError, "Channel config must be provided.") @@ -56,47 +55,3 @@ pub(crate) fn handle_update_channel_config_request( Ok(UpdateChannelConfigResponse {}) } - -fn build_updated_channel_config( - current_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, -) -> Result { - let max_dust_htlc_exposure = proto_channel_config - .max_dust_htlc_exposure - .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { - MaxDustHtlcExposure::FixedLimitMsat(limit_msat) => { - MaxDustHTLCExposure::FixedLimit { limit_msat } - }, - MaxDustHtlcExposure::FeeRateMultiplier(multiplier) => { - MaxDustHTLCExposure::FeeRateMultiplier { multiplier } - }, - }) - .unwrap_or(current_config.max_dust_htlc_exposure); - - let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { - Some(c) => Some(u16::try_from(c).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid cltv_expiry_delta, must be between 0 and {}", u16::MAX), - ) - })?), - None => None, - } - .unwrap_or_else(|| current_config.cltv_expiry_delta); - - Ok(ChannelConfig { - forwarding_fee_proportional_millionths: proto_channel_config - .forwarding_fee_proportional_millionths - .unwrap_or(current_config.forwarding_fee_proportional_millionths), - forwarding_fee_base_msat: proto_channel_config - .forwarding_fee_base_msat - .unwrap_or(current_config.forwarding_fee_base_msat), - cltv_expiry_delta, - max_dust_htlc_exposure, - force_close_avoidance_max_fee_satoshis: proto_channel_config - .force_close_avoidance_max_fee_satoshis - .unwrap_or(current_config.force_close_avoidance_max_fee_satoshis), - accept_underpaying_htlcs: proto_channel_config - .accept_underpaying_htlcs - .unwrap_or(current_config.accept_underpaying_htlcs), - }) -}