From fdb891c1459a145f74bad8e0cf13da3636229646 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 30 Sep 2026 04:08:32 +0200 Subject: [PATCH 1/2] fix: accept incoming payments above 10% of an unannounced channel's value A node that cannot announce channels only accepts unannounced ones, so LDK's default of 10% of the channel value as maximum inbound HTLC value in flight capped every single incoming payment far below the inbound capacity. Set it to 100% for such nodes, as already done for unannounced channels we open ourselves. The integration test opens a channel to a node without a node alias and pays it 9% and 20% of the channel value; the second payment fails without the change. --- src/config.rs | 7 ++++ tests/integration_tests_rust.rs | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/config.rs b/src/config.rs index c271b71200..afc3eed194 100644 --- a/src/config.rs +++ b/src/config.rs @@ -679,6 +679,13 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig { user_config.accept_forwards_to_priv_channels = false; user_config.channel_handshake_config.announce_for_forwarding = false; user_config.channel_handshake_limits.force_announced_channel_preference = true; + + // A node that can't announce channels only accepts unannounced ones. LDK's default of 10% + // of the channel value in flight would cap every single incoming payment far below the + // inbound capacity, so we allow the counterparty to use all of it, as we already do for + // unannounced channels we open ourselves. + user_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = + 100; } user_config diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 631de346b7..2a999eea21 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -3588,3 +3588,65 @@ async fn electrum_connection_timeout_above_max_is_capped() { node.sync_wallets().unwrap(); node.stop().unwrap(); } + +// A node that cannot announce channels (no node alias, as in the mobile apps) only accepts +// unannounced channels. It must let the counterparty put the whole channel value in flight +// instead of LDK's default of 10%, otherwise a single payment above that limit cannot be +// received even though the channel has the capacity. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn inbound_htlc_in_flight_limit_for_unannounced_node() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = TestChainSource::Esplora(&electrsd); + + let config_a = random_config(true); + let node_a = setup_node(&chain_source, config_a, None); + + let mut config_b = random_config(true); + config_b.node_config.node_alias = None; + let node_b = setup_node(&chain_source, config_b, None); + + let addr_a = node_a.onchain_payment().new_address().unwrap(); + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![addr_a], + Amount::from_sat(2_000_000), + ) + .await; + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + let channel_value_sat = 1_000_000; + open_channel(&node_a, &node_b, channel_value_sat, false, &electrsd).await; + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + let description = + Bolt11InvoiceDescription::Direct(Description::new(String::from("inbound")).unwrap()); + + // 9% of the channel value is below LDK's default limit and always worked. + let below_limit_msat = channel_value_sat * 1000 * 9 / 100; + let invoice = node_b + .bolt11_payment() + .receive(below_limit_msat, &description.clone().into(), 9217) + .unwrap(); + let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap(); + expect_payment_successful_event!(node_a, Some(payment_id), None); + expect_payment_received_event!(node_b, below_limit_msat); + + // 20% of the channel value used to fail because node B advertised a 10% in-flight limit. + let above_limit_msat = channel_value_sat * 1000 * 20 / 100; + let invoice = node_b + .bolt11_payment() + .receive(above_limit_msat, &description.clone().into(), 9217) + .unwrap(); + let payment_id = node_a + .bolt11_payment() + .send(&invoice, None) + .expect("a payment above 10% of the channel value must be routable"); + expect_payment_successful_event!(node_a, Some(payment_id), None); + expect_payment_received_event!(node_b, above_limit_msat); +} From 4a7e44d1d7bf1b963154bdcd0a00f98a919eecfd Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 1 Oct 2026 23:36:18 +0200 Subject: [PATCH 2/2] test: cover the inbound htlc limit for nodes that cannot announce The default_user_config branch that sets the maximum inbound HTLC value in flight to 100% now has focused unit tests: a node without alias and listening addresses, one without listening addresses, one without alias, and a node that can announce channels, which keeps LDK's default. --- src/config.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index afc3eed194..b1068b9582 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1021,7 +1021,11 @@ pub enum AsyncPaymentsRole { mod tests { use std::str::FromStr; - use super::{may_announce_channel, AnnounceError, Config, NodeAlias, SocketAddress}; + use lightning::util::config::UserConfig; + + use super::{ + default_user_config, may_announce_channel, AnnounceError, Config, NodeAlias, SocketAddress, + }; #[test] fn node_announce_channel() { @@ -1069,6 +1073,46 @@ mod tests { assert!(may_announce_channel(&node_config).is_ok()); } + #[test] + fn inbound_htlc_value_in_flight_limit_follows_announce_ability() { + let default_percent = UserConfig::default() + .channel_handshake_config + .max_inbound_htlc_value_in_flight_percent_of_channel; + let percent_of = |config: &Config| { + default_user_config(config) + .channel_handshake_config + .max_inbound_htlc_value_in_flight_percent_of_channel + }; + let alias = NodeAlias([1u8; 32]); + let address = SocketAddress::from_str("localhost:8000").unwrap(); + + // Neither alias nor listening addresses: the node can't announce, so the whole channel + // value may be in flight. + let mut node_config = Config::default(); + assert!(may_announce_channel(&node_config).is_err()); + assert_eq!(percent_of(&node_config), 100); + + // Missing listening addresses only. + node_config.node_alias = Some(alias); + assert_eq!( + may_announce_channel(&node_config), + Err(AnnounceError::MissingListeningAddresses) + ); + assert_eq!(percent_of(&node_config), 100); + + // Missing node alias only. + node_config.node_alias = None; + node_config.listening_addresses = Some(vec![address]); + assert_eq!(may_announce_channel(&node_config), Err(AnnounceError::MissingNodeAlias)); + assert_eq!(percent_of(&node_config), 100); + + // A node that can announce channels keeps LDK's default. + node_config.node_alias = Some(alias); + assert!(may_announce_channel(&node_config).is_ok()); + assert_eq!(percent_of(&node_config), default_percent); + assert!(default_percent < 100); + } + #[test] fn onchain_wallet_account_helpers() { use super::{AddressType, OnchainWalletAccount, MAX_ONCHAIN_WALLET_ACCOUNT_INDEX};