Set `AnchorDescriptor` output value to CSV and P2A anchor amounts
What changed, and why it matters
This commit changes how Lightning anchor output amounts are tracked and set in commitment transactions. Previously, anchor outputs used a fixed value constant. The patch makes the anchor value dynamic, reading it from the actual commitment transaction output. This is part of support for a newer Lightning feature called 'zero-fee commitments' where anchor amounts can vary. The change appears to be a correctness fix to ensure the wallet software constructs follow-up transactions using the real anchor value rather than a hardcoded one, which could otherwise cause transaction creation failures or fee estimation errors.
Review as part of normal code review. Validate that the dynamic anchor value is correctly propagated in all bump-transaction paths and that serialization/deserialization of AnchorDescriptor handles the new `value` field compatibly. Consider regression tests covering both keyed and shared P2A anchor channels.
Security signals we found
Changes anchor output value from hardcoded constant to transaction-derived value
Adds support for variable-value P2A anchors in zero-fee commitment transactions
Removes fixed `ANCHOR_OUTPUT_VALUE_SATOSHI` usage in `AnchorDescriptor::tx_out`
Distinguishes keyed anchor vs shared P2A anchor script types based on channel features
Touches on-chain transaction construction and fee bumping paths
Evidence from the diff
The patch modifies rust-lightning’s anchor handling across channelmonitor.rs, onchaintx.rs, bump_transaction/mod.rs, and chan_utils.rs. Key changes: (1) AnchorDescriptor gains a value: Amount field, populated from commitment_tx.output[anchor_output_idx].value instead of the constant ANCHOR_OUTPUT_VALUE_SATOSHI. (2) chan_utils::get_keyed_anchor_output is removed and replaced with inline logic in onchaintx.rs that distinguishes between keyed anchors (P2WSH) for zero-fee HTLC tx channels and shared P2A anchors for zero-fee commitment channels. (3) P2A_SCRIPT constant is removed in favor of shared_anchor_script_pubkey(). (4) CommitmentTransaction now sets the P2A anchor value to min(P2A_MAX_VALUE, trimmed_sum_sat) rather than a fixed amount. These changes align anchor spending/bumping logic with the actual on-chain output values for the newer zero-fee commitment protocol.
Changed components
lightning/src/chain/channelmonitor.rslightning/src/chain/onchaintx.rslightning/src/events/bump_transaction/mod.rslightning/src/ln/chan_utils.rsInspect captured patch +21 / −20
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index 852ceab..d882262 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -4416,8 +4416,6 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
counterparty_node_id,
claim_id,
package_target_feerate_sat_per_1000_weight,
- commitment_tx,
- commitment_tx_fee_satoshis,
anchor_descriptor: AnchorDescriptor {
channel_derivation_parameters: ChannelDerivationParameters {
keys_id: self.channel_keys_id,
@@ -4428,8 +4426,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
txid: commitment_txid,
vout: anchor_output_idx,
},
+ value: commitment_tx.output[anchor_output_idx as usize].value,
},
pending_htlcs: pending_nondust_htlcs,
+ commitment_tx,
+ commitment_tx_fee_satoshis,
}));
},
ClaimEvent::BumpHTLC {
diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 0db5e2b..0d70f9d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -30,7 +30,8 @@ use crate::chain::package::{PackageSolvingData, PackageTemplate};
use crate::chain::transaction::MaybeSignedTransaction;
use crate::chain::ClaimId;
use crate::ln::chan_utils::{
- self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction,
+ get_keyed_anchor_redeemscript, shared_anchor_script_pubkey, ChannelTransactionParameters,
+ HTLCOutputInCommitment, HolderCommitmentTransaction,
};
use crate::ln::msgs::DecodeError;
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, HTLCDescriptor, SignerProvider};
@@ -677,7 +678,16 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
let channel_parameters = output.channel_parameters.as_ref()
.unwrap_or(self.channel_parameters());
let funding_pubkey = &channel_parameters.holder_pubkeys.funding_pubkey;
- match chan_utils::get_keyed_anchor_output(&tx, funding_pubkey) {
+ let script_pubkey = if channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ get_keyed_anchor_redeemscript(funding_pubkey).to_p2wsh()
+ } else {
+ debug_assert!(channel_parameters.channel_type_features.supports_anchor_zero_fee_commitments());
+ shared_anchor_script_pubkey()
+ };
+ let anchor_output = tx.output.iter().enumerate()
+ .find(|(_, txout)| txout.script_pubkey == script_pubkey)
+ .map(|(idx, txout)| (idx as u32, txout));
+ match anchor_output {
// An anchor output was found, so we should yield a funding event externally.
Some((idx, _)) => {
// TODO: Use a lower confirmation target when both our and the
diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs
index fb872d6..c5b7885 100644
--- a/lightning/src/events/bump_transaction/mod.rs
+++ b/lightning/src/events/bump_transaction/mod.rs
@@ -26,7 +26,6 @@ use crate::ln::chan_utils::{
shared_anchor_script_pubkey, HTLCOutputInCommitment, ANCHOR_INPUT_WITNESS_WEIGHT,
HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT,
};
-use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::types::ChannelId;
use crate::prelude::*;
use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -64,6 +63,8 @@ pub struct AnchorDescriptor {
/// The transaction input's outpoint corresponding to the commitment transaction's anchor
/// output.
pub outpoint: OutPoint,
+ /// Zero-fee-commitment anchors have variable value, which is tracked here.
+ pub value: Amount,
}
impl AnchorDescriptor {
@@ -80,7 +81,7 @@ impl AnchorDescriptor {
assert!(tx_params.channel_type_features.supports_anchor_zero_fee_commitments());
shared_anchor_script_pubkey()
};
- TxOut { script_pubkey, value: Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI) }
+ TxOut { script_pubkey, value: self.value }
}
/// Returns the unsigned transaction input spending the anchor output in the commitment
@@ -1029,6 +1030,7 @@ mod tests {
};
use crate::io::Cursor;
use crate::ln::chan_utils::ChannelTransactionParameters;
+ use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::sign::KeysManager;
use crate::types::features::ChannelTypeFeatures;
use crate::util::ser::Readable;
@@ -1147,6 +1149,7 @@ mod tests {
transaction_parameters,
},
outpoint: OutPoint { txid: Txid::from_byte_array([42; 32]), vout: 0 },
+ value: Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI),
},
pending_htlcs: Vec::new(),
});
diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs
index 85bb51e..f7aaf39 100644
--- a/lightning/src/ln/chan_utils.rs
+++ b/lightning/src/ln/chan_utils.rs
@@ -89,9 +89,6 @@ pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 114;
#[cfg(not(feature = "grind_signatures"))]
pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 115;
-/// The P2A scriptpubkey
-pub const P2A_SCRIPT: &[u8] = &[0x51, 0x02, 0x4e, 0x73];
-
/// The maximum value of the P2A anchor
pub const P2A_MAX_VALUE: u64 = 240;
@@ -978,16 +975,6 @@ pub fn get_keyed_anchor_redeemscript(funding_pubkey: &PublicKey) -> ScriptBuf {
.into_script()
}
-/// Locates the output with a keyed anchor (non-zero-fee-commitments) script paying to
-/// `funding_pubkey` within `commitment_tx`.
-#[rustfmt::skip]
-pub(crate) fn get_keyed_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> {
- let anchor_script = get_keyed_anchor_redeemscript(funding_pubkey).to_p2wsh();
- commitment_tx.output.iter().enumerate()
- .find(|(_, txout)| txout.script_pubkey == anchor_script)
- .map(|(idx, txout)| (idx as u32, txout))
-}
-
/// Returns the witness required to satisfy and spend a keyed anchor (non-zero-fee-commitments)
/// input.
pub fn build_keyed_anchor_input_witness(
@@ -1891,7 +1878,7 @@ impl CommitmentTransaction {
// These subtractions panic on underflow, but this should never happen
let trimmed_sum_sat = channel_value_satoshis - nondust_htlcs_value_sum_sat - to_broadcaster_value_sat - to_countersignatory_value_sat;
insert_non_htlc_output(TxOut {
- script_pubkey: ScriptBuf::from_bytes(P2A_SCRIPT.to_vec()),
+ script_pubkey: shared_anchor_script_pubkey(),
value: cmp::min(Amount::from_sat(P2A_MAX_VALUE), trimmed_sum_sat),
});
}
Why this scored 57/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.