use crate::chain::chaininterface::BroadcasterInterface;
use crate::chain::chaininterface::FeeEstimator;
use crate::chain::chainmonitor::ChainMonitor;
use crate::chain::chainmonitor::Persist;
use crate::chain::Filter;
use crate::events::bump_transaction::Utxo;
use crate::ln::chan_utils::max_htlcs;
use crate::ln::channelmanager::AChannelManager;
use crate::prelude::new_hash_set;
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::EntropySource;
use crate::types::features::ChannelTypeFeatures;
use crate::util::logger::Logger;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::Weight;
use core::cmp::min;
use core::ops::Deref;
const COMMITMENT_TRANSACTION_BASE_WEIGHT: u64 = 900 + 224;
const COMMITMENT_TRANSACTION_PER_HTLC_WEIGHT: u64 = 172;
const PER_HTLC_TIMEOUT_WEIGHT: u64 = 666;
const PER_HTLC_SUCCESS_WEIGHT: u64 = 706;
const TRANSACTION_BASE_WEIGHT: u64 = (4 + 4 + 1 + 1) * WITNESS_SCALE_FACTOR as u64 + 2;
const P2WPKH_INPUT_WEIGHT: u64 = (36 + 4 + 1) * WITNESS_SCALE_FACTOR as u64 + (1 + 1 + 72 + 1 + 33);
const P2WPKH_OUTPUT_WEIGHT: u64 = (8 + 1 + 22) * WITNESS_SCALE_FACTOR as u64;
const P2TR_KEYPATH_INPUT_WEIGHT: u64 = (36 + 4 + 1) * WITNESS_SCALE_FACTOR as u64 + (1 + 1 + 64);
const P2TR_OUTPUT_WEIGHT: u64 = (8 + 1 + 34) * WITNESS_SCALE_FACTOR as u64;
const ANCHOR_INPUT_WEIGHT: u64 = (36 + 4 + 1) * WITNESS_SCALE_FACTOR as u64 + (1 + 1 + 72 + 1 + 40);
fn htlc_success_transaction_weight(context: &AnchorChannelReserveContext) -> u64 {
PER_HTLC_SUCCESS_WEIGHT
+ if context.taproot_wallet {
P2TR_KEYPATH_INPUT_WEIGHT + P2TR_OUTPUT_WEIGHT
} else {
P2WPKH_INPUT_WEIGHT + P2WPKH_OUTPUT_WEIGHT
}
}
fn htlc_timeout_transaction_weight(context: &AnchorChannelReserveContext) -> u64 {
PER_HTLC_TIMEOUT_WEIGHT
+ if context.taproot_wallet {
P2TR_KEYPATH_INPUT_WEIGHT + P2TR_OUTPUT_WEIGHT
} else {
P2WPKH_INPUT_WEIGHT + P2WPKH_OUTPUT_WEIGHT
}
}
fn anchor_output_spend_transaction_weight(
context: &AnchorChannelReserveContext, input_weight: Weight,
) -> u64 {
TRANSACTION_BASE_WEIGHT
+ ANCHOR_INPUT_WEIGHT
+ input_weight.to_wu()
+ if context.taproot_wallet { P2TR_OUTPUT_WEIGHT } else { P2WPKH_OUTPUT_WEIGHT }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnchorChannelReserveContext {
pub upper_bound_fee_rate: FeeRate,
pub expected_accepted_htlcs: u16,
pub taproot_wallet: bool,
}
impl Default for AnchorChannelReserveContext {
fn default() -> Self {
AnchorChannelReserveContext {
upper_bound_fee_rate: FeeRate::from_sat_per_kwu(50 * 250),
expected_accepted_htlcs: 10,
taproot_wallet: false,
}
}
}
fn get_reserve_per_channel_with_input(
context: &AnchorChannelReserveContext, initial_input_weight: Weight,
) -> Amount {
let max_max_htlcs = max_htlcs(&ChannelTypeFeatures::only_static_remote_key());
let expected_accepted_htlcs = min(context.expected_accepted_htlcs, max_max_htlcs) as u64;
let weight = Weight::from_wu(
COMMITMENT_TRANSACTION_BASE_WEIGHT +
2 * expected_accepted_htlcs * COMMITMENT_TRANSACTION_PER_HTLC_WEIGHT +
anchor_output_spend_transaction_weight(context, initial_input_weight) +
htlc_success_transaction_weight(context) * expected_accepted_htlcs +
htlc_timeout_transaction_weight(context) * expected_accepted_htlcs,
);
context.upper_bound_fee_rate.fee_wu(weight).unwrap_or(Amount::MAX)
}
pub fn get_reserve_per_channel(context: &AnchorChannelReserveContext) -> Amount {
get_reserve_per_channel_with_input(
context,
if context.taproot_wallet {
Weight::from_wu(P2TR_KEYPATH_INPUT_WEIGHT)
} else {
Weight::from_wu(P2WPKH_INPUT_WEIGHT)
},
)
}
pub fn get_supportable_anchor_channels(
context: &AnchorChannelReserveContext, utxos: &[Utxo],
) -> u64 {
let reserve_per_channel = get_reserve_per_channel_with_input(context, Weight::ZERO);
let mut total_fractional_amount = Amount::from_sat(0);
let mut num_whole_utxos = 0;
for utxo in utxos {
let satisfaction_fee = context
.upper_bound_fee_rate
.fee_wu(Weight::from_wu(utxo.satisfaction_weight))
.unwrap_or(Amount::MAX);
let amount = utxo.output.value.checked_sub(satisfaction_fee).unwrap_or(Amount::MIN);
if amount >= reserve_per_channel {
num_whole_utxos += 1;
} else {
total_fractional_amount =
total_fractional_amount.checked_add(amount).unwrap_or(Amount::MAX);
}
}
num_whole_utxos + total_fractional_amount.to_sat() / reserve_per_channel.to_sat() / 2
}
pub fn can_support_additional_anchor_channel<
AChannelManagerRef: Deref,
ChannelSigner: EcdsaChannelSigner,
FilterRef: Deref,
BroadcasterRef: Deref,
EstimatorRef: Deref,
LoggerRef: Deref,
PersistRef: Deref,
EntropySourceRef: Deref,
ChainMonitorRef: Deref<
Target = ChainMonitor<
ChannelSigner,
FilterRef,
BroadcasterRef,
EstimatorRef,
LoggerRef,
PersistRef,
EntropySourceRef,
>,
>,
>(
context: &AnchorChannelReserveContext, utxos: &[Utxo], a_channel_manager: AChannelManagerRef,
chain_monitor: ChainMonitorRef,
) -> bool
where
AChannelManagerRef::Target: AChannelManager,
FilterRef::Target: Filter,
BroadcasterRef::Target: BroadcasterInterface,
EstimatorRef::Target: FeeEstimator,
LoggerRef::Target: Logger,
PersistRef::Target: Persist<ChannelSigner>,
EntropySourceRef::Target: EntropySource,
{
let mut anchor_channels = new_hash_set();
for channel_id in chain_monitor.list_monitors() {
let channel_monitor = if let Ok(channel_monitor) = chain_monitor.get_monitor(channel_id) {
channel_monitor
} else {
continue;
};
if channel_monitor.channel_type_features().supports_anchors_zero_fee_htlc_tx()
&& !channel_monitor.get_claimable_balances().is_empty()
{
anchor_channels.insert(channel_id);
}
}
for channel in a_channel_manager.get_cm().list_channels() {
if channel.channel_type.map_or(true, |ct| ct.supports_anchors_zero_fee_htlc_tx()) {
anchor_channels.insert(channel.channel_id);
}
}
get_supportable_anchor_channels(context, utxos) > anchor_channels.len() as u64
}
#[cfg(test)]
mod test {
use super::*;
use bitcoin::{OutPoint, ScriptBuf, TxOut, Txid};
use std::str::FromStr;
#[test]
fn test_get_reserve_per_channel() {
assert_eq!(
get_reserve_per_channel(&AnchorChannelReserveContext {
upper_bound_fee_rate: FeeRate::from_sat_per_kwu(1000),
expected_accepted_htlcs: 1,
taproot_wallet: false,
}),
Amount::from_sat(4349)
);
}
fn make_p2wpkh_utxo(amount: Amount) -> Utxo {
Utxo {
outpoint: OutPoint {
txid: Txid::from_str(
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
)
.unwrap(),
vout: 0,
},
output: TxOut { value: amount, script_pubkey: ScriptBuf::new() },
satisfaction_weight: 1 * 4 + (1 + 1 + 72 + 1 + 33),
}
}
#[test]
fn test_get_supportable_anchor_channels() {
let context = AnchorChannelReserveContext::default();
let reserve_per_channel = get_reserve_per_channel(&context);
let utxos = vec![
make_p2wpkh_utxo(reserve_per_channel * 3 / 2),
make_p2wpkh_utxo(reserve_per_channel),
make_p2wpkh_utxo(reserve_per_channel * 99 / 100),
make_p2wpkh_utxo(reserve_per_channel * 99 / 100),
make_p2wpkh_utxo(reserve_per_channel * 20 / 100),
];
assert_eq!(get_supportable_anchor_channels(&context, utxos.as_slice()), 3);
}
#[test]
fn test_anchor_output_spend_transaction_weight() {
assert_eq!(
anchor_output_spend_transaction_weight(
&AnchorChannelReserveContext { taproot_wallet: false, ..Default::default() },
Weight::from_wu(P2WPKH_INPUT_WEIGHT),
),
717
);
assert_eq!(
anchor_output_spend_transaction_weight(
&AnchorChannelReserveContext { taproot_wallet: true, ..Default::default() },
Weight::from_wu(P2TR_KEYPATH_INPUT_WEIGHT),
),
723
);
}
#[test]
fn test_htlc_success_transaction_weight() {
assert_eq!(
htlc_success_transaction_weight(&AnchorChannelReserveContext {
taproot_wallet: false,
..Default::default()
}),
1102
);
assert_eq!(
htlc_success_transaction_weight(&AnchorChannelReserveContext {
taproot_wallet: true,
..Default::default()
}),
1108
);
}
#[test]
fn test_htlc_timeout_transaction_weight() {
assert_eq!(
htlc_timeout_transaction_weight(&AnchorChannelReserveContext {
taproot_wallet: false,
..Default::default()
}),
1062
);
assert_eq!(
htlc_timeout_transaction_weight(&AnchorChannelReserveContext {
taproot_wallet: true,
..Default::default()
}),
1068
);
}
}