diff --git a/src/liquidity/mod.rs b/src/liquidity/mod.rs index 87a0650c83..140f789699 100644 --- a/src/liquidity/mod.rs +++ b/src/liquidity/mod.rs @@ -26,7 +26,7 @@ use lightning_liquidity::lsps1::client::LSPS1ClientConfig as LdkLSPS1ClientConfi use lightning_liquidity::lsps2::client::LSPS2ClientConfig as LdkLSPS2ClientConfig; use lightning_liquidity::lsps2::service::LSPS2ServiceConfig as LdkLSPS2ServiceConfig; use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig}; -pub use service::lsps2::LSPS2ServiceConfig; +pub use service::lsps2::{LSPS2ChannelConfig, LSPS2ServiceConfig}; use tokio::sync::oneshot; use crate::builder::BuildError; diff --git a/src/liquidity/service/lsps2.rs b/src/liquidity/service/lsps2.rs index 1143a08d73..83d0650c00 100644 --- a/src/liquidity/service/lsps2.rs +++ b/src/liquidity/service/lsps2.rs @@ -22,6 +22,7 @@ use lightning_liquidity::lsps2::msgs::LSPS2RawOpeningFeeParams; use lightning_liquidity::lsps2::service::LSPS2ServiceConfig as LdkLSPS2ServiceConfig; use lightning_types::payment::PaymentHash; +use crate::config::MaxDustHTLCExposure; use crate::logger::{log_error, LdkLogger}; use crate::types::{ChannelManager, KeysManager, LiquidityManager, PeerManager, Wallet}; use crate::{total_anchor_channels_reserve_sats, Config}; @@ -106,6 +107,31 @@ pub struct LSPS2ServiceConfig { /// /// [`Node::open_0reserve_channel`]: crate::Node::open_0reserve_channel pub disable_client_reserve: bool, + /// The channel config we'll apply to the JIT channels we open to clients, allowing to + /// configure them separately from regular channels. + /// + /// Any unset fields, or if set to `None` entirely, fall back to the node-wide config. + pub channel_config: Option, +} + +/// A subset of channel configuration options applied to the JIT channels opened towards +/// clients when acting as an LSPS2 service. +/// +/// Any fields set to `None` fall back to the node-wide config. +/// +/// Note that the forwarding fees of JIT channels are always set to zero, as we're +/// compensated via the channel opening fee. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct LSPS2ChannelConfig { + /// The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded + /// over the channel. + pub cltv_expiry_delta: Option, + /// The total exposure we're willing to allow to potential loss due to dust HTLCs. + pub max_dust_htlc_exposure: Option, + /// The additional fee we're willing to pay to avoid waiting for the counterparty's + /// `to_self_delay` to reclaim funds. + pub force_close_avoidance_max_fee_satoshis: Option, } impl LSPS2ServiceLiquiditySource @@ -487,12 +513,28 @@ where debug_assert!(!config.channel_handshake_config.announce_for_forwarding); debug_assert!(config.accept_forwards_to_priv_channels); - // We set the forwarding fee to 0 for now as we're getting paid by the channel fee. - // - // TODO: revisit this decision eventually. + // We set the forwarding fees to 0 as we're getting paid by the channel opening + // fee. config.channel_config.forwarding_fee_base_msat = 0; config.channel_config.forwarding_fee_proportional_millionths = 0; + if let Some(jit_channel_config) = service_config.channel_config.as_ref() { + if let Some(cltv_expiry_delta) = jit_channel_config.cltv_expiry_delta { + config.channel_config.cltv_expiry_delta = cltv_expiry_delta; + } + if let Some(max_dust_htlc_exposure) = jit_channel_config.max_dust_htlc_exposure + { + config.channel_config.max_dust_htlc_exposure = + max_dust_htlc_exposure.into(); + } + if let Some(force_close_avoidance_max_fee_satoshis) = + jit_channel_config.force_close_avoidance_max_fee_satoshis + { + config.channel_config.force_close_avoidance_max_fee_satoshis = + force_close_avoidance_max_fee_satoshis; + } + } + let result = if service_config.disable_client_reserve { self.channel_manager.create_channel_to_trusted_peer_0reserve( their_network_key, diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index b07a90629f..b3b5f8fb5f 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -31,7 +31,7 @@ use electrsd::corepc_node::{self, Node as BitcoinD}; use electrsd::ElectrsD; use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig, DEFAULT_FULL_SCAN_STOP_GAP}; use ldk_node::entropy::NodeEntropy; -use ldk_node::liquidity::LSPS2ServiceConfig; +use ldk_node::liquidity::{LSPS2ChannelConfig, LSPS2ServiceConfig}; use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, TransactionType, UnifiedPaymentResult, @@ -2787,6 +2787,12 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { // Setup three nodes: service, client, and payer let channel_opening_fee_ppm = 10_000; let channel_over_provisioning_ppm = 100_000; + + let jit_channel_config = LSPS2ChannelConfig { + cltv_expiry_delta: None, + max_dust_htlc_exposure: None, + force_close_avoidance_max_fee_satoshis: Some(1337), + }; let lsps2_service_config = LSPS2ServiceConfig { require_token: None, advertise_service: false, @@ -2799,6 +2805,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { max_client_to_self_delay: 1024, client_trusts_lsp, disable_client_reserve: false, + channel_config: Some(jit_channel_config), }; let service_config = random_config(true); @@ -2890,6 +2897,14 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let channel_value_sats = client_node.list_channels().first().unwrap().channel_value_sats; assert_eq!(channel_value_sats, expected_channel_size_sat); + let jit_channel = service_node + .list_channels() + .into_iter() + .find(|c| c.counterparty.node_id == client_node.node_id()) + .unwrap(); + assert_eq!(jit_channel.config.force_close_avoidance_max_fee_satoshis, 1337); + assert_eq!(jit_channel.config.forwarding_fee_base_msat, 0); + println!("Generating regular invoice!"); let invoice_description = Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap()).into(); @@ -3118,6 +3133,7 @@ async fn lsps2_client_trusts_lsp() { max_client_to_self_delay: 1024, client_trusts_lsp: true, disable_client_reserve: false, + channel_config: None, }; let service_config = random_config(true); @@ -3293,6 +3309,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { max_client_to_self_delay: 1024, client_trusts_lsp: false, disable_client_reserve: false, + channel_config: None, }; let service_config = random_config(true); @@ -4092,6 +4109,7 @@ async fn do_lsps2_multi_lsp_picks_cheapest(reverse_order: bool) { max_client_to_self_delay: 1024, client_trusts_lsp: true, disable_client_reserve: false, + channel_config: None, }; let cheap_node_config = random_config(true); setup_builder!(cheap_builder, cheap_node_config.node_config); @@ -4115,6 +4133,7 @@ async fn do_lsps2_multi_lsp_picks_cheapest(reverse_order: bool) { max_client_to_self_delay: 1024, client_trusts_lsp: true, disable_client_reserve: false, + channel_config: None, }; let expensive_node_config = random_config(true); setup_builder!(expensive_builder, expensive_node_config.node_config);