Merge commit 'refs/pull/398/head' of github.com:bitcoindevkit/bdk

This commit is contained in:
Alekos Filini 2021-07-27 13:07:56 +02:00
commit 27600f4a11
No known key found for this signature in database
GPG Key ID: 431401E4A4530061
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);
} }
}); });
} }