// licenses.
use std::convert::AsRef;
+use std::ops::Sub;
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
use bitcoin::{hash_types::Txid, util::psbt};
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
pub fn as_sat_vb(&self) -> f32 {
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 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`].
///
/// [`Wallet`]: crate::Wallet
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::*;
-//! # use bdk::wallet::coin_selection::*;
+//! # use bdk::wallet::{self, coin_selection::*};
//! # use bdk::database::Database;
//! # use bdk::*;
//! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
//! optional_utxos: Vec<WeightedUtxo>,
//! fee_rate: FeeRate,
//! amount_needed: u64,
-//! fee_amount: f32,
+//! fee_amount: u64,
//! ) -> Result<CoinSelectionResult, bdk::Error> {
//! let mut selected_amount = 0;
//! let mut additional_weight = 0;
//! },
//! )
//! .collect::<Vec<_>>();
-//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
-//! let amount_needed_with_fees =
-//! (fee_amount + additional_fees).ceil() as u64 + amount_needed;
+//! let additional_fees = fee_rate.fee_wu(additional_weight);
+//! let amount_needed_with_fees = (fee_amount + additional_fees) + amount_needed;
//! if amount_needed_with_fees > selected_amount {
//! return Err(bdk::Error::InsufficientFunds {
//! needed: amount_needed_with_fees,
//! ```
use crate::types::FeeRate;
-use crate::wallet::Vbytes;
use crate::{database::Database, WeightedUtxo};
use crate::{error::Error, Utxo};
/// List of outputs selected for use as inputs
pub selected: Vec<Utxo>,
/// Total fee amount in satoshi
- pub fee_amount: f32,
+ pub fee_amount: u64,
}
impl CoinSelectionResult {
optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate,
amount_needed: u64,
- fee_amount: f32,
+ fee_amount: u64,
) -> Result<CoinSelectionResult, Error>;
}
mut optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate,
amount_needed: u64,
- mut fee_amount: f32,
+ mut fee_amount: u64,
) -> Result<CoinSelectionResult, Error> {
- let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
-
log::debug!(
"amount_needed = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
amount_needed,
.scan(
(&mut selected_amount, &mut fee_amount),
|(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 +=
- 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;
log::debug!(
)
.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 {
return Err(Error::InsufficientFunds {
needed: amount_needed_with_fees,
struct OutputGroup {
weighted_utxo: WeightedUtxo,
// 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
effective_value: i64,
}
impl OutputGroup {
fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
- let fee =
- (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight).vbytes() * fee_rate.as_sat_vb();
- let effective_value = weighted_utxo.utxo.txout().value as i64 - fee.ceil() as i64;
+ let fee = fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
+ let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64;
OutputGroup {
weighted_utxo,
fee,
optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate,
amount_needed: u64,
- fee_amount: f32,
+ fee_amount: u64,
) -> Result<CoinSelectionResult, Error> {
// Mapping every (UTXO, usize) to an output group
let required_utxos: Vec<OutputGroup> = required_utxos
.iter()
.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 expected = (curr_available_value + curr_value)
mut curr_value: i64,
mut curr_available_value: i64,
actual_target: i64,
- fee_amount: f32,
+ fee_amount: u64,
cost_of_change: f32,
) -> Result<CoinSelectionResult, Error> {
// current_selection[i] will contain true if we are using optional_utxos[i],
mut optional_utxos: Vec<OutputGroup>,
curr_value: i64,
actual_target: i64,
- fee_amount: f32,
+ fee_amount: u64,
) -> CoinSelectionResult {
#[cfg(not(test))]
optional_utxos.shuffle(&mut thread_rng());
fn calculate_cs_result(
mut selected_utxos: Vec<OutputGroup>,
mut required_utxos: Vec<OutputGroup>,
- mut fee_amount: f32,
+ mut fee_amount: u64,
) -> CoinSelectionResult {
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
.into_iter()
.map(|u| u.weighted_utxo.utxo)
use super::*;
use crate::database::MemoryDatabase;
use crate::types::*;
+ use crate::wallet::Vbytes;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
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> {
vec![
vec![],
FeeRate::from_sat_per_vb(1.0),
250_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010);
- assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 254)
}
#[test]
vec![],
FeeRate::from_sat_per_vb(1.0),
20_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010);
- assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 254);
}
#[test]
utxos,
FeeRate::from_sat_per_vb(1.0),
20_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount(), 200_000);
- assert!((result.fee_amount - 118.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 118);
}
#[test]
utxos,
FeeRate::from_sat_per_vb(1.0),
500_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
}
utxos,
FeeRate::from_sat_per_vb(1000.0),
250_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
}
utxos,
FeeRate::from_sat_per_vb(1.0),
250_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000);
- assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 254);
}
#[test]
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010);
- assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 254);
}
#[test]
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300010);
- assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
+ assert_eq!(result.fee_amount, 254);
}
#[test]
utxos,
FeeRate::from_sat_per_vb(1.0),
500_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
}
utxos,
FeeRate::from_sat_per_vb(1000.0),
250_000,
- 50.0,
+ FEE_AMOUNT,
)
.unwrap();
}
utxos,
FeeRate::from_sat_per_vb(1.0),
99932, // first utxo's effective value
- 0.0,
+ 0,
)
.unwrap();
assert_eq!(result.selected_amount(), 100_000);
let input_size = (TXIN_BASE_WEIGHT + P2WPKH_WITNESS_SIZE).vbytes();
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]
optional_utxos,
FeeRate::from_sat_per_vb(0.0),
target_amount,
- 0.0,
+ 0,
)
.unwrap();
assert_eq!(result.selected_amount(), target_amount);
0,
curr_available_value,
20_000,
- 50.0,
+ FEE_AMOUNT,
cost_of_change,
)
.unwrap();
0,
curr_available_value,
20_000,
- 50.0,
+ FEE_AMOUNT,
cost_of_change,
)
.unwrap();
let fee_rate = FeeRate::from_sat_per_vb(1.0);
let size_of_change = 31;
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)
.into_iter()
curr_value,
curr_available_value,
target_amount,
- fee_amount,
+ FEE_AMOUNT,
cost_of_change,
)
.unwrap();
- assert!((result.fee_amount - 186.0).abs() < f32::EPSILON);
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
curr_value,
curr_available_value,
target_amount,
- 0.0,
+ 0,
0.0,
)
.unwrap();
utxos,
0,
target_amount as i64,
- 50.0,
+ FEE_AMOUNT,
);
assert!(result.selected_amount() > target_amount);
- assert!(
- (result.fee_amount - (50.0 + result.selected.len() as f32 * 68.0)).abs() < f32::EPSILON
- );
+ assert_eq!(result.fee_amount, (50 + result.selected.len() * 68) as u64);
}
}
});
}
}
- (FeeRate::from_sat_per_vb(0.0), *fee as f32)
+ (FeeRate::from_sat_per_vb(0.0), *fee)
}
FeePolicy::FeeRate(rate) => {
if let Some(previous_fee) = params.bumping_fee {
});
}
}
- (*rate, 0.0)
+ (*rate, 0)
}
};
let mut outgoing: u64 = 0;
let mut received: u64 = 0;
- let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
- fee_amount += calc_fee_bytes(tx.get_weight());
+ fee_amount += fee_rate.fee_wu(tx.get_weight());
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
script_pubkey: script_pubkey.clone(),
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);
}
};
- 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);
if tx.output.is_empty() {
return Err(Error::IrreplaceableTransaction);
}
- let vbytes = tx.get_weight().vbytes();
- let feerate = details.fee.ok_or(Error::FeeRateUnavailable)? as f32 / vbytes;
+ let feerate = FeeRate::from_wu(
+ details.fee.ok_or(Error::FeeRateUnavailable)?,
+ tx.get_weight(),
+ );
// remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
utxos: original_utxos,
bumping_fee: Some(tx_builder::PreviousFee {
absolute: details.fee.ok_or(Error::FeeRateUnavailable)?,
- rate: feerate,
+ rate: feerate.as_sat_vb(),
}),
..Default::default()
};
}
}
-/// 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)]
pub(crate) mod test {
use std::str::FromStr;
dust_change = true;
)*
- let tx_fee_rate = $fees as f32 / (tx.get_weight().vbytes());
- let fee_rate = $fee_rate.as_sat_vb();
+ let tx_fee_rate = FeeRate::from_wu($fees, tx.get_weight());
+ let fee_rate = $fee_rate;
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 {
- 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);
}
});
}