#![allow(dead_code)]
use core::cmp;
use core::ops::Deref;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
use crate::ln::chan_utils::{
commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight, htlc_tx_fees_sat,
second_stage_tx_fees_sat, ChannelTransactionParameters, CommitmentTransaction,
HTLCOutputInCommitment,
};
use crate::ln::channel::{CommitmentStats, ANCHOR_OUTPUT_VALUE_SATOSHI};
use crate::prelude::*;
use crate::types::features::ChannelTypeFeatures;
use crate::util::logger::Logger;
pub(crate) struct HTLCAmountDirection {
pub outbound: bool,
pub amount_msat: u64,
}
impl HTLCAmountDirection {
fn is_dust(
&self, local: bool, feerate_per_kw: u32, broadcaster_dust_limit_satoshis: u64,
channel_type: &ChannelTypeFeatures,
) -> bool {
let (success_tx_fee_sat, timeout_tx_fee_sat) =
second_stage_tx_fees_sat(channel_type, feerate_per_kw);
let htlc_tx_fee_sat =
if self.outbound == local { timeout_tx_fee_sat } else { success_tx_fee_sat };
self.amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
}
}
pub(crate) struct NextCommitmentStats {
pub is_outbound_from_holder: bool,
pub inbound_htlcs_count: usize,
pub inbound_htlcs_value_msat: u64,
pub holder_balance_before_fee_msat: u64,
pub counterparty_balance_before_fee_msat: u64,
pub nondust_htlc_count: usize,
pub commit_tx_fee_sat: u64,
pub dust_exposure_msat: u64,
pub extra_accepted_htlc_dust_exposure_msat: u64,
}
impl NextCommitmentStats {
pub(crate) fn get_holder_counterparty_balances_incl_fee_msat(&self) -> Result<(u64, u64), ()> {
if self.is_outbound_from_holder {
Ok((
self.holder_balance_before_fee_msat
.checked_sub(self.commit_tx_fee_sat * 1000)
.ok_or(())?,
self.counterparty_balance_before_fee_msat,
))
} else {
Ok((
self.holder_balance_before_fee_msat,
self.counterparty_balance_before_fee_msat
.checked_sub(self.commit_tx_fee_sat * 1000)
.ok_or(())?,
))
}
}
}
fn commit_plus_htlc_tx_fees_msat(
local: bool, next_commitment_htlcs: &[HTLCAmountDirection], dust_buffer_feerate: u32,
feerate: u32, broadcaster_dust_limit_satoshis: u64, channel_type: &ChannelTypeFeatures,
) -> (u64, u64) {
let accepted_nondust_htlcs = next_commitment_htlcs
.iter()
.filter(|htlc| {
htlc.outbound != local
&& !htlc.is_dust(
local,
dust_buffer_feerate,
broadcaster_dust_limit_satoshis,
channel_type,
)
})
.count();
let offered_nondust_htlcs = next_commitment_htlcs
.iter()
.filter(|htlc| {
htlc.outbound == local
&& !htlc.is_dust(
local,
dust_buffer_feerate,
broadcaster_dust_limit_satoshis,
channel_type,
)
})
.count();
let commitment_fee_sat =
commit_tx_fee_sat(feerate, accepted_nondust_htlcs + offered_nondust_htlcs, channel_type);
let second_stage_fees_sat =
htlc_tx_fees_sat(feerate, accepted_nondust_htlcs, offered_nondust_htlcs, channel_type);
let total_fees_msat = (commitment_fee_sat + second_stage_fees_sat) * 1000;
let extra_accepted_htlc_commitment_fee_sat = commit_tx_fee_sat(
feerate,
accepted_nondust_htlcs + 1 + offered_nondust_htlcs,
channel_type,
);
let extra_accepted_htlc_second_stage_fees_sat =
htlc_tx_fees_sat(feerate, accepted_nondust_htlcs + 1, offered_nondust_htlcs, channel_type);
let extra_accepted_htlc_total_fees_msat =
(extra_accepted_htlc_commitment_fee_sat + extra_accepted_htlc_second_stage_fees_sat) * 1000;
(total_fees_msat, extra_accepted_htlc_total_fees_msat)
}
fn subtract_addl_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), ()> {
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 {
Ok((
value_to_self_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?,
value_to_remote_after_htlcs_msat,
))
} else {
Ok((
value_to_self_after_htlcs_msat,
value_to_remote_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?,
))
}
}
fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 {
let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000);
cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX))
}
pub(crate) trait TxBuilder {
fn get_next_commitment_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, ()>;
fn commit_tx_fee_sat(
&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
) -> u64;
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: Deref>(
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
value_to_self_msat: u64, htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
broadcaster_dust_limit_satoshis: u64, logger: &L,
) -> (CommitmentTransaction, CommitmentStats)
where
L::Target: Logger;
}
pub(crate) struct SpecTxBuilder {}
impl TxBuilder for SpecTxBuilder {
fn get_next_commitment_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);
}
let inbound_htlcs_count =
next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count();
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(())?;
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,
)?;
let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw);
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,
);
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();
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,
})
}
fn commit_tx_fee_sat(
&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
) -> u64 {
commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count, channel_type)
}
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;
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: Deref>(
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
value_to_self_msat: u64, mut htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
broadcaster_dust_limit_satoshis: u64, logger: &L,
) -> (CommitmentTransaction, CommitmentStats)
where
L::Target: Logger,
{
let mut local_htlc_total_msat = 0;
let mut remote_htlc_total_msat = 0;
let channel_type = &channel_parameters.channel_type_features;
let is_dust = |offered: bool, amount_msat: u64| -> bool {
let htlc_tx_fee_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
0
} else {
let htlc_tx_weight = if offered {
htlc_timeout_tx_weight(channel_type)
} else {
htlc_success_tx_weight(channel_type)
};
feerate_per_kw as u64 * htlc_tx_weight / 1000
};
amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
};
htlcs_in_tx.retain(|htlc| {
if htlc.offered == local {
local_htlc_total_msat += htlc.amount_msat;
} else {
remote_htlc_total_msat += htlc.amount_msat;
}
if is_dust(htlc.offered, htlc.amount_msat) {
log_trace!(
logger,
" ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}",
if htlc.offered == local { "outbound" } else { "inbound" },
htlc.amount_msat / 1000,
htlc.payment_hash,
broadcaster_dust_limit_satoshis
);
false
} else {
true
}
});
let commit_tx_fee_sat = self.commit_tx_fee_sat(
feerate_per_kw,
htlcs_in_tx.len(),
&channel_parameters.channel_type_features,
);
let value_to_self_after_htlcs_msat =
value_to_self_msat.checked_sub(local_htlc_total_msat).unwrap();
let value_to_remote_after_htlcs_msat = (channel_parameters.channel_value_satoshis * 1000)
.checked_sub(value_to_self_msat)
.unwrap()
.checked_sub(remote_htlc_total_msat)
.unwrap();
let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = self
.subtract_non_htlc_outputs(
channel_parameters.is_outbound_from_holder,
value_to_self_after_htlcs_msat,
value_to_remote_after_htlcs_msat,
&channel_parameters.channel_type_features,
);
let (value_to_self, value_to_remote) = if channel_parameters.is_outbound_from_holder {
(
(local_balance_before_fee_msat / 1000).saturating_sub(commit_tx_fee_sat),
remote_balance_before_fee_msat / 1000,
)
} else {
(
local_balance_before_fee_msat / 1000,
(remote_balance_before_fee_msat / 1000).saturating_sub(commit_tx_fee_sat),
)
};
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
log_trace!(
logger,
" ...including {} output with value {}",
if local { "to_local" } else { "to_remote" },
to_broadcaster_value_sat
);
} else {
to_broadcaster_value_sat = 0;
}
if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
log_trace!(
logger,
" ...including {} output with value {}",
if local { "to_remote" } else { "to_local" },
to_countersignatory_value_sat
);
} else {
to_countersignatory_value_sat = 0;
}
let directed_parameters = if local {
channel_parameters.as_holder_broadcastable()
} else {
channel_parameters.as_counterparty_broadcastable()
};
let tx = CommitmentTransaction::new(
commitment_number,
per_commitment_point,
to_broadcaster_value_sat,
to_countersignatory_value_sat,
feerate_per_kw,
htlcs_in_tx,
&directed_parameters,
secp_ctx,
);
(
tx,
CommitmentStats {
commit_tx_fee_sat,
local_balance_before_fee_msat,
remote_balance_before_fee_msat,
},
)
}
}