Account for UTXO base weight in anchor reserve checks
What changed, and why it matters
This commit fixes a calculation in how Lightning Dev Kit estimates whether leftover on-chain funds (UTXOs) are enough to support opening additional 'anchor' channels. Previously, the code only subtracted the fee for the cryptographic proof needed to spend a UTXO, but forgot to also subtract the fixed base size every transaction input adds. As a result, a UTXO worth just slightly less than the advertised reserve could be wrongly counted as sufficient, potentially leading the node to believe it could open more anchor channels than it actually could afford to fund later. The fix adds that base input weight to the fee estimate, and a new test confirms a borderline UTXO is now rejected.
Treat as a low-to-moderate correctness fix. Review related reserve and fee-estimation logic for similar omissions of BASE_INPUT_WEIGHT, and ensure the new regression test passes. No immediate emergency response is indicated, but node operators relying on anchor channel reserve estimates should update to avoid capacity-planning errors.
Security signals we found
Incorrect fee/weight accounting in reserve calculation
Potential overestimation of available anchor channel capacity
Fix includes regression test for boundary condition
Third-party credited discovery ('Project Loupe')
Small, targeted change in single file
Evidence from the diff
In get_supportable_anchor_channels, the code computes spend_fee as upper_bound_fee_rate.fee_wu(BASE_INPUT_WEIGHT + utxo.satisfaction_weight) rather than just utxo.satisfaction_weight. The resulting amount (UTXO value minus spend fee) is then compared against reserve_per_channel. This prevents UTXOs whose effective spendable value falls below the per-channel reserve from being counted as whole reserve contributors. The change is accompanied by a regression test using a P2WPKH UTXO valued at reserve - 1 sat and asserting zero supportable channels.
Changed components
lightning/src/util/anchor_channel_reserves.rsget_supportable_anchor_channelsInspect captured patch +13 / −4
diff --git a/lightning/src/util/anchor_channel_reserves.rs b/lightning/src/util/anchor_channel_reserves.rs
index 000f543..d4db63a 100644
--- a/lightning/src/util/anchor_channel_reserves.rs
+++ b/lightning/src/util/anchor_channel_reserves.rs
@@ -24,7 +24,7 @@ use crate::chain::chaininterface::FeeEstimator;
use crate::chain::chainmonitor::ChainMonitor;
use crate::chain::chainmonitor::Persist;
use crate::chain::Filter;
-use crate::ln::chan_utils::max_htlcs;
+use crate::ln::chan_utils::{max_htlcs, BASE_INPUT_WEIGHT};
use crate::ln::channelmanager::AChannelManager;
use crate::prelude::new_hash_set;
use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -240,11 +240,11 @@ pub fn get_supportable_anchor_channels(
let mut total_fractional_amount = Amount::from_sat(0);
let mut num_whole_utxos = 0;
for utxo in utxos {
- let satisfaction_fee = context
+ let spend_fee = context
.upper_bound_fee_rate
- .fee_wu(Weight::from_wu(utxo.satisfaction_weight))
+ .fee_wu(Weight::from_wu(BASE_INPUT_WEIGHT + utxo.satisfaction_weight))
.unwrap_or(Amount::MAX);
- let amount = utxo.output.value.checked_sub(satisfaction_fee).unwrap_or(Amount::MIN);
+ let amount = utxo.output.value.checked_sub(spend_fee).unwrap_or(Amount::MIN);
if amount >= reserve_per_channel {
num_whole_utxos += 1;
} else {
@@ -370,6 +370,15 @@ mod test {
assert_eq!(get_supportable_anchor_channels(&context, utxos.as_slice()), 3);
}
+ #[test]
+ fn test_get_supportable_anchor_channels_accounts_for_input_weight() {
+ let context = AnchorChannelReserveContext::default();
+ let reserve = get_reserve_per_channel(&context);
+ let utxo = make_p2wpkh_utxo(reserve - Amount::from_sat(1));
+
+ assert_eq!(get_supportable_anchor_channels(&context, &[utxo]), 0);
+ }
+
#[test]
fn test_anchor_output_spend_transaction_weight() {
// Example with smaller signatures:
Why this scored 44/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.