use core::cmp::max;
use crate::{
consensus::{self, BlockHeight},
transaction::{
components::{
amount::{BalanceError, NonNegativeAmount},
transparent::OutPoint,
},
fees::transparent,
},
};
pub const MARGINAL_FEE: NonNegativeAmount = NonNegativeAmount::const_from_u64(5_000);
pub const GRACE_ACTIONS: usize = 2;
pub const P2PKH_STANDARD_INPUT_SIZE: usize = 150;
pub const P2PKH_STANDARD_OUTPUT_SIZE: usize = 34;
pub const MINIMUM_FEE: NonNegativeAmount = NonNegativeAmount::const_from_u64(10_000);
#[derive(Clone, Debug)]
pub struct FeeRule {
marginal_fee: NonNegativeAmount,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
}
impl FeeRule {
pub fn standard() -> Self {
Self {
marginal_fee: MARGINAL_FEE,
grace_actions: GRACE_ACTIONS,
p2pkh_standard_input_size: P2PKH_STANDARD_INPUT_SIZE,
p2pkh_standard_output_size: P2PKH_STANDARD_OUTPUT_SIZE,
}
}
#[cfg(feature = "non-standard-fees")]
pub fn non_standard(
marginal_fee: NonNegativeAmount,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
) -> Option<Self> {
if p2pkh_standard_input_size == 0 || p2pkh_standard_output_size == 0 {
None
} else {
Some(Self {
marginal_fee,
grace_actions,
p2pkh_standard_input_size,
p2pkh_standard_output_size,
})
}
}
pub fn marginal_fee(&self) -> NonNegativeAmount {
self.marginal_fee
}
pub fn grace_actions(&self) -> usize {
self.grace_actions
}
pub fn p2pkh_standard_input_size(&self) -> usize {
self.p2pkh_standard_input_size
}
pub fn p2pkh_standard_output_size(&self) -> usize {
self.p2pkh_standard_output_size
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FeeError {
Balance(BalanceError),
NonP2pkhInputs(Vec<OutPoint>),
}
impl From<BalanceError> for FeeError {
fn from(err: BalanceError) -> Self {
FeeError::Balance(err)
}
}
impl std::fmt::Display for FeeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self {
FeeError::Balance(e) => write!(
f,
"A balance calculation violated amount validity bounds: {}.",
e
),
FeeError::NonP2pkhInputs(_) => write!(f, "Only P2PKH inputs are supported."),
}
}
}
impl super::FeeRule for FeeRule {
type Error = FeeError;
fn fee_required<P: consensus::Parameters>(
&self,
_params: &P,
_target_height: BlockHeight,
transparent_input_sizes: impl IntoIterator<Item = transparent::InputSize>,
transparent_output_sizes: impl IntoIterator<Item = usize>,
sapling_input_count: usize,
sapling_output_count: usize,
orchard_action_count: usize,
) -> Result<NonNegativeAmount, Self::Error> {
let mut t_in_total_size: usize = 0;
let mut non_p2pkh_outpoints = vec![];
for sz in transparent_input_sizes.into_iter() {
match sz {
transparent::InputSize::Known(s) => {
t_in_total_size += s;
}
transparent::InputSize::Unknown(outpoint) => {
non_p2pkh_outpoints.push(outpoint.clone());
}
}
}
if !non_p2pkh_outpoints.is_empty() {
return Err(FeeError::NonP2pkhInputs(non_p2pkh_outpoints));
}
let t_out_total_size = transparent_output_sizes.into_iter().sum();
let ceildiv = |num: usize, den: usize| (num + den - 1) / den;
let logical_actions = max(
ceildiv(t_in_total_size, self.p2pkh_standard_input_size),
ceildiv(t_out_total_size, self.p2pkh_standard_output_size),
) + max(sapling_input_count, sapling_output_count)
+ orchard_action_count;
(self.marginal_fee * max(self.grace_actions, logical_actions))
.ok_or_else(|| BalanceError::Overflow.into())
}
}