Introduce `TxBuilder::get_channel_stats`
What changed, and why it matters
This commit is a straightforward internal code reorganization in the Lightning Dev Kit's transaction-building code. It renames and restructures a method so that a new wrapper, `get_channel_stats`, returns the same underlying commitment statistics inside a new container struct. The commit message explicitly states there are no functional changes beyond the API rename, and the diff shows only mechanical renames and field-access adjustments (e.g., `stats.nondust_htlc_count` becomes `stats.commitment_stats.nondust_htlc_count`). There is no indication of a security fix, vulnerability, or behavior change.
No security action required. Treat as a normal code-quality refactor. If reviewing downstream consumers, verify they update to the renamed `get_channel_stats` API and the new `ChannelStats` struct shape.
Security signals we found
No functional logic change
Pure refactor: method rename and struct wrapping
No new input validation or boundary checks introduced
No changes to cryptographic, signing, or network-handling code
Commit message explicitly states 'no functional changes'
Evidence from the diff
The patch refactors TxBuilder::get_next_commitment_stats into a private free function get_next_commitment_stats and exposes a new public trait method TxBuilder::get_channel_stats that wraps the result in a new ChannelStats { commitment_stats: NextCommitmentStats } struct. All call sites in channel.rs are updated to call get_channel_stats and access fields through .commitment_stats. The actual fee, dust, balance, and HTLC-count computations are unchanged. This is purely an API/structural refactor.
Changed components
lightning/src/sign/tx_builder.rslightning/src/ln/channel.rsInspect captured patch +189 / −155
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 2416981..509202b 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -71,7 +71,7 @@ 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::{HTLCAmountDirection, NextCommitmentStats, SpecTxBuilder, TxBuilder};
+use crate::sign::tx_builder::{ChannelStats, HTLCAmountDirection, SpecTxBuilder, TxBuilder};
use crate::sign::{ChannelSigner, EntropySource, NodeSigner, Recipient, SignerProvider};
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
use crate::types::payment::{PaymentHash, PaymentPreimage};
@@ -4862,7 +4862,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
feerate_per_kw: u32, dust_exposure_limiting_feerate: Option<u32>,
- ) -> Result<NextCommitmentStats, ()> {
+ ) -> Result<ChannelStats, ()> {
let next_commitment_htlcs = self.get_next_commitment_htlcs(
true,
htlc_candidate,
@@ -4870,7 +4870,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
);
let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(true, funding);
- let ret = SpecTxBuilder {}.get_next_commitment_stats(
+ let local_stats = SpecTxBuilder {}.get_channel_stats(
true,
funding.is_outbound(),
funding.get_value_satoshis(),
@@ -4888,12 +4888,12 @@ impl<SP: SignerProvider> ChannelContext<SP> {
if addl_nondust_htlc_count == 0 {
*funding.next_local_fee.lock().unwrap() = PredictedNextFee {
predicted_feerate: feerate_per_kw,
- predicted_nondust_htlc_count: ret.nondust_htlc_count,
- predicted_fee_sat: ret.commit_tx_fee_sat,
+ predicted_nondust_htlc_count: local_stats.commitment_stats.nondust_htlc_count,
+ predicted_fee_sat: local_stats.commitment_stats.commit_tx_fee_sat,
};
} else {
let predicted_stats = SpecTxBuilder {}
- .get_next_commitment_stats(
+ .get_channel_stats(
true,
funding.is_outbound(),
funding.get_value_satoshis(),
@@ -4905,7 +4905,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
self.holder_dust_limit_satoshis,
funding.get_channel_type(),
)
- .expect("Balance after HTLCs and anchors exhausted on local commitment");
+ .expect("Balance after HTLCs and anchors exhausted on local commitment")
+ .commitment_stats;
*funding.next_local_fee.lock().unwrap() = PredictedNextFee {
predicted_feerate: feerate_per_kw,
predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count,
@@ -4914,14 +4915,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}
}
- Ok(ret)
+ Ok(local_stats)
}
fn get_next_remote_commitment_stats(
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
feerate_per_kw: u32, dust_exposure_limiting_feerate: Option<u32>,
- ) -> Result<NextCommitmentStats, ()> {
+ ) -> Result<ChannelStats, ()> {
let next_commitment_htlcs = self.get_next_commitment_htlcs(
false,
htlc_candidate,
@@ -4929,7 +4930,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
);
let next_value_to_self_msat = self.get_next_commitment_value_to_self_msat(false, funding);
- let ret = SpecTxBuilder {}.get_next_commitment_stats(
+ let remote_stats = SpecTxBuilder {}.get_channel_stats(
false,
funding.is_outbound(),
funding.get_value_satoshis(),
@@ -4947,12 +4948,12 @@ impl<SP: SignerProvider> ChannelContext<SP> {
if addl_nondust_htlc_count == 0 {
*funding.next_remote_fee.lock().unwrap() = PredictedNextFee {
predicted_feerate: feerate_per_kw,
- predicted_nondust_htlc_count: ret.nondust_htlc_count,
- predicted_fee_sat: ret.commit_tx_fee_sat,
+ predicted_nondust_htlc_count: remote_stats.commitment_stats.nondust_htlc_count,
+ predicted_fee_sat: remote_stats.commitment_stats.commit_tx_fee_sat,
};
} else {
let predicted_stats = SpecTxBuilder {}
- .get_next_commitment_stats(
+ .get_channel_stats(
false,
funding.is_outbound(),
funding.get_value_satoshis(),
@@ -4964,7 +4965,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
self.counterparty_dust_limit_satoshis,
funding.get_channel_type(),
)
- .expect("Balance after HTLCs and anchors exhausted on remote commitment");
+ .expect("Balance after HTLCs and anchors exhausted on remote commitment")
+ .commitment_stats;
*funding.next_remote_fee.lock().unwrap() = PredictedNextFee {
predicted_feerate: feerate_per_kw,
predicted_nondust_htlc_count: predicted_stats.nondust_htlc_count,
@@ -4973,7 +4975,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}
}
- Ok(ret)
+ Ok(remote_stats)
}
fn validate_update_add_htlc<F: FeeEstimator>(
@@ -4993,7 +4995,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
let include_counterparty_unknown_htlcs = false;
// Don't include the extra fee spike buffer HTLC in calculations
let fee_spike_buffer_htlc = 0;
- let next_remote_commitment_stats = self
+ let remote_stats = self
.get_next_remote_commitment_stats(
funding,
Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }),
@@ -5006,7 +5008,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
ChannelError::close(String::from("Remote HTLC add would overdraw remaining funds"))
})?;
- if next_remote_commitment_stats.inbound_htlcs_count
+ if remote_stats.commitment_stats.inbound_htlcs_count
> self.holder_max_accepted_htlcs as usize
{
return Err(ChannelError::close(format!(
@@ -5014,7 +5016,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
self.holder_max_accepted_htlcs,
)));
}
- if next_remote_commitment_stats.inbound_htlcs_value_msat
+ if remote_stats.commitment_stats.inbound_htlcs_value_msat
> self.holder_max_htlc_value_in_flight_msat
{
return Err(ChannelError::close(format!(
@@ -5042,16 +5044,17 @@ impl<SP: SignerProvider> ChannelContext<SP> {
let remote_commit_tx_fee_msat = if funding.is_outbound() {
0
} else {
- next_remote_commitment_stats.commit_tx_fee_sat * 1000
+ remote_stats.commitment_stats.commit_tx_fee_sat * 1000
};
- if next_remote_commitment_stats.counterparty_balance_before_fee_msat
+ if remote_stats.commitment_stats.counterparty_balance_before_fee_msat
< remote_commit_tx_fee_msat
{
return Err(ChannelError::close(
"Remote HTLC add would not leave enough to pay for fees".to_owned(),
));
};
- if next_remote_commitment_stats
+ if remote_stats
+ .commitment_stats
.counterparty_balance_before_fee_msat
.saturating_sub(remote_commit_tx_fee_msat)
< funding.holder_selected_channel_reserve_satoshis * 1000
@@ -5063,7 +5066,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
}
if funding.is_outbound() {
- let next_local_commitment_stats = self
+ let local_stats = self
.get_next_local_commitment_stats(
funding,
Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }),
@@ -5078,9 +5081,9 @@ impl<SP: SignerProvider> ChannelContext<SP> {
))
})?;
// Check that they won't violate our local required channel reserve by adding this HTLC.
- if next_local_commitment_stats.holder_balance_before_fee_msat
+ if local_stats.commitment_stats.holder_balance_before_fee_msat
< funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000
- + next_local_commitment_stats.commit_tx_fee_sat * 1000
+ + local_stats.commitment_stats.commit_tx_fee_sat * 1000
{
return Err(ChannelError::close(
"Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()
@@ -5101,7 +5104,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
// Do not include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed
// by the counterparty (ie. LocalAnnounced HTLCs)
let include_counterparty_unknown_htlcs = false;
- let next_local_commitment_stats = self
+ let local_stats = self
.get_next_local_commitment_stats(
funding,
None,
@@ -5116,7 +5119,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
))
})?;
- next_local_commitment_stats
+ local_stats
+ .commitment_stats
.get_holder_counterparty_balances_incl_fee_msat()
.and_then(|(_, counterparty_balance_incl_fee_msat)| {
counterparty_balance_incl_fee_msat
@@ -5127,7 +5131,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
ChannelError::close("Funding remote cannot afford proposed new fee".to_owned())
})?;
- let next_remote_commitment_stats = self
+ let remote_stats = self
.get_next_remote_commitment_stats(
funding,
None,
@@ -5144,21 +5148,21 @@ impl<SP: SignerProvider> ChannelContext<SP> {
let max_dust_htlc_exposure_msat =
self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
- if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
return Err(ChannelError::close(
format!(
"Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our own transactions (totaling {} msat)",
new_feerate_per_kw,
- next_local_commitment_stats.dust_exposure_msat,
+ local_stats.commitment_stats.dust_exposure_msat,
)
));
}
- if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
return Err(ChannelError::close(
format!(
"Peer sent update_fee with a feerate ({}) which may over-expose us to dust-in-flight on our counterparty's transactions (totaling {} msat)",
new_feerate_per_kw,
- next_remote_commitment_stats.dust_exposure_msat,
+ remote_stats.commitment_stats.dust_exposure_msat,
)
));
}
@@ -5304,7 +5308,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
// Include outbound update_add_htlc's in the holding cell, and those which haven't yet been ACK'ed by
// the counterparty (ie. LocalAnnounced HTLCs)
let include_counterparty_unknown_htlcs = true;
- let next_remote_commitment_stats = if let Ok(stats) = self.get_next_remote_commitment_stats(
+ let remote_stats = if let Ok(stats) = self.get_next_remote_commitment_stats(
funding,
None,
include_counterparty_unknown_htlcs,
@@ -5322,8 +5326,8 @@ impl<SP: SignerProvider> ChannelContext<SP> {
};
// Note that `stats.commit_tx_fee_sat` accounts for any HTLCs that transition from non-dust to dust
// under a higher feerate (in the case where HTLC-transactions pay endogenous fees).
- if next_remote_commitment_stats.holder_balance_before_fee_msat
- < next_remote_commitment_stats.commit_tx_fee_sat * 1000
+ if remote_stats.commitment_stats.holder_balance_before_fee_msat
+ < remote_stats.commitment_stats.commit_tx_fee_sat * 1000
+ funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000
{
//TODO: auto-close after a number of failures?
@@ -5335,7 +5339,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
// `feerate_per_kw`.
let max_dust_htlc_exposure_msat =
self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
- if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
log_debug!(
logger,
"Cannot afford to send new feerate at {} without infringing max dust htlc exposure",
@@ -5344,7 +5348,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
return false;
}
- let next_local_commitment_stats = if let Ok(stats) = self.get_next_local_commitment_stats(
+ let local_stats = if let Ok(stats) = self.get_next_local_commitment_stats(
funding,
None,
include_counterparty_unknown_htlcs,
@@ -5360,7 +5364,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
);
return false;
};
- if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
log_debug!(
logger,
"Cannot afford to send new feerate at {} without infringing max dust htlc exposure",
@@ -5390,7 +5394,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
cmp::max(self.feerate_per_kw, self.pending_update_fee.map(|(fee, _)| fee).unwrap_or(0));
// A `None` `HTLCCandidate` is used as in this case because we're already accounting for
// the incoming HTLC as it has been fully committed by both sides.
- let next_local_commitment_stats = self
+ let local_stats = self
.get_next_local_commitment_stats(
funding,
None,
@@ -5403,7 +5407,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
log_trace!(logger, "Attempting to fail HTLC due to balance after HTLCs and anchors exhausted on local commitment");
LocalHTLCFailureReason::ChannelBalanceOverdrawn
})?;
- let next_remote_commitment_stats = self
+ let remote_stats = self
.get_next_remote_commitment_stats(
funding,
None,
@@ -5419,22 +5423,22 @@ impl<SP: SignerProvider> ChannelContext<SP> {
let max_dust_htlc_exposure_msat =
self.get_max_dust_htlc_exposure_msat(dust_exposure_limiting_feerate);
- if next_remote_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if remote_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
// Note that the total dust exposure includes both the dust HTLCs and the excess mining fees of
// the counterparty commitment transaction
log_info!(
logger,
"Cannot accept value that would put our total dust exposure at {} over the limit {} on counterparty commitment tx",
- next_remote_commitment_stats.dust_exposure_msat,
+ remote_stats.commitment_stats.dust_exposure_msat,
max_dust_htlc_exposure_msat,
);
return Err(LocalHTLCFailureReason::DustLimitCounterparty);
}
- if next_local_commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
+ if local_stats.commitment_stats.dust_exposure_msat > max_dust_htlc_exposure_msat {
log_info!(
logger,
"Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx",
- next_local_commitment_stats.dust_exposure_msat,
+ local_stats.commitment_stats.dust_exposure_msat,
max_dust_htlc_exposure_msat,
);
return Err(LocalHTLCFailureReason::DustLimitHolder);
@@ -5442,14 +5446,15 @@ impl<SP: SignerProvider> ChannelContext<SP> {
if !funding.is_outbound() {
let mut remote_fee_incl_fee_spike_buffer_htlc_msat =
- next_remote_commitment_stats.commit_tx_fee_sat * 1000;
+ remote_stats.commitment_stats.commit_tx_fee_sat * 1000;
// Note that with anchor outputs we are no longer as sensitive to fee spikes, so we don't need
// to account for them.
if !funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
remote_fee_incl_fee_spike_buffer_htlc_msat *=
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
}
- if next_remote_commitment_stats
+ if remote_stats
+ .commitment_stats
.counterparty_balance_before_fee_msat
.saturating_sub(funding.holder_selected_channel_reserve_satoshis * 1000)
< remote_fee_incl_fee_spike_buffer_htlc_msat
@@ -5571,7 +5576,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
let value_to_self_msat = (funding.value_to_self_msat + value_to_self_claimed_msat).checked_sub(value_to_remote_claimed_msat).unwrap();
- let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction(
+ let (tx, _stats) = SpecTxBuilder {}.build_commitment_transaction(
local,
commitment_number,
per_commitment_point,
@@ -5587,7 +5592,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
{
let PredictedNextFee { predicted_feerate, predicted_nondust_htlc_count, predicted_fee_sat } = if local { *funding.next_local_fee.lock().unwrap() } else { *funding.next_remote_fee.lock().unwrap() };
if predicted_feerate == tx.negotiated_feerate_per_kw() && predicted_nondust_htlc_count == tx.nondust_htlcs().len() {
- assert_eq!(predicted_fee_sat, stats.commit_tx_fee_sat);
+ assert_eq!(predicted_fee_sat, _stats.commit_tx_fee_sat);
}
}
#[cfg(debug_assertions)]
@@ -5600,19 +5605,19 @@ impl<SP: SignerProvider> ChannelContext<SP> {
funding.counterparty_prev_commitment_tx_balance.lock().unwrap()
};
- if stats.local_balance_before_fee_msat / 1000 < funding.counterparty_selected_channel_reserve_satoshis.unwrap() {
+ if _stats.local_balance_before_fee_msat / 1000 < funding.counterparty_selected_channel_reserve_satoshis.unwrap() {
// If the local balance is below the reserve on this new commitment, it MUST be
// greater than or equal to the one on the previous commitment.
- debug_assert!(broadcaster_prev_commitment_balance.0 <= stats.local_balance_before_fee_msat);
+ debug_assert!(broadcaster_prev_commitment_balance.0 <= _stats.local_balance_before_fee_msat);
}
- broadcaster_prev_commitment_balance.0 = stats.local_balance_before_fee_msat;
+ broadcaster_prev_commitment_balance.0 = _stats.local_balance_before_fee_msat;
- if stats.remote_balance_before_fee_msat / 1000 < funding.holder_selected_channel_reserve_satoshis {
+ if _stats.remote_balance_before_fee_msat / 1000 < funding.holder_selected_channel_reserve_satoshis {
// If the remote balance is below the reserve on this new commitment, it MUST be
// greater than or equal to the one on the previous commitment.
- debug_assert!(broadcaster_prev_commitment_balance.1 <= stats.remote_balance_before_fee_msat);
+ debug_assert!(broadcaster_prev_commitment_balance.1 <= _stats.remote_balance_before_fee_msat);
}
- broadcaster_prev_commitment_balance.1 = stats.remote_balance_before_fee_msat;
+ broadcaster_prev_commitment_balance.1 = _stats.remote_balance_before_fee_msat;
}
// This populates the HTLC-source table with the indices from the HTLCs in the commitment
@@ -12704,7 +12709,7 @@ where
// We are not interested in dust exposure
let dust_exposure_limiting_feerate = None;
- let local_commitment_stats = self
+ let local_stats = self
.context
.get_next_local_commitment_stats(
funding,
@@ -12716,11 +12721,12 @@ where
)
.map_err(|()| "Balance after HTLCs and anchors exhausted on local commitment")?;
let (holder_balance_on_local_msat, counterparty_balance_on_local_msat) =
- local_commitment_stats
+ local_stats
+ .commitment_stats
.get_holder_counterparty_balances_incl_fee_msat()
.map_err(|()| "Channel funder cannot afford the fee on local commitment")?;
- let remote_commitment_stats = self
+ let remote_stats = self
.context
.get_next_remote_commitment_stats(
funding,
@@ -12732,7 +12738,8 @@ where
)
.map_err(|()| "Balance after HTLCs and anchors exhausted on remote commitment")?;
let (holder_balance_on_remote_msat, counterparty_balance_on_remote_msat) =
- remote_commitment_stats
+ remote_stats
+ .commitment_stats
.get_holder_counterparty_balances_incl_fee_msat()
.map_err(|()| "Channel funder cannot afford the fee on remote commitment")?;
diff --git a/lightning/src/sign/tx_builder.rs b/lightning/src/sign/tx_builder.rs
index 27b8b1a..d004cc9 100644
--- a/lightning/src/sign/tx_builder.rs
+++ b/lightning/src/sign/tx_builder.rs
@@ -45,6 +45,10 @@ pub(crate) struct NextCommitmentStats {
pub extra_accepted_htlc_dust_exposure_msat: u64,
}
+pub(crate) struct ChannelStats {
+ pub commitment_stats: NextCommitmentStats,
+}
+
impl NextCommitmentStats {
pub(crate) fn get_holder_counterparty_balances_incl_fee_msat(&self) -> Result<(u64, u64), ()> {
if self.is_outbound_from_holder {
@@ -153,14 +157,120 @@ fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 {
cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX))
}
+fn get_next_commitment_stats(
+ local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
+ value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
+ addl_nondust_htlc_count: usize, feerate_per_kw: u32,
+ dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
+ channel_type: &ChannelTypeFeatures,
+) -> Result<NextCommitmentStats, ()> {
+ let excess_feerate =
+ feerate_per_kw.saturating_sub(dust_exposure_limiting_feerate.unwrap_or(feerate_per_kw));
+ if channel_type.supports_anchor_zero_fee_commitments() {
+ debug_assert_eq!(feerate_per_kw, 0);
+ debug_assert_eq!(excess_feerate, 0);
+ debug_assert_eq!(addl_nondust_htlc_count, 0);
+ }
+
+ // Calculate inbound htlc count
+ let inbound_htlcs_count =
+ next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count();
+
+ // Calculate balances after htlcs
+ let value_to_counterparty_msat =
+ (channel_value_satoshis * 1000).checked_sub(value_to_holder_msat).ok_or(())?;
+ let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
+ .iter()
+ .filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat))
+ .sum();
+ let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
+ .iter()
+ .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat))
+ .sum();
+ let value_to_holder_after_htlcs_msat =
+ value_to_holder_msat.checked_sub(outbound_htlcs_value_msat).ok_or(())?;
+ let value_to_counterparty_after_htlcs_msat =
+ value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?;
+
+ // Subtract the anchors from the channel funder
+ let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) =
+ subtract_addl_outputs(
+ is_outbound_from_holder,
+ value_to_holder_after_htlcs_msat,
+ value_to_counterparty_after_htlcs_msat,
+ channel_type,
+ )?;
+
+ // Increment the feerate by a buffer to calculate dust exposure
+ let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw);
+
+ // Calculate fees on commitment transaction
+ let nondust_htlc_count = next_commitment_htlcs
+ .iter()
+ .filter(|htlc| {
+ !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
+ })
+ .count();
+ let commit_tx_fee_sat = commit_tx_fee_sat(
+ feerate_per_kw,
+ nondust_htlc_count + addl_nondust_htlc_count,
+ channel_type,
+ );
+
+ // Calculate dust exposure on commitment transaction
+ let dust_exposure_msat = next_commitment_htlcs
+ .iter()
+ .filter_map(|htlc| {
+ htlc.is_dust(
+ local,
+ dust_buffer_feerate,
+ broadcaster_dust_limit_satoshis,
+ channel_type,
+ )
+ .then_some(htlc.amount_msat)
+ })
+ .sum();
+
+ // Add any excess fees to dust exposure on counterparty transactions
+ let (dust_exposure_msat, extra_accepted_htlc_dust_exposure_msat) = if local {
+ (dust_exposure_msat, dust_exposure_msat)
+ } else {
+ let (excess_fees_msat, extra_accepted_htlc_excess_fees_msat) =
+ commit_plus_htlc_tx_fees_msat(
+ local,
+ &next_commitment_htlcs,
+ dust_buffer_feerate,
+ excess_feerate,
+ broadcaster_dust_limit_satoshis,
+ channel_type,
+ );
+ (
+ dust_exposure_msat + excess_fees_msat,
+ dust_exposure_msat + extra_accepted_htlc_excess_fees_msat,
+ )
+ };
+
+ Ok(NextCommitmentStats {
+ is_outbound_from_holder,
+ inbound_htlcs_count,
+ inbound_htlcs_value_msat,
+ holder_balance_before_fee_msat,
+ counterparty_balance_before_fee_msat,
+ nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count,
+ commit_tx_fee_sat,
+ dust_exposure_msat,
+ extra_accepted_htlc_dust_exposure_msat,
+ })
+}
+
pub(crate) trait TxBuilder {
- fn get_next_commitment_stats(
+ fn get_channel_stats(
&self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
addl_nondust_htlc_count: usize, feerate_per_kw: u32,
dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
channel_type: &ChannelTypeFeatures,
- ) -> Result<NextCommitmentStats, ()>;
+ ) -> Result<ChannelStats, ()>;
fn commit_tx_fee_sat(
&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
) -> u64;
@@ -179,110 +289,27 @@ pub(crate) trait TxBuilder {
pub(crate) struct SpecTxBuilder {}
impl TxBuilder for SpecTxBuilder {
- fn get_next_commitment_stats(
+ fn get_channel_stats(
&self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
addl_nondust_htlc_count: usize, feerate_per_kw: u32,
dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
channel_type: &ChannelTypeFeatures,
- ) -> Result<NextCommitmentStats, ()> {
- let excess_feerate =
- feerate_per_kw.saturating_sub(dust_exposure_limiting_feerate.unwrap_or(feerate_per_kw));
- if channel_type.supports_anchor_zero_fee_commitments() {
- debug_assert_eq!(feerate_per_kw, 0);
- debug_assert_eq!(excess_feerate, 0);
- debug_assert_eq!(addl_nondust_htlc_count, 0);
- }
-
- // Calculate inbound htlc count
- let inbound_htlcs_count =
- next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count();
-
- // Calculate balances after htlcs
- let value_to_counterparty_msat =
- (channel_value_satoshis * 1000).checked_sub(value_to_holder_msat).ok_or(())?;
- let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
- .iter()
- .filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat))
- .sum();
- let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
- .iter()
- .filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat))
- .sum();
- let value_to_holder_after_htlcs_msat =
- value_to_holder_msat.checked_sub(outbound_htlcs_value_msat).ok_or(())?;
- let value_to_counterparty_after_htlcs_msat =
- value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?;
-
- // Subtract the anchors from the channel funder
- let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) =
- subtract_addl_outputs(
- is_outbound_from_holder,
- value_to_holder_after_htlcs_msat,
- value_to_counterparty_after_htlcs_msat,
- channel_type,
- )?;
-
- // Increment the feerate by a buffer to calculate dust exposure
- let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw);
-
- // Calculate fees on commitment transaction
- let nondust_htlc_count = next_commitment_htlcs
- .iter()
- .filter(|htlc| {
- !htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
- })
- .count();
- let commit_tx_fee_sat = commit_tx_fee_sat(
+ ) -> Result<ChannelStats, ()> {
+ let commitment_stats = get_next_commitment_stats(
+ local,
+ is_outbound_from_holder,
+ channel_value_satoshis,
+ value_to_holder_msat,
+ next_commitment_htlcs,
+ addl_nondust_htlc_count,
feerate_per_kw,
- nondust_htlc_count + addl_nondust_htlc_count,
+ dust_exposure_limiting_feerate,
+ broadcaster_dust_limit_satoshis,
channel_type,
- );
+ )?;
- // Calculate dust exposure on commitment transaction
- let dust_exposure_msat = next_commitment_htlcs
- .iter()
- .filter_map(|htlc| {
- htlc.is_dust(
- local,
- dust_buffer_feerate,
- broadcaster_dust_limit_satoshis,
- channel_type,
- )
- .then_some(htlc.amount_msat)
- })
- .sum();
-
- // Add any excess fees to dust exposure on counterparty transactions
- let (dust_exposure_msat, extra_accepted_htlc_dust_exposure_msat) = if local {
- (dust_exposure_msat, dust_exposure_msat)
- } else {
- let (excess_fees_msat, extra_accepted_htlc_excess_fees_msat) =
- commit_plus_htlc_tx_fees_msat(
- local,
- &next_commitment_htlcs,
- dust_buffer_feerate,
- excess_feerate,
- broadcaster_dust_limit_satoshis,
- channel_type,
- );
- (
- dust_exposure_msat + excess_fees_msat,
- dust_exposure_msat + extra_accepted_htlc_excess_fees_msat,
- )
- };
-
- Ok(NextCommitmentStats {
- is_outbound_from_holder,
- inbound_htlcs_count,
- inbound_htlcs_value_msat,
- holder_balance_before_fee_msat,
- counterparty_balance_before_fee_msat,
- nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count,
- commit_tx_fee_sat,
- dust_exposure_msat,
- extra_accepted_htlc_dust_exposure_msat,
- })
+ Ok(ChannelStats { commitment_stats })
}
fn commit_tx_fee_sat(
&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
Why this scored 13/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.