Delete `TxBuilder::subtract_non_htlc_outputs`
What changed, and why it matters
This commit is a straightforward internal code cleanup in the Lightning Dev Kit's Rust implementation. It removes a helper method called subtract_non_htlc_outputs from a trait and replaces its uses with direct calls to a similar helper function. The commit message explicitly states there is no functional change, and the diff shows the new helper behaves identically to the removed method. There is no indication this fixes or introduces a security issue.
No security action needed. Treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors balance calculation logic for Lightning commitment transactions. It deletes the TxBuilder::subtract_non_htlc_outputs trait method and the corresponding SpecTxBuilder implementation, then introduces a standalone saturating_sub_anchor_outputs function with identical logic. Existing call sites are updated to use the new function. A related internal function is renamed from subtract_addl_outputs to checked_sub_anchor_outputs. The behavior—saturating subtraction of anchor output values from the funder’s balance—remains unchanged. The commit message claims this is temporary scaffolding for an upcoming larger refactor.
Changed components
lightning/src/ln/channel.rslightning/src/sign/tx_builder.rsInspect captured patch +49 / −48
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index d09e117..104e61a 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -71,7 +71,10 @@ use crate::ln::types::ChannelId;
use crate::offers::static_invoice::StaticInvoice;
use crate::routing::gossip::NodeId;
use crate::sign::ecdsa::EcdsaChannelSigner;
-use crate::sign::tx_builder::{ChannelStats, HTLCAmountDirection, SpecTxBuilder, TxBuilder};
+use crate::sign::tx_builder::{
+ saturating_sub_anchor_outputs, ChannelStats, HTLCAmountDirection, SpecTxBuilder,
+ TxBuilder,
+};
use crate::sign::{ChannelSigner, EntropySource, NodeSigner, Recipient, SignerProvider};
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
use crate::types::payment::{PaymentHash, PaymentPreimage};
@@ -5914,8 +5917,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
);
let htlc_stats = context.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate);
- // Subtract any non-HTLC outputs from the local and remote balances
- let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = SpecTxBuilder {}.subtract_non_htlc_outputs(
+ // Subtract anchor outputs from the local and remote balances
+ let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = saturating_sub_anchor_outputs(
funding.is_outbound(),
funding.value_to_self_msat.saturating_sub(htlc_stats.pending_outbound_htlcs_value_msat),
(funding.get_value_satoshis() * 1000).checked_sub(funding.value_to_self_msat).unwrap().saturating_sub(htlc_stats.pending_inbound_htlcs_value_msat),
diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs
index 3b34fb8..9c2942f 100644
--- a/lightning/src/sign/tx_builder.rs
+++ b/lightning/src/sign/tx_builder.rs
@@ -117,7 +117,7 @@ fn commit_plus_htlc_tx_fees_msat(
(total_fees_msat, extra_accepted_htlc_total_fees_msat)
}
-fn subtract_addl_outputs(
+pub(crate) fn checked_sub_anchor_outputs(
is_outbound_from_holder: bool, value_to_self_after_htlcs_msat: u64,
value_to_remote_after_htlcs_msat: u64, channel_type: &ChannelTypeFeatures,
) -> Result<(u64, u64), ()> {
@@ -127,13 +127,6 @@ fn subtract_addl_outputs(
0
};
- // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
- // than or equal to `total_anchors_sat`.
- //
- // This is because when the remote party sends an `update_fee` message, we build the new
- // commitment transaction *before* checking whether the remote party's balance is enough to
- // cover the total anchor sum.
-
if is_outbound_from_holder {
Ok((
value_to_self_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?,
@@ -147,6 +140,29 @@ fn subtract_addl_outputs(
}
}
+pub(crate) fn saturating_sub_anchor_outputs(
+ is_outbound_from_holder: bool, value_to_self_after_htlcs: u64,
+ value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
+) -> (u64, u64) {
+ let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
+ } else {
+ 0
+ };
+
+ if is_outbound_from_holder {
+ (
+ value_to_self_after_htlcs.saturating_sub(total_anchors_sat * 1000),
+ value_to_remote_after_htlcs,
+ )
+ } else {
+ (
+ value_to_self_after_htlcs,
+ value_to_remote_after_htlcs.saturating_sub(total_anchors_sat * 1000),
+ )
+ }
+}
+
fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 {
// When calculating our exposure to dust HTLCs, we assume that the channel feerate
// may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
@@ -192,8 +208,16 @@ fn get_next_commitment_stats(
value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?;
// Subtract the anchors from the channel funder
+
+ // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
+ // than or equal to `total_anchors_sat`.
+ //
+ // This is because when the remote party sends an `update_fee` message, we build the new
+ // commitment transaction *before* checking whether the remote party's balance is enough to
+ // cover the total anchor sum.
+
let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) =
- subtract_addl_outputs(
+ checked_sub_anchor_outputs(
is_outbound_from_holder,
value_to_holder_after_htlcs_msat,
value_to_counterparty_after_htlcs_msat,
@@ -270,10 +294,6 @@ pub(crate) trait TxBuilder {
dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
channel_type: &ChannelTypeFeatures,
) -> Result<ChannelStats, ()>;
- fn subtract_non_htlc_outputs(
- &self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64,
- value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
- ) -> (u64, u64);
fn build_commitment_transaction<L: Logger>(
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
@@ -307,36 +327,6 @@ impl TxBuilder for SpecTxBuilder {
Ok(ChannelStats { commitment_stats })
}
- fn subtract_non_htlc_outputs(
- &self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64,
- value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
- ) -> (u64, u64) {
- let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
- ANCHOR_OUTPUT_VALUE_SATOSHI * 2
- } else {
- 0
- };
-
- let mut local_balance_before_fee_msat = value_to_self_after_htlcs;
- let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs;
-
- // We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
- // than or equal to `total_anchors_sat`.
- //
- // This is because when the remote party sends an `update_fee` message, we build the new
- // commitment transaction *before* checking whether the remote party's balance is enough to
- // cover the total anchor sum.
-
- if is_outbound_from_holder {
- local_balance_before_fee_msat =
- local_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000);
- } else {
- remote_balance_before_fee_msat =
- remote_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000);
- }
-
- (local_balance_before_fee_msat, remote_balance_before_fee_msat)
- }
fn build_commitment_transaction<L: Logger>(
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
@@ -402,8 +392,16 @@ impl TxBuilder for SpecTxBuilder {
.unwrap()
.checked_sub(remote_htlc_total_msat)
.unwrap();
- let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = self
- .subtract_non_htlc_outputs(
+
+ // We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
+ // than or equal to `total_anchors_sat`.
+ //
+ // This is because when the remote party sends an `update_fee` message, we build the new
+ // commitment transaction *before* checking whether the remote party's balance is enough to
+ // cover the total anchor sum.
+
+ let (local_balance_before_fee_msat, remote_balance_before_fee_msat) =
+ saturating_sub_anchor_outputs(
channel_parameters.is_outbound_from_holder,
value_to_self_after_htlcs_msat,
value_to_remote_after_htlcs_msat,
Why this scored 12/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.