ln: remove test flag from `negotiate_anchor_zero_fee_commitments`
What changed, and why it matters
This commit removes a test-only restriction on a Lightning channel feature called 'anchor zero-fee commitments.' Previously, the feature could only be negotiated during tests; now it can be used in production code. The change itself is a feature enablement, not a direct bug fix, but it exposes a new protocol path that could carry security risks if the feature has flaws. There is no disclosed security incident or vulnerability tied to this commit in the provided materials.
Review the anchor zero-fee commitment implementation for correctness and robustness now that it is exposed in production builds. Ensure serialization compatibility is handled for downgrades and persisted configs. Monitor for any follow-up security advisories from the LDK team regarding this feature.
Security signals we found
Feature enablement: removes test-only gating for anchor zero-fee commitments
Protocol surface expansion: new channel type can now be negotiated in production
Serialization change: config field now always serialized/deserialized, affecting persisted state compatibility
No direct vulnerability or exploit code visible in diff
Evidence from the diff
The patch removes #[cfg(test)] guards from the negotiate_anchor_zero_fee_commitments field and its uses in channel.rs and channelmanager.rs, making the anchor zero-fee commitment feature available outside test builds. It also updates fuzzing seeds to reflect the changed serialized config layout. A documentation TODO about linking the config field is resolved. The commit does not alter cryptographic logic or fix a specific vulnerability; it enables a previously test-only feature for general use.
Changed components
lightning/src/util/config.rslightning/src/ln/channel.rslightning/src/ln/channelmanager.rsfuzz/src/full_stack.rsInspect captured patch +7 / −17
diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs
index eddf2ce..3fb1e4a 100644
--- a/fuzz/src/full_stack.rs
+++ b/fuzz/src/full_stack.rs
@@ -1048,7 +1048,7 @@ fn two_peer_forwarding_seed() -> Vec<u8> {
// our network key
ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test);
// config
- ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
+ ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
// new outbound connection with id 0
ext_from_hex("00", &mut test);
@@ -1502,7 +1502,7 @@ fn gossip_exchange_seed() -> Vec<u8> {
// our network key
ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test);
// config
- ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
+ ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
// new outbound connection with id 0
ext_from_hex("00", &mut test);
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 9dd0f73..dd9d394 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -13837,14 +13837,9 @@ pub(super) fn get_initial_channel_type(
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
- #[cfg(not(test))]
- let negotiate_zero_fee_commitments = false;
-
- #[cfg(test)]
- let negotiate_zero_fee_commitments =
- config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;
-
- if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
+ if config.channel_handshake_config.negotiate_anchor_zero_fee_commitments
+ && their_features.supports_anchor_zero_fee_commitments()
+ {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index f991d22..b10ed8c 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -14925,7 +14925,6 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
#[cfg(any(test, fuzzing))]
features.set_quiescence_optional();
- #[cfg(test)]
if config.channel_handshake_config.negotiate_anchor_zero_fee_commitments {
features.set_anchor_zero_fee_commitments_optional();
}
diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs
index f0c0330..656cfc4 100644
--- a/lightning/src/util/config.rs
+++ b/lightning/src/util/config.rs
@@ -219,7 +219,6 @@ pub struct ChannelHandshakeConfig {
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
/// [`DecodeError::InvalidValue`]: crate::ln::msgs::DecodeError::InvalidValue
- #[cfg(test)]
pub negotiate_anchor_zero_fee_commitments: bool,
/// The maximum number of HTLCs in-flight from our counterparty towards us at the same time.
@@ -251,7 +250,6 @@ impl Default for ChannelHandshakeConfig {
commit_upfront_shutdown_pubkey: true,
their_channel_reserve_proportional_millionths: 10_000,
negotiate_anchors_zero_fee_htlc_tx: false,
- #[cfg(test)]
negotiate_anchor_zero_fee_commitments: false,
our_max_accepted_htlcs: 50,
}
@@ -274,7 +272,6 @@ impl Readable for ChannelHandshakeConfig {
commit_upfront_shutdown_pubkey: Readable::read(reader)?,
their_channel_reserve_proportional_millionths: Readable::read(reader)?,
negotiate_anchors_zero_fee_htlc_tx: Readable::read(reader)?,
- #[cfg(test)]
negotiate_anchor_zero_fee_commitments: Readable::read(reader)?,
our_max_accepted_htlcs: Readable::read(reader)?,
})
@@ -446,6 +443,7 @@ pub enum MaxDustHTLCExposure {
/// thus never experience dust exposure changes due to feerate shifts, resulting in no
/// force-closes due to dust exposure limits), such channels will calculate their maximum
/// dust exposure using a constant feerate of 250 sat/KW when using this variant.
+ /// See [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`] to enable such channels.
///
/// # Backwards Compatibility
/// This variant only became available in LDK 0.0.116, so if you downgrade to a prior version
@@ -453,9 +451,7 @@ pub enum MaxDustHTLCExposure {
///
/// [`FeeEstimator`]: crate::chain::chaininterface::FeeEstimator
/// [`ConfirmationTarget::MaximumFeeEstimate`]: crate::chain::chaininterface::ConfirmationTarget::MaximumFeeEstimate
- //
- // TODO: link ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitment in zero fee
- // commitment doc once field is no longer behind cfg[test] flag.
+ /// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`]: ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments
FeeRateMultiplier(u64),
}
Why this scored 32/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.