use bitcoin::blockdata::transaction::TxOut;
use bitcoin::blockdata::witness::Witness;
use bitcoin::util::amount::Denomination;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::Amount;
use crate::bitcoin::transaction;
use crate::blockchain::{Fee, FeePriority, FeeStrategy, FeeStrategyError};
use crate::consensus::{self, CanonicalBytes};
use std::str::FromStr;
use serde::ser::{Serialize, Serializer};
use serde::{de, Deserialize, Deserializer};
pub const WEIGHT_UNIT: &str = "kvB";
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Hash, Eq, Display)]
#[display(display_sats_per_vbyte)]
pub struct SatPerKvB(Amount);
fn display_sats_per_vbyte(rate: &SatPerKvB) -> String {
format!(
"{}/{}",
rate.as_native_unit()
.to_string_with_denomination(Denomination::Satoshi),
WEIGHT_UNIT
)
}
impl SatPerKvB {
pub fn from_sat(satoshis: u64) -> Self {
SatPerKvB(Amount::from_sat(satoshis))
}
pub fn as_sat(&self) -> u64 {
self.0.as_sat()
}
pub fn from_native_unit(amount: Amount) -> Self {
SatPerKvB(amount)
}
pub fn as_native_unit(&self) -> Amount {
self.0
}
}
impl Serialize for SatPerKvB {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(format!("{}", self).as_ref())
}
}
impl<'de> Deserialize<'de> for SatPerKvB {
fn deserialize<D>(deserializer: D) -> Result<SatPerKvB, D::Error>
where
D: Deserializer<'de>,
{
SatPerKvB::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}
impl CanonicalBytes for SatPerKvB {
fn as_canonical_bytes(&self) -> Vec<u8> {
bitcoin::consensus::encode::serialize(&self.0.as_sat())
}
fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, consensus::Error>
where
Self: Sized,
{
Ok(SatPerKvB(Amount::from_sat(
bitcoin::consensus::encode::deserialize(bytes).map_err(consensus::Error::new)?,
)))
}
}
impl FromStr for SatPerKvB {
type Err = consensus::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split('/').collect::<Vec<&str>>();
if parts.len() != 2 {
return Err(consensus::Error::ParseFailed(
"sat/kvB format is not respected",
));
}
let amount = parts[0].parse::<Amount>().map_err(consensus::Error::new)?;
match parts[1] {
WEIGHT_UNIT => Ok(Self(amount)),
_ => Err(consensus::Error::ParseFailed("Weight unit parse failed")),
}
}
}
fn get_available_input_sat(tx: &PartiallySignedTransaction) -> Result<Amount, FeeStrategyError> {
let inputs: Result<Vec<TxOut>, FeeStrategyError> = tx
.inputs
.iter()
.map(|psbt_in| {
psbt_in
.witness_utxo
.clone()
.ok_or(FeeStrategyError::MissingInputsMetadata)
})
.collect();
Ok(Amount::from_sat(
inputs?.iter().map(|txout| txout.value).sum(),
))
}
fn upper_bound_simulated_witness() -> Witness {
Witness::from_vec(vec![vec![0; 71], vec![0; 71], vec![0], vec![0; 111]])
}
impl Fee for PartiallySignedTransaction {
type FeeUnit = SatPerKvB;
type Amount = Amount;
#[allow(unused_variables)]
fn set_fee(
&mut self,
strategy: &FeeStrategy<SatPerKvB>,
politic: FeePriority,
) -> Result<Self::Amount, FeeStrategyError> {
if self.unsigned_tx.output.len() != 1 {
return Err(FeeStrategyError::new(
transaction::Error::MultiUTXOUnsuported,
));
}
let input_sum = get_available_input_sat(self)?;
self.unsigned_tx.input[0].witness = upper_bound_simulated_witness();
let vsize = self.unsigned_tx.vsize() as f64;
self.unsigned_tx.input[0].witness = Witness::new();
let fee_rate = match strategy {
FeeStrategy::Fixed(sat_per_kvb) => sat_per_kvb
.as_native_unit()
.to_float_in(Denomination::Satoshi),
#[cfg(feature = "fee_range")]
FeeStrategy::Range { min_inc, max_inc } => match politic {
FeePriority::Low => min_inc.as_native_unit().to_float_in(Denomination::Satoshi),
FeePriority::High => max_inc.as_native_unit().to_float_in(Denomination::Satoshi),
},
};
let fee_amount = fee_rate / 1000f64 * vsize;
let fee_amount = Amount::from_sat(fee_amount.round() as u64);
self.unsigned_tx.output[0].value = input_sum
.checked_sub(fee_amount)
.ok_or(FeeStrategyError::NotEnoughAssets)?
.as_sat();
Ok(fee_amount)
}
fn validate_fee(&self, strategy: &FeeStrategy<SatPerKvB>) -> Result<bool, FeeStrategyError> {
if self.unsigned_tx.output.len() != 1 {
return Err(FeeStrategyError::new(
transaction::Error::MultiUTXOUnsuported,
));
}
let input_sum = get_available_input_sat(self)?.as_sat();
let output_sum = self.unsigned_tx.output[0].value;
let effective_fee = input_sum
.checked_sub(output_sum)
.ok_or(FeeStrategyError::AmountOfFeeTooHigh)?;
let mut tx = self.unsigned_tx.clone();
tx.input[0].witness = upper_bound_simulated_witness();
let vsize = tx.vsize() as f64;
match strategy {
FeeStrategy::Fixed(sat_per_kvb) => {
let fee_rate = sat_per_kvb
.as_native_unit()
.to_float_in(Denomination::Satoshi);
let fee_amount = fee_rate / 1000f64 * vsize;
Ok(effective_fee == fee_amount.round() as u64)
}
#[cfg(feature = "fee_range")]
FeeStrategy::Range { min_inc, max_inc } => todo!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct SerdeTest {
fee: SatPerKvB,
}
#[test]
fn parse_sats_per_vbyte() {
for s in [
"0.0001 BTC/kvB",
"100 satoshi/kvB",
"100 satoshis/kvB",
"10 satoshi/kvB",
"1 satoshi/kvB",
]
.iter()
{
let parse = SatPerKvB::from_str(s);
assert!(parse.is_ok());
}
for s in ["1 satoshi", "100 kvB"].iter() {
let parse = SatPerKvB::from_str(s);
assert!(parse.is_err());
}
}
#[test]
fn display_sats_per_vbyte() {
let fee_rate = SatPerKvB::from_sat(100);
assert_eq!(format!("{}", fee_rate), "100 satoshi/kvB".to_string());
}
#[test]
fn serialize_fee_rate_in_yaml() {
let fee_rate = SerdeTest {
fee: SatPerKvB::from_sat(10),
};
let s = serde_yaml::to_string(&fee_rate).expect("Encode fee rate in yaml");
assert_eq!("---\nfee: 10 satoshi/kvB\n", s);
}
#[test]
fn deserialize_fee_rate_in_yaml() {
let s = "---\nfee: 10 satoshi/kvB\n";
let fee_rate = serde_yaml::from_str(&s).expect("Decode fee rate from yaml");
assert_eq!(
SerdeTest {
fee: SatPerKvB::from_sat(10)
},
fee_rate
);
}
}