From e7029bfbfa1dabf49b819b9af782fc93f62a183f 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 | 9 ++++++--- tests/integration_tests_rust.rs | 9 +++++---- 7 files changed, 39 insertions(+), 45 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index bd1e4fc43..9f3d4cdc8 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 d221dd6c3..bda7ee6fc 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; @@ -219,7 +218,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 @@ -235,33 +233,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 db6ef13f1..7070f8c5b 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, }; @@ -1108,7 +1111,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 f07b2def3..8d27e8caa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,9 @@ use lightning::ln::channel_state::ChannelShutdownState; use lightning::ln::channelmanager::PaymentId; 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}; @@ -1069,7 +1072,7 @@ impl Node { if init_features.requires_anchors_zero_fee_htlc_tx() && !c.trusted_peers_no_reserve.contains(&node_id) { - c.per_channel_reserve_sats + get_reserve_per_channel(&AnchorChannelReserveContext::default()).to_sat() } else { 0 } @@ -1132,13 +1135,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, @@ -1167,13 +1168,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, @@ -1580,6 +1579,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() @@ -1593,6 +1594,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 81d48e530..56b4d865d 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -20,6 +20,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}; @@ -672,7 +675,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 4d02895c7..e14658b5e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -29,6 +29,7 @@ use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD}; use electrsd::{corepc_node, ElectrsD}; use electrum_client::ElectrumApi; use ldk_node::config::{AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig}; +use ldk_node::get_anchor_reserve_per_channel; use ldk_node::io::sqlite_store::SqliteStore; use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; use ldk_node::{ @@ -640,8 +641,9 @@ pub(crate) 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, @@ -725,7 +727,8 @@ pub(crate) 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 @@ -746,7 +749,7 @@ pub(crate) 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 804bba876..e8a7bee78 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -26,6 +26,7 @@ use common::{ setup_two_nodes, wait_for_tx, TestChainSource, TestSyncStore, }; use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig}; +use ldk_node::get_anchor_reserve_per_channel; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, @@ -317,7 +318,7 @@ 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, @@ -349,7 +350,7 @@ 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); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); @@ -514,8 +515,8 @@ 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,