Fix vbytes and fee rate code

It was just pointed out that we are calculating the virtual bytes
incorrectly by forgetting to take the ceiling after division by 4 [1]

Add helper functions to encapsulate all weight unit -> virtual byte
calculations including fee to and from fee rate. This makes the code
easier to read, easier to write, and gives us a better chance that bugs
like this will be easier to see.

As an added bonus we can also stop using f32 values for fee amount,
which is by definition an amount in sats so should be a u64. This
removes a bunch of casts and the need for epsilon comparisons and just
deep down feels nice :)

[1] https://github.com/bitcoindevkit/bdk/pull/386#discussion_r670882678
This commit is contained in:
Tobin Harding 2021-07-16 15:14:20 +10:00
parent 474620e6a5
commit 2986fce7c6
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
3 changed files with 102 additions and 78 deletions

View File

@ -10,6 +10,7 @@
// licenses. // licenses.
use std::convert::AsRef; use std::convert::AsRef;
use std::ops::Sub;
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
use bitcoin::{hash_types::Txid, util::psbt}; use bitcoin::{hash_types::Txid, util::psbt};
@ -65,10 +66,31 @@ impl FeeRate {
FeeRate(1.0) FeeRate(1.0)
} }
/// Calculate fee rate from `fee` and weight units (`wu`).
pub fn from_wu(fee: u64, wu: usize) -> FeeRate {
Self::from_vb(fee, wu.vbytes())
}
/// Calculate fee rate from `fee` and `vbytes`.
pub fn from_vb(fee: u64, vbytes: usize) -> FeeRate {
let rate = fee as f32 / vbytes as f32;
Self::from_sat_per_vb(rate)
}
/// Return the value as satoshi/vbyte /// Return the value as satoshi/vbyte
pub fn as_sat_vb(&self) -> f32 { pub fn as_sat_vb(&self) -> f32 {
self.0 self.0
} }
/// Calculate absolute fee in Satoshis using size in weight units.
pub fn fee_wu(&self, wu: usize) -> u64 {
self.fee_vb(wu.vbytes())
}
/// Calculate absolute fee in Satoshis using size in virtual bytes.
pub fn fee_vb(&self, vbytes: usize) -> u64 {
(self.as_sat_vb() * vbytes as f32).ceil() as u64
}
} }
impl std::default::Default for FeeRate { impl std::default::Default for FeeRate {
@ -77,6 +99,27 @@ impl std::default::Default for FeeRate {
} }
} }
impl Sub for FeeRate {
type Output = Self;
fn sub(self, other: FeeRate) -> Self::Output {
FeeRate(self.0 - other.0)
}
}
/// Trait implemented by types that can be used to measure weight units.
pub trait Vbytes {
/// Convert weight units to virtual bytes.
fn vbytes(self) -> usize;
}
impl Vbytes for usize {
fn vbytes(self) -> usize {
// ref: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations
(self as f32 / 4.0).ceil() as usize
}
}
/// An unspent output owned by a [`Wallet`]. /// An unspent output owned by a [`Wallet`].
/// ///
/// [`Wallet`]: crate::Wallet /// [`Wallet`]: crate::Wallet

View File

@ -26,7 +26,7 @@
//! ``` //! ```
//! # use std::str::FromStr; //! # use std::str::FromStr;
//! # use bitcoin::*; //! # use bitcoin::*;
//! # use bdk::wallet::coin_selection::*; //! # use bdk::wallet::{self, coin_selection::*};
//! # use bdk::database::Database; //! # use bdk::database::Database;
//! # use bdk::*; //! # use bdk::*;
//! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4; //! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
@ -41,7 +41,7 @@
//! optional_utxos: Vec<WeightedUtxo>, //! optional_utxos: Vec<WeightedUtxo>,
//! fee_rate: FeeRate, //! fee_rate: FeeRate,
//! amount_needed: u64, //! amount_needed: u64,
//! fee_amount: f32, //! fee_amount: u64,
//! ) -> Result<CoinSelectionResult, bdk::Error> { //! ) -> Result<CoinSelectionResult, bdk::Error> {
//! let mut selected_amount = 0; //! let mut selected_amount = 0;
//! let mut additional_weight = 0; //! let mut additional_weight = 0;
@ -57,9 +57,8 @@
//! }, //! },
//! ) //! )
//! .collect::<Vec<_>>(); //! .collect::<Vec<_>>();
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0; //! let additional_fees = fee_rate.fee_wu(additional_weight);
//! let amount_needed_with_fees = //! let amount_needed_with_fees = (fee_amount + additional_fees) + amount_needed;
//! (fee_amount + additional_fees).ceil() as u64 + amount_needed;
//! if amount_needed_with_fees > selected_amount { //! if amount_needed_with_fees > selected_amount {
//! return Err(bdk::Error::InsufficientFunds { //! return Err(bdk::Error::InsufficientFunds {
//! needed: amount_needed_with_fees, //! needed: amount_needed_with_fees,
@ -90,7 +89,6 @@
//! ``` //! ```
use crate::types::FeeRate; use crate::types::FeeRate;
use crate::wallet::Vbytes;
use crate::{database::Database, WeightedUtxo}; use crate::{database::Database, WeightedUtxo};
use crate::{error::Error, Utxo}; use crate::{error::Error, Utxo};
@ -118,7 +116,7 @@ pub struct CoinSelectionResult {
/// List of outputs selected for use as inputs /// List of outputs selected for use as inputs
pub selected: Vec<Utxo>, pub selected: Vec<Utxo>,
/// Total fee amount in satoshi /// Total fee amount in satoshi
pub fee_amount: f32, pub fee_amount: u64,
} }
impl CoinSelectionResult { impl CoinSelectionResult {
@ -165,7 +163,7 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
optional_utxos: Vec<WeightedUtxo>, optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
fee_amount: f32, fee_amount: u64,
) -> Result<CoinSelectionResult, Error>; ) -> Result<CoinSelectionResult, Error>;
} }
@ -184,10 +182,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
mut optional_utxos: Vec<WeightedUtxo>, mut optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
mut fee_amount: f32, mut fee_amount: u64,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
log::debug!( log::debug!(
"amount_needed = `{}`, fee_amount = `{}`, fee_rate = `{:?}`", "amount_needed = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
amount_needed, amount_needed,
@ -212,9 +208,9 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
.scan( .scan(
(&mut selected_amount, &mut fee_amount), (&mut selected_amount, &mut fee_amount),
|(selected_amount, fee_amount), (must_use, weighted_utxo)| { |(selected_amount, fee_amount), (must_use, weighted_utxo)| {
if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) { if must_use || **selected_amount < amount_needed + **fee_amount {
**fee_amount += **fee_amount +=
calc_fee_bytes(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight); fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
**selected_amount += weighted_utxo.utxo.txout().value; **selected_amount += weighted_utxo.utxo.txout().value;
log::debug!( log::debug!(
@ -231,7 +227,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let amount_needed_with_fees = amount_needed + (fee_amount.ceil() as u64); let amount_needed_with_fees = amount_needed + fee_amount;
if selected_amount < amount_needed_with_fees { if selected_amount < amount_needed_with_fees {
return Err(Error::InsufficientFunds { return Err(Error::InsufficientFunds {
needed: amount_needed_with_fees, needed: amount_needed_with_fees,
@ -251,16 +247,15 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
struct OutputGroup { struct OutputGroup {
weighted_utxo: WeightedUtxo, weighted_utxo: WeightedUtxo,
// Amount of fees for spending a certain utxo, calculated using a certain FeeRate // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
fee: f32, fee: u64,
// The effective value of the UTXO, i.e., the utxo value minus the fee for spending it // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
effective_value: i64, effective_value: i64,
} }
impl OutputGroup { impl OutputGroup {
fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
let fee = let fee = fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight).vbytes() * fee_rate.as_sat_vb(); let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64;
let effective_value = weighted_utxo.utxo.txout().value as i64 - fee.ceil() as i64;
OutputGroup { OutputGroup {
weighted_utxo, weighted_utxo,
fee, fee,
@ -303,7 +298,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
optional_utxos: Vec<WeightedUtxo>, optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
fee_amount: f32, fee_amount: u64,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
// Mapping every (UTXO, usize) to an output group // Mapping every (UTXO, usize) to an output group
let required_utxos: Vec<OutputGroup> = required_utxos let required_utxos: Vec<OutputGroup> = required_utxos
@ -325,7 +320,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value); .fold(0, |acc, x| acc + x.effective_value);
let actual_target = fee_amount.ceil() as u64 + amount_needed; let actual_target = fee_amount + amount_needed;
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
let expected = (curr_available_value + curr_value) let expected = (curr_available_value + curr_value)
@ -378,7 +373,7 @@ impl BranchAndBoundCoinSelection {
mut curr_value: i64, mut curr_value: i64,
mut curr_available_value: i64, mut curr_available_value: i64,
actual_target: i64, actual_target: i64,
fee_amount: f32, fee_amount: u64,
cost_of_change: f32, cost_of_change: f32,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
// current_selection[i] will contain true if we are using optional_utxos[i], // current_selection[i] will contain true if we are using optional_utxos[i],
@ -486,7 +481,7 @@ impl BranchAndBoundCoinSelection {
mut optional_utxos: Vec<OutputGroup>, mut optional_utxos: Vec<OutputGroup>,
curr_value: i64, curr_value: i64,
actual_target: i64, actual_target: i64,
fee_amount: f32, fee_amount: u64,
) -> CoinSelectionResult { ) -> CoinSelectionResult {
#[cfg(not(test))] #[cfg(not(test))]
optional_utxos.shuffle(&mut thread_rng()); optional_utxos.shuffle(&mut thread_rng());
@ -515,10 +510,10 @@ impl BranchAndBoundCoinSelection {
fn calculate_cs_result( fn calculate_cs_result(
mut selected_utxos: Vec<OutputGroup>, mut selected_utxos: Vec<OutputGroup>,
mut required_utxos: Vec<OutputGroup>, mut required_utxos: Vec<OutputGroup>,
mut fee_amount: f32, mut fee_amount: u64,
) -> CoinSelectionResult { ) -> CoinSelectionResult {
selected_utxos.append(&mut required_utxos); selected_utxos.append(&mut required_utxos);
fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>(); fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<u64>();
let selected = selected_utxos let selected = selected_utxos
.into_iter() .into_iter()
.map(|u| u.weighted_utxo.utxo) .map(|u| u.weighted_utxo.utxo)
@ -540,6 +535,7 @@ mod test {
use super::*; use super::*;
use crate::database::MemoryDatabase; use crate::database::MemoryDatabase;
use crate::types::*; use crate::types::*;
use crate::wallet::Vbytes;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
@ -547,7 +543,7 @@ mod test {
const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2;
const FEE_AMOUNT: f32 = 50.0; const FEE_AMOUNT: u64 = 50;
fn get_test_utxos() -> Vec<WeightedUtxo> { fn get_test_utxos() -> Vec<WeightedUtxo> {
vec![ vec![
@ -656,13 +652,13 @@ mod test {
vec![], vec![],
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
250_000, 250_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 254)
} }
#[test] #[test]
@ -677,13 +673,13 @@ mod test {
vec![], vec![],
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
20_000, 20_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 254);
} }
#[test] #[test]
@ -698,13 +694,13 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
20_000, 20_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 1); assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount(), 200_000); assert_eq!(result.selected_amount(), 200_000);
assert!((result.fee_amount - 118.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 118);
} }
#[test] #[test]
@ -720,7 +716,7 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
500_000, 500_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
} }
@ -738,7 +734,7 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1000.0), FeeRate::from_sat_per_vb(1000.0),
250_000, 250_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
} }
@ -758,13 +754,13 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
250_000, 250_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000); assert_eq!(result.selected_amount(), 300_000);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 254);
} }
#[test] #[test]
@ -785,7 +781,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 254);
} }
#[test] #[test]
@ -806,7 +802,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300010); assert_eq!(result.selected_amount(), 300010);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON); assert_eq!(result.fee_amount, 254);
} }
#[test] #[test]
@ -822,7 +818,7 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
500_000, 500_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
} }
@ -840,7 +836,7 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1000.0), FeeRate::from_sat_per_vb(1000.0),
250_000, 250_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
} }
@ -857,7 +853,7 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
99932, // first utxo's effective value 99932, // first utxo's effective value
0.0, 0,
) )
.unwrap(); .unwrap();
@ -865,7 +861,7 @@ mod test {
assert_eq!(result.selected_amount(), 100_000); assert_eq!(result.selected_amount(), 100_000);
let input_size = (TXIN_BASE_WEIGHT + P2WPKH_WITNESS_SIZE).vbytes(); let input_size = (TXIN_BASE_WEIGHT + P2WPKH_WITNESS_SIZE).vbytes();
let epsilon = 0.5; let epsilon = 0.5;
assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon); assert!((1.0 - (result.fee_amount as f32 / input_size as f32)).abs() < epsilon);
} }
#[test] #[test]
@ -884,7 +880,7 @@ mod test {
optional_utxos, optional_utxos,
FeeRate::from_sat_per_vb(0.0), FeeRate::from_sat_per_vb(0.0),
target_amount, target_amount,
0.0, 0,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected_amount(), target_amount); assert_eq!(result.selected_amount(), target_amount);
@ -911,7 +907,7 @@ mod test {
0, 0,
curr_available_value, curr_available_value,
20_000, 20_000,
50.0, FEE_AMOUNT,
cost_of_change, cost_of_change,
) )
.unwrap(); .unwrap();
@ -938,7 +934,7 @@ mod test {
0, 0,
curr_available_value, curr_available_value,
20_000, 20_000,
50.0, FEE_AMOUNT,
cost_of_change, cost_of_change,
) )
.unwrap(); .unwrap();
@ -950,7 +946,6 @@ mod test {
let fee_rate = FeeRate::from_sat_per_vb(1.0); let fee_rate = FeeRate::from_sat_per_vb(1.0);
let size_of_change = 31; let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
let fee_amount = 50.0;
let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) let utxos: Vec<_> = generate_same_value_utxos(50_000, 10)
.into_iter() .into_iter()
@ -972,12 +967,12 @@ mod test {
curr_value, curr_value,
curr_available_value, curr_available_value,
target_amount, target_amount,
fee_amount, FEE_AMOUNT,
cost_of_change, cost_of_change,
) )
.unwrap(); .unwrap();
assert!((result.fee_amount - 186.0).abs() < f32::EPSILON);
assert_eq!(result.selected_amount(), 100_000); assert_eq!(result.selected_amount(), 100_000);
assert_eq!(result.fee_amount, 186);
} }
// TODO: bnb() function should be optimized, and this test should be done with more utxos // TODO: bnb() function should be optimized, and this test should be done with more utxos
@ -1009,7 +1004,7 @@ mod test {
curr_value, curr_value,
curr_available_value, curr_available_value,
target_amount, target_amount,
0.0, 0,
0.0, 0.0,
) )
.unwrap(); .unwrap();
@ -1035,12 +1030,10 @@ mod test {
utxos, utxos,
0, 0,
target_amount as i64, target_amount as i64,
50.0, FEE_AMOUNT,
); );
assert!(result.selected_amount() > target_amount); assert!(result.selected_amount() > target_amount);
assert!( assert_eq!(result.fee_amount, (50 + result.selected.len() * 68) as u64);
(result.fee_amount - (50.0 + result.selected.len() as f32 * 68.0)).abs() < f32::EPSILON
);
} }
} }

View File

@ -543,7 +543,7 @@ where
}); });
} }
} }
(FeeRate::from_sat_per_vb(0.0), *fee as f32) (FeeRate::from_sat_per_vb(0.0), *fee)
} }
FeePolicy::FeeRate(rate) => { FeePolicy::FeeRate(rate) => {
if let Some(previous_fee) = params.bumping_fee { if let Some(previous_fee) = params.bumping_fee {
@ -554,7 +554,7 @@ where
}); });
} }
} }
(*rate, 0.0) (*rate, 0)
} }
}; };
@ -573,8 +573,7 @@ where
let mut outgoing: u64 = 0; let mut outgoing: u64 = 0;
let mut received: u64 = 0; let mut received: u64 = 0;
let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0; fee_amount += fee_rate.fee_wu(tx.get_weight());
fee_amount += calc_fee_bytes(tx.get_weight());
let recipients = params.recipients.iter().map(|(r, v)| (r, *v)); let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
@ -591,7 +590,7 @@ where
script_pubkey: script_pubkey.clone(), script_pubkey: script_pubkey.clone(),
value, value,
}; };
fee_amount += calc_fee_bytes(serialize(&new_out).len() * 4); fee_amount += fee_rate.fee_vb(serialize(&new_out).len());
tx.output.push(new_out); tx.output.push(new_out);
@ -649,9 +648,8 @@ where
} }
}; };
fee_amount += calc_fee_bytes(serialize(&drain_output).len() * 4); fee_amount += fee_rate.fee_vb(serialize(&drain_output).len());
let mut fee_amount = fee_amount.ceil() as u64;
let drain_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount); let drain_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount);
if tx.output.is_empty() { if tx.output.is_empty() {
@ -754,8 +752,10 @@ where
return Err(Error::IrreplaceableTransaction); return Err(Error::IrreplaceableTransaction);
} }
let vbytes = tx.get_weight().vbytes(); let feerate = FeeRate::from_wu(
let feerate = details.fee.ok_or(Error::FeeRateUnavailable)? as f32 / vbytes; details.fee.ok_or(Error::FeeRateUnavailable)?,
tx.get_weight(),
);
// remove the inputs from the tx and process them // remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>(); let original_txin = tx.input.drain(..).collect::<Vec<_>>();
@ -832,7 +832,7 @@ where
utxos: original_utxos, utxos: original_utxos,
bumping_fee: Some(tx_builder::PreviousFee { bumping_fee: Some(tx_builder::PreviousFee {
absolute: details.fee.ok_or(Error::FeeRateUnavailable)?, absolute: details.fee.ok_or(Error::FeeRateUnavailable)?,
rate: feerate, rate: feerate.as_sat_vb(),
}), }),
..Default::default() ..Default::default()
}; };
@ -1548,18 +1548,6 @@ where
} }
} }
/// Trait implemented by types that can be used to measure weight units.
pub trait Vbytes {
/// Convert weight units to virtual bytes.
fn vbytes(self) -> f32;
}
impl Vbytes for usize {
fn vbytes(self) -> f32 {
self as f32 / 4.0
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod test { pub(crate) mod test {
use std::str::FromStr; use std::str::FromStr;
@ -1746,13 +1734,13 @@ pub(crate) mod test {
dust_change = true; dust_change = true;
)* )*
let tx_fee_rate = $fees as f32 / (tx.get_weight().vbytes()); let tx_fee_rate = FeeRate::from_wu($fees, tx.get_weight());
let fee_rate = $fee_rate.as_sat_vb(); let fee_rate = $fee_rate;
if !dust_change { if !dust_change {
assert!((tx_fee_rate - fee_rate).abs() < 0.5, "Expected fee rate of {}, the tx has {}", fee_rate, tx_fee_rate); assert!((tx_fee_rate - fee_rate).as_sat_vb().abs() < 0.5, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
} else { } else {
assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {}, the tx has {}", fee_rate, tx_fee_rate); assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
} }
}); });
} }