From 9daa9856a203b21b40125bd51b1bbfee4f3c9bcf Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Tue, 21 Oct 2025 23:40:30 +0100 Subject: [PATCH] use `lightning::util::anchor_channel_reserves` for improved anchor reserve estimation Replaced flat fee-based reserve logic with estimation using `get_reserve_per_channel`, following changes introduced in lightningdevkit/rust-lightning#3487. --- bindings/ldk_node.udl | 1 - src/config.rs | 26 +------------------------- src/event.rs | 6 +++++- src/lib.rs | 27 +++++++++++++++++---------- src/liquidity.rs | 6 +++++- tests/common/mod.rs | 8 +++++--- tests/integration_tests_rust.rs | 10 +++++----- 7 files changed, 38 insertions(+), 46 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c881dbe09..5867984b6 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -17,7 +17,6 @@ dictionary Config { dictionary AnchorChannelsConfig { sequence trusted_peers_no_reserve; - u64 per_channel_reserve_sats; }; dictionary BackgroundSyncConfig { diff --git a/src/config.rs b/src/config.rs index 6c9d1640a..c2cfa8ea2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,7 +27,6 @@ const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80; const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3; -const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000; /// The default log level. pub const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug; @@ -233,7 +232,6 @@ impl Default for Config { /// | Parameter | Value | /// |----------------------------|--------| /// | `trusted_peers_no_reserve` | [] | -/// | `per_channel_reserve_sats` | 25000 | /// /// /// [BOLT 3]: https://github.com/lightning/bolts/blob/master/03-transactions.md#htlc-timeout-and-htlc-success-transactions @@ -249,33 +247,11 @@ pub struct AnchorChannelsConfig { /// required Anchor spending transactions confirmed on-chain is potentially insecure /// as the channel may not be closed if they refuse to do so. pub trusted_peers_no_reserve: Vec, - /// The amount of satoshis per anchors-negotiated channel with an untrusted peer that we keep - /// as an emergency reserve in our on-chain wallet. - /// - /// This allows for having the required Anchor output spending and HTLC transactions confirmed - /// when the channel is closed. - /// - /// If the channel peer is not marked as trusted via - /// [`AnchorChannelsConfig::trusted_peers_no_reserve`], we will always try to spend the Anchor - /// outputs with *any* on-chain funds available, i.e., the total reserve value as well as any - /// spendable funds available in the on-chain wallet. Therefore, this per-channel multiplier is - /// really a emergency reserve that we maintain at all time to reduce reduce the risk of - /// insufficient funds at time of a channel closure. To this end, we will refuse to open - /// outbound or accept inbound channels if we don't have sufficient on-chain funds available to - /// cover the additional reserve requirement. - /// - /// **Note:** Depending on the fee market at the time of closure, this reserve amount might or - /// might not suffice to successfully spend the Anchor output and have the HTLC transactions - /// confirmed on-chain, i.e., you may want to adjust this value accordingly. - pub per_channel_reserve_sats: u64, } impl Default for AnchorChannelsConfig { fn default() -> Self { - Self { - trusted_peers_no_reserve: Vec::new(), - per_channel_reserve_sats: DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS, - } + Self { trusted_peers_no_reserve: Vec::new() } } } diff --git a/src/event.rs b/src/event.rs index 6f0ed8e09..0ba72968c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -22,6 +22,9 @@ use lightning::impl_writeable_tlv_based_enum; use lightning::ln::channelmanager::PaymentId; use lightning::ln::types::ChannelId; use lightning::routing::gossip::NodeId; +use lightning::util::anchor_channel_reserves::{ + get_reserve_per_channel, AnchorChannelReserveContext, +}; use lightning::util::config::{ ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, }; @@ -1177,7 +1180,8 @@ where { 0 } else { - anchor_channels_config.per_channel_reserve_sats + get_reserve_per_channel(&AnchorChannelReserveContext::default()) + .to_sat() }; if spendable_amount_sats < required_amount_sats { diff --git a/src/lib.rs b/src/lib.rs index d2222d949..30add6ff8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,9 @@ use lightning::ln::channelmanager::PaymentId; use lightning::ln::funding::SpliceContribution; use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; +use lightning::util::anchor_channel_reserves::{ + get_reserve_per_channel, AnchorChannelReserveContext, +}; use lightning::util::persist::KVStoreSync; use lightning_background_processor::process_events_async; use liquidity::{LSPS1Liquidity, LiquiditySource}; @@ -1181,7 +1184,7 @@ impl Node { if init_features.requires_anchors_zero_fee_htlc_tx() && !c.trusted_peers_no_reserve.contains(peer_node_id) { - c.per_channel_reserve_sats + get_reserve_per_channel(&AnchorChannelReserveContext::default()).to_sat() } else { 0 } @@ -1208,13 +1211,11 @@ impl Node { /// channel counterparty on channel open. This can be useful to start out with the balance not /// entirely shifted to one side, therefore allowing to receive payments from the getgo. /// - /// If Anchor channels are enabled, this will ensure the configured - /// [`AnchorChannelsConfig::per_channel_reserve_sats`] is available and will be retained before - /// opening the channel. + /// If Anchor channels are enabled, this will ensure the reserved amount per + /// channel is available and will be retained before opening the channel. /// /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// - /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats pub fn open_channel( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, channel_config: Option, @@ -1243,13 +1244,11 @@ impl Node { /// channel counterparty on channel open. This can be useful to start out with the balance not /// entirely shifted to one side, therefore allowing to receive payments from the getgo. /// - /// If Anchor channels are enabled, this will ensure the configured - /// [`AnchorChannelsConfig::per_channel_reserve_sats`] is available and will be retained before - /// opening the channel. + /// If Anchor channels are enabled, this will ensure the reserved amount per + /// channel is available and will be retained before opening the channel. /// /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// - /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats pub fn open_announced_channel( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, channel_config: Option, @@ -1862,6 +1861,8 @@ impl_writeable_tlv_based!(NodeMetrics, { pub(crate) fn total_anchor_channels_reserve_sats( channel_manager: &ChannelManager, config: &Config, ) -> u64 { + let reserve_sat_per_channel = get_anchor_reserve_per_channel(); + config.anchor_channels_config.as_ref().map_or(0, |anchor_channels_config| { channel_manager .list_channels() @@ -1875,6 +1876,12 @@ pub(crate) fn total_anchor_channels_reserve_sats( .map_or(false, |t| t.requires_anchors_zero_fee_htlc_tx()) }) .count() as u64 - * anchor_channels_config.per_channel_reserve_sats + * reserve_sat_per_channel }) } + +/// Returns the configured anchor channel reserve per channel in satoshis. +pub fn get_anchor_reserve_per_channel() -> u64 { + let context = AnchorChannelReserveContext::default(); + get_reserve_per_channel(&context).to_sat() +} diff --git a/src/liquidity.rs b/src/liquidity.rs index 2151110b6..7ae6075df 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -21,6 +21,9 @@ use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA}; use lightning::ln::msgs::SocketAddress; use lightning::ln::types::ChannelId; use lightning::routing::router::{RouteHint, RouteHintHop}; +use lightning::util::anchor_channel_reserves::{ + get_reserve_per_channel, AnchorChannelReserveContext, +}; use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees}; use lightning_liquidity::events::LiquidityEvent; use lightning_liquidity::lsps0::ser::{LSPSDateTime, LSPSRequestId}; @@ -752,7 +755,8 @@ where if init_features.requires_anchors_zero_fee_htlc_tx() && !c.trusted_peers_no_reserve.contains(&their_network_key) { - c.per_channel_reserve_sats + get_reserve_per_channel(&AnchorChannelReserveContext::default()) + .to_sat() } else { 0 } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 96f58297c..3521c3f89 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -687,8 +687,9 @@ pub(crate) async fn do_channel_full_cycle( let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); - let premine_amount_sat = if expect_anchor_channel { 2_125_000 } else { 2_100_000 }; + let anchor_reserve = if expect_anchor_channel { get_anchor_reserve_per_channel() } else { 0 }; + let premine_amount_sat = 2_100_000 + anchor_reserve; premine_and_distribute_funds( &bitcoind, electrsd, @@ -773,7 +774,8 @@ pub(crate) async fn do_channel_full_cycle( ); let onchain_fee_buffer_sat = 5000; - let node_a_anchor_reserve_sat = if expect_anchor_channel { 25_000 } else { 0 }; + let node_a_anchor_reserve_sat = + if expect_anchor_channel { get_anchor_reserve_per_channel() } else { 0 }; let node_a_upper_bound_sat = premine_amount_sat - node_a_anchor_reserve_sat - funding_amount_sat; let node_a_lower_bound_sat = premine_amount_sat @@ -794,7 +796,7 @@ pub(crate) async fn do_channel_full_cycle( { 0 } else { - 25_000 + get_anchor_reserve_per_channel() }; assert_eq!( node_b.list_balances().spendable_onchain_balance_sats, diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 4e94dd044..49e5f31f7 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -33,7 +33,7 @@ use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, UnifiedPaymentResult, }; -use ldk_node::{Builder, Event, NodeError}; +use ldk_node::{get_anchor_reserve_per_channel, Builder, Event, NodeError}; use lightning::ln::channelmanager::PaymentId; use lightning::routing::gossip::{NodeAlias, NodeId}; use lightning::routing::router::RouteParametersConfig; @@ -331,7 +331,7 @@ async fn onchain_send_receive() { let unchecked_address = Address::::from_str(static_address).unwrap(); let addr_c = unchecked_address.assume_checked(); - let premine_amount_sat = 1_100_000; + let premine_amount_sat = 2_100_000; premine_and_distribute_funds( &bitcoind.client, &electrsd.client, @@ -364,7 +364,7 @@ async fn onchain_send_receive() { } let channel_amount_sat = 1_000_000; - let reserve_amount_sat = 25_000; + let reserve_amount_sat = get_anchor_reserve_per_channel(); open_channel(&node_b, &node_a, channel_amount_sat, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; @@ -529,8 +529,8 @@ async fn onchain_send_all_retains_reserve() { let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); - let premine_amount_sat = 1_000_000; - let reserve_amount_sat = 25_000; + let premine_amount_sat = 2_000_000; + let reserve_amount_sat = get_anchor_reserve_per_channel(); let onchain_fee_buffer_sat = 1000; premine_and_distribute_funds( &bitcoind.client,