Merge bitcoindevkit/bdk#662: Consolidate fee_amount
and amount_needed
e8df3d2d91927edb9a339c664f0603c47622e4b0 Consolidate `fee_amount` and `amount_needed` (Cesar Alvarez Vallero) Pull request description: ### Description Before this commit `fee_amount` and `amount_needed` were passed as independent parameters. From the perspective of coin selection algorithms, they are always used jointly for the same purpose, to create a coin selection with a total effective value greater than it's summed values. This commit removes the abstraction that the use of the two parameter introduced by consolidating both into a single parameter, `target_amount`, who carries their values added up. Resolves: #641 ### Notes to the reviewers I just updated old tests and didn't create new ones because almost all changes are renames and "logic changes" (like the addition of the selection fee) are tested in the modified tests. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [ ] I've added tests for the new feature * [x] I've added docs for the new feature * [x] I've updated `CHANGELOG.md` #### Bugfixes: * [x] This pull request breaks the existing API * [x] I'm linking the issue being fixed by this PR ACKs for top commit: danielabrozzoni: re-ACK e8df3d2d91927edb9a339c664f0603c47622e4b0 - I tested with the fuzzer, run it for 13,000,000 iterations, couldn't find any crash :) Tree-SHA512: 64b46473799352c06cc554659e4b159a33812b3d3793c9d436bd1e46b65edd085d71b219f6a0474f6836979ca608aa019a72bdc6915a2cc2d744a76e2a28b889
This commit is contained in:
commit
9c0a769675
@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Change the interface of `SqliteDatabase::new` to accept any type that implement AsRef<Path>
|
||||
- Add the ability to specify which leaves to sign in a taproot transaction through `TapLeavesOptions` in `SignOptions`
|
||||
- Add the ability to specify whether a taproot transaction should be signed using the internal key or not, using `sign_with_tap_internal_key` in `SignOptions`
|
||||
- Consolidate params `fee_amount` and `amount_needed` in `target_amount` in `CoinSelectionAlgorithm::coin_select` signature.
|
||||
- Change the meaning of the `fee_amount` field inside `CoinSelectionResult`: from now on the `fee_amount` will represent only the fees asociated with the utxos in the `selected` field of `CoinSelectionResult`.
|
||||
|
||||
## [v0.20.0] - [v0.19.0]
|
||||
|
||||
|
@ -41,8 +41,7 @@
|
||||
//! required_utxos: Vec<WeightedUtxo>,
|
||||
//! optional_utxos: Vec<WeightedUtxo>,
|
||||
//! fee_rate: FeeRate,
|
||||
//! amount_needed: u64,
|
||||
//! fee_amount: u64,
|
||||
//! target_amount: u64,
|
||||
//! drain_script: &Script,
|
||||
//! ) -> Result<CoinSelectionResult, bdk::Error> {
|
||||
//! let mut selected_amount = 0;
|
||||
@ -60,7 +59,7 @@
|
||||
//! )
|
||||
//! .collect::<Vec<_>>();
|
||||
//! let additional_fees = fee_rate.fee_wu(additional_weight);
|
||||
//! let amount_needed_with_fees = (fee_amount + additional_fees) + amount_needed;
|
||||
//! let amount_needed_with_fees = additional_fees + target_amount;
|
||||
//! if selected_amount < amount_needed_with_fees {
|
||||
//! return Err(bdk::Error::InsufficientFunds {
|
||||
//! needed: amount_needed_with_fees,
|
||||
@ -74,7 +73,7 @@
|
||||
//!
|
||||
//! Ok(CoinSelectionResult {
|
||||
//! selected: all_utxos_selected,
|
||||
//! fee_amount: fee_amount + additional_fees,
|
||||
//! fee_amount: additional_fees,
|
||||
//! excess,
|
||||
//! })
|
||||
//! }
|
||||
@ -148,7 +147,7 @@ pub enum Excess {
|
||||
pub struct CoinSelectionResult {
|
||||
/// List of outputs selected for use as inputs
|
||||
pub selected: Vec<Utxo>,
|
||||
/// Total fee amount in satoshi
|
||||
/// Total fee amount for the selected utxos in satoshis
|
||||
pub fee_amount: u64,
|
||||
/// Remaining amount after deducing fees and outgoing outputs
|
||||
pub excess: Excess,
|
||||
@ -183,14 +182,13 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
|
||||
///
|
||||
/// - `database`: a reference to the wallet's database that can be used to lookup additional
|
||||
/// details for a specific UTXO
|
||||
/// - `required_utxos`: the utxos that must be spent regardless of `amount_needed` with their
|
||||
/// - `required_utxos`: the utxos that must be spent regardless of `target_amount` with their
|
||||
/// weight cost
|
||||
/// - `optional_utxos`: the remaining available utxos to satisfy `amount_needed` with their
|
||||
/// - `optional_utxos`: the remaining available utxos to satisfy `target_amount` with their
|
||||
/// weight cost
|
||||
/// - `fee_rate`: fee rate to use
|
||||
/// - `amount_needed`: the amount in satoshi to select
|
||||
/// - `fee_amount`: the amount of fees in satoshi already accumulated from adding outputs and
|
||||
/// the transaction's header
|
||||
/// - `target_amount`: the outgoing amount in satoshis and the fees already
|
||||
/// accumulated from added outputs and transaction’s header.
|
||||
/// - `drain_script`: the script to use in case of change
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn coin_select(
|
||||
@ -199,8 +197,7 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
|
||||
required_utxos: Vec<WeightedUtxo>,
|
||||
optional_utxos: Vec<WeightedUtxo>,
|
||||
fee_rate: FeeRate,
|
||||
amount_needed: u64,
|
||||
fee_amount: u64,
|
||||
target_amount: u64,
|
||||
drain_script: &Script,
|
||||
) -> Result<CoinSelectionResult, Error>;
|
||||
}
|
||||
@ -219,14 +216,12 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
||||
required_utxos: Vec<WeightedUtxo>,
|
||||
mut optional_utxos: Vec<WeightedUtxo>,
|
||||
fee_rate: FeeRate,
|
||||
amount_needed: u64,
|
||||
fee_amount: u64,
|
||||
target_amount: u64,
|
||||
drain_script: &Script,
|
||||
) -> Result<CoinSelectionResult, Error> {
|
||||
log::debug!(
|
||||
"amount_needed = `{}`, fee_amount = `{}`, fee_rate = `{:?}`",
|
||||
amount_needed,
|
||||
fee_amount,
|
||||
"target_amount = `{}`, fee_rate = `{:?}`",
|
||||
target_amount,
|
||||
fee_rate
|
||||
);
|
||||
|
||||
@ -240,7 +235,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
||||
.chain(optional_utxos.into_iter().rev().map(|utxo| (false, utxo)))
|
||||
};
|
||||
|
||||
select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_script)
|
||||
select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,8 +253,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for OldestFirstCoinSelection {
|
||||
required_utxos: Vec<WeightedUtxo>,
|
||||
mut optional_utxos: Vec<WeightedUtxo>,
|
||||
fee_rate: FeeRate,
|
||||
amount_needed: u64,
|
||||
fee_amount: u64,
|
||||
target_amount: u64,
|
||||
drain_script: &Script,
|
||||
) -> Result<CoinSelectionResult, Error> {
|
||||
// query db and create a blockheight lookup table
|
||||
@ -300,7 +294,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for OldestFirstCoinSelection {
|
||||
.chain(optional_utxos.into_iter().map(|utxo| (false, utxo)))
|
||||
};
|
||||
|
||||
select_sorted_utxos(utxos, fee_rate, amount_needed, fee_amount, drain_script)
|
||||
select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,16 +327,16 @@ pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Sc
|
||||
fn select_sorted_utxos(
|
||||
utxos: impl Iterator<Item = (bool, WeightedUtxo)>,
|
||||
fee_rate: FeeRate,
|
||||
amount_needed: u64,
|
||||
mut fee_amount: u64,
|
||||
target_amount: u64,
|
||||
drain_script: &Script,
|
||||
) -> Result<CoinSelectionResult, Error> {
|
||||
let mut selected_amount = 0;
|
||||
let mut fee_amount = 0;
|
||||
let selected = utxos
|
||||
.scan(
|
||||
(&mut selected_amount, &mut fee_amount),
|
||||
|(selected_amount, fee_amount), (must_use, weighted_utxo)| {
|
||||
if must_use || **selected_amount < amount_needed + **fee_amount {
|
||||
if must_use || **selected_amount < target_amount + **fee_amount {
|
||||
**fee_amount +=
|
||||
fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
|
||||
**selected_amount += weighted_utxo.utxo.txout().value;
|
||||
@ -361,8 +355,7 @@ fn select_sorted_utxos(
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let amount_needed_with_fees = amount_needed + fee_amount;
|
||||
|
||||
let amount_needed_with_fees = target_amount + fee_amount;
|
||||
if selected_amount < amount_needed_with_fees {
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: amount_needed_with_fees,
|
||||
@ -436,8 +429,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
required_utxos: Vec<WeightedUtxo>,
|
||||
optional_utxos: Vec<WeightedUtxo>,
|
||||
fee_rate: FeeRate,
|
||||
amount_needed: u64,
|
||||
fee_amount: u64,
|
||||
target_amount: u64,
|
||||
drain_script: &Script,
|
||||
) -> Result<CoinSelectionResult, Error> {
|
||||
// Mapping every (UTXO, usize) to an output group
|
||||
@ -460,7 +452,6 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
.iter()
|
||||
.fold(0, |acc, x| acc + x.effective_value);
|
||||
|
||||
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)
|
||||
@ -469,29 +460,28 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
Error::Generic("Sum of UTXO spendable values does not fit into u64".to_string())
|
||||
})?;
|
||||
|
||||
if expected < actual_target {
|
||||
if expected < target_amount {
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: actual_target,
|
||||
needed: target_amount,
|
||||
available: expected,
|
||||
});
|
||||
}
|
||||
|
||||
let actual_target = actual_target
|
||||
let target_amount = target_amount
|
||||
.try_into()
|
||||
.expect("Bitcoin amount to fit into i64");
|
||||
|
||||
if curr_value > actual_target {
|
||||
if curr_value > target_amount {
|
||||
// remaining_amount can't be negative as that would mean the
|
||||
// selection wasn't successful
|
||||
// actual_target = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (curr_value - actual_target) as u64;
|
||||
// target_amount = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (curr_value - target_amount) as u64;
|
||||
|
||||
let excess = decide_change(remaining_amount, fee_rate, drain_script);
|
||||
|
||||
return Ok(BranchAndBoundCoinSelection::calculate_cs_result(
|
||||
vec![],
|
||||
required_utxos,
|
||||
fee_amount,
|
||||
excess,
|
||||
));
|
||||
}
|
||||
@ -502,8 +492,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
optional_utxos.clone(),
|
||||
curr_value,
|
||||
curr_available_value,
|
||||
actual_target,
|
||||
fee_amount,
|
||||
target_amount,
|
||||
cost_of_change,
|
||||
drain_script,
|
||||
fee_rate,
|
||||
@ -513,8 +502,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
required_utxos,
|
||||
optional_utxos,
|
||||
curr_value,
|
||||
actual_target,
|
||||
fee_amount,
|
||||
target_amount,
|
||||
drain_script,
|
||||
fee_rate,
|
||||
)
|
||||
@ -532,8 +520,7 @@ impl BranchAndBoundCoinSelection {
|
||||
mut optional_utxos: Vec<OutputGroup>,
|
||||
mut curr_value: i64,
|
||||
mut curr_available_value: i64,
|
||||
actual_target: i64,
|
||||
fee_amount: u64,
|
||||
target_amount: i64,
|
||||
cost_of_change: f32,
|
||||
drain_script: &Script,
|
||||
fee_rate: FeeRate,
|
||||
@ -559,11 +546,11 @@ impl BranchAndBoundCoinSelection {
|
||||
// Cannot possibly reach target with the amount remaining in the curr_available_value,
|
||||
// or the selected value is out of range.
|
||||
// Go back and try other branch
|
||||
if curr_value + curr_available_value < actual_target
|
||||
|| curr_value > actual_target + cost_of_change as i64
|
||||
if curr_value + curr_available_value < target_amount
|
||||
|| curr_value > target_amount + cost_of_change as i64
|
||||
{
|
||||
backtrack = true;
|
||||
} else if curr_value >= actual_target {
|
||||
} else if curr_value >= target_amount {
|
||||
// Selected value is within range, there's no point in going forward. Start
|
||||
// backtracking
|
||||
backtrack = true;
|
||||
@ -576,7 +563,7 @@ impl BranchAndBoundCoinSelection {
|
||||
}
|
||||
|
||||
// If we found a perfect match, break here
|
||||
if curr_value == actual_target {
|
||||
if curr_value == target_amount {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -634,15 +621,14 @@ impl BranchAndBoundCoinSelection {
|
||||
|
||||
// remaining_amount can't be negative as that would mean the
|
||||
// selection wasn't successful
|
||||
// actual_target = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (selected_amount - actual_target) as u64;
|
||||
// target_amount = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (selected_amount - target_amount) as u64;
|
||||
|
||||
let excess = decide_change(remaining_amount, fee_rate, drain_script);
|
||||
|
||||
Ok(BranchAndBoundCoinSelection::calculate_cs_result(
|
||||
selected_utxos,
|
||||
required_utxos,
|
||||
fee_amount,
|
||||
excess,
|
||||
))
|
||||
}
|
||||
@ -653,8 +639,7 @@ impl BranchAndBoundCoinSelection {
|
||||
required_utxos: Vec<OutputGroup>,
|
||||
mut optional_utxos: Vec<OutputGroup>,
|
||||
curr_value: i64,
|
||||
actual_target: i64,
|
||||
fee_amount: u64,
|
||||
target_amount: i64,
|
||||
drain_script: &Script,
|
||||
fee_rate: FeeRate,
|
||||
) -> CoinSelectionResult {
|
||||
@ -670,7 +655,7 @@ impl BranchAndBoundCoinSelection {
|
||||
let selected_utxos = optional_utxos.into_iter().fold(
|
||||
(curr_value, vec![]),
|
||||
|(mut amount, mut utxos), utxo| {
|
||||
if amount >= actual_target {
|
||||
if amount >= target_amount {
|
||||
(amount, utxos)
|
||||
} else {
|
||||
amount += utxo.effective_value;
|
||||
@ -682,27 +667,21 @@ impl BranchAndBoundCoinSelection {
|
||||
|
||||
// remaining_amount can't be negative as that would mean the
|
||||
// selection wasn't successful
|
||||
// actual_target = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (selected_utxos.0 - actual_target) as u64;
|
||||
// target_amount = amount_needed + (fee_amount - vin_fees)
|
||||
let remaining_amount = (selected_utxos.0 - target_amount) as u64;
|
||||
|
||||
let excess = decide_change(remaining_amount, fee_rate, drain_script);
|
||||
|
||||
BranchAndBoundCoinSelection::calculate_cs_result(
|
||||
selected_utxos.1,
|
||||
required_utxos,
|
||||
fee_amount,
|
||||
excess,
|
||||
)
|
||||
BranchAndBoundCoinSelection::calculate_cs_result(selected_utxos.1, required_utxos, excess)
|
||||
}
|
||||
|
||||
fn calculate_cs_result(
|
||||
mut selected_utxos: Vec<OutputGroup>,
|
||||
mut required_utxos: Vec<OutputGroup>,
|
||||
mut fee_amount: u64,
|
||||
excess: Excess,
|
||||
) -> CoinSelectionResult {
|
||||
selected_utxos.append(&mut required_utxos);
|
||||
fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<u64>();
|
||||
let fee_amount = selected_utxos.iter().map(|u| u.fee).sum::<u64>();
|
||||
let selected = selected_utxos
|
||||
.into_iter()
|
||||
.map(|u| u.weighted_utxo.utxo)
|
||||
@ -874,6 +853,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 250_000 + FEE_AMOUNT;
|
||||
|
||||
let result = LargestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -881,15 +861,14 @@ mod test {
|
||||
utxos,
|
||||
vec![],
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
250_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300_010);
|
||||
assert_eq!(result.fee_amount, 254)
|
||||
assert_eq!(result.fee_amount, 204)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -897,6 +876,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let result = LargestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -904,15 +884,14 @@ mod test {
|
||||
utxos,
|
||||
vec![],
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300_010);
|
||||
assert_eq!(result.fee_amount, 254);
|
||||
assert_eq!(result.fee_amount, 204);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -920,6 +899,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let result = LargestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -927,15 +907,14 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 1);
|
||||
assert_eq!(result.selected_amount(), 200_000);
|
||||
assert_eq!(result.fee_amount, 118);
|
||||
assert_eq!(result.fee_amount, 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -944,6 +923,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 500_000 + FEE_AMOUNT;
|
||||
|
||||
LargestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -951,8 +931,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
500_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -964,6 +943,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 250_000 + FEE_AMOUNT;
|
||||
|
||||
LargestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -971,8 +951,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1000.0),
|
||||
250_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -983,6 +962,7 @@ mod test {
|
||||
let mut database = MemoryDatabase::default();
|
||||
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 180_000 + FEE_AMOUNT;
|
||||
|
||||
let result = OldestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -990,15 +970,14 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
180_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 2);
|
||||
assert_eq!(result.selected_amount(), 200_000);
|
||||
assert_eq!(result.fee_amount, 186)
|
||||
assert_eq!(result.fee_amount, 136)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1042,21 +1021,22 @@ mod test {
|
||||
database.set_tx(&utxo1_tx_details).unwrap();
|
||||
database.set_tx(&utxo2_tx_details).unwrap();
|
||||
|
||||
let target_amount = 180_000 + FEE_AMOUNT;
|
||||
|
||||
let result = OldestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
vec![],
|
||||
vec![utxo3, utxo1, utxo2],
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
180_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 2);
|
||||
assert_eq!(result.selected_amount(), 200_000);
|
||||
assert_eq!(result.fee_amount, 186)
|
||||
assert_eq!(result.fee_amount, 136)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1064,6 +1044,7 @@ mod test {
|
||||
let mut database = MemoryDatabase::default();
|
||||
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let result = OldestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1071,15 +1052,14 @@ mod test {
|
||||
utxos,
|
||||
vec![],
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 500_000);
|
||||
assert_eq!(result.fee_amount, 254);
|
||||
assert_eq!(result.fee_amount, 204);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1087,6 +1067,7 @@ mod test {
|
||||
let mut database = MemoryDatabase::default();
|
||||
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let result = OldestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1094,15 +1075,14 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 1);
|
||||
assert_eq!(result.selected_amount(), 120_000);
|
||||
assert_eq!(result.fee_amount, 118);
|
||||
assert_eq!(result.fee_amount, 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1111,6 +1091,7 @@ mod test {
|
||||
let mut database = MemoryDatabase::default();
|
||||
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 600_000 + FEE_AMOUNT;
|
||||
|
||||
OldestFirstCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1118,8 +1099,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
600_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1131,8 +1111,7 @@ mod test {
|
||||
let mut database = MemoryDatabase::default();
|
||||
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
|
||||
|
||||
let amount_needed: u64 =
|
||||
utxos.iter().map(|wu| wu.utxo.txout().value).sum::<u64>() - (FEE_AMOUNT + 50);
|
||||
let target_amount: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::<u64>() - 50;
|
||||
let drain_script = Script::default();
|
||||
|
||||
OldestFirstCoinSelection::default()
|
||||
@ -1141,8 +1120,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1000.0),
|
||||
amount_needed,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1157,21 +1135,22 @@ mod test {
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
|
||||
let target_amount = 250_000 + FEE_AMOUNT;
|
||||
|
||||
let result = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
250_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300_000);
|
||||
assert_eq!(result.fee_amount, 254);
|
||||
assert_eq!(result.fee_amount, 204);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1179,6 +1158,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let result = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1186,15 +1166,14 @@ mod test {
|
||||
utxos.clone(),
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300_010);
|
||||
assert_eq!(result.fee_amount, 254);
|
||||
assert_eq!(result.fee_amount, 204);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1202,6 +1181,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 299756 + FEE_AMOUNT;
|
||||
|
||||
let result = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1209,15 +1189,14 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
299756,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300010);
|
||||
assert_eq!(result.fee_amount, 254);
|
||||
assert_eq!(result.fee_amount, 204);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1236,21 +1215,22 @@ mod test {
|
||||
assert!(amount > 150_000);
|
||||
let drain_script = Script::default();
|
||||
|
||||
let target_amount = 150_000 + FEE_AMOUNT;
|
||||
|
||||
let result = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
required,
|
||||
optional,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
150_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.selected.len(), 3);
|
||||
assert_eq!(result.selected_amount(), 300_010);
|
||||
assert!((result.fee_amount as f32 - 254.0).abs() < f32::EPSILON);
|
||||
assert!((result.fee_amount as f32 - 204.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1259,6 +1239,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 500_000 + FEE_AMOUNT;
|
||||
|
||||
BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1266,8 +1247,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
500_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1279,6 +1259,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 250_000 + FEE_AMOUNT;
|
||||
|
||||
BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
@ -1286,8 +1267,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1000.0),
|
||||
250_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1298,6 +1278,7 @@ mod test {
|
||||
let utxos = get_test_utxos();
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
let target_amount = 99932; // first utxo's effective value
|
||||
|
||||
let result = BranchAndBoundCoinSelection::new(0)
|
||||
.coin_select(
|
||||
@ -1305,8 +1286,7 @@ mod test {
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(1.0),
|
||||
99932, // first utxo's effective value
|
||||
0,
|
||||
target_amount,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1314,8 +1294,8 @@ mod test {
|
||||
assert_eq!(result.selected.len(), 1);
|
||||
assert_eq!(result.selected_amount(), 100_000);
|
||||
let input_size = (TXIN_BASE_WEIGHT + P2WPKH_SATISFACTION_SIZE).vbytes();
|
||||
let epsilon = 0.5;
|
||||
assert!((1.0 - (result.fee_amount as f32 / input_size as f32)).abs() < epsilon);
|
||||
// the final fee rate should be exactly the same as the fee rate given
|
||||
assert!((1.0 - (result.fee_amount as f32 / input_size as f32)).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1335,7 +1315,6 @@ mod test {
|
||||
optional_utxos,
|
||||
FeeRate::from_sat_per_vb(0.0),
|
||||
target_amount,
|
||||
0,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap();
|
||||
@ -1356,17 +1335,15 @@ mod test {
|
||||
|
||||
let size_of_change = 31;
|
||||
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
|
||||
|
||||
let drain_script = Script::default();
|
||||
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
BranchAndBoundCoinSelection::new(size_of_change)
|
||||
.bnb(
|
||||
vec![],
|
||||
utxos,
|
||||
0,
|
||||
curr_available_value,
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount as i64,
|
||||
cost_of_change,
|
||||
&drain_script,
|
||||
fee_rate,
|
||||
@ -1387,6 +1364,7 @@ mod test {
|
||||
|
||||
let size_of_change = 31;
|
||||
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
|
||||
let target_amount = 20_000 + FEE_AMOUNT;
|
||||
|
||||
let drain_script = Script::default();
|
||||
|
||||
@ -1396,8 +1374,7 @@ mod test {
|
||||
utxos,
|
||||
0,
|
||||
curr_available_value,
|
||||
20_000,
|
||||
FEE_AMOUNT,
|
||||
target_amount as i64,
|
||||
cost_of_change,
|
||||
&drain_script,
|
||||
fee_rate,
|
||||
@ -1434,14 +1411,13 @@ mod test {
|
||||
curr_value,
|
||||
curr_available_value,
|
||||
target_amount,
|
||||
FEE_AMOUNT,
|
||||
cost_of_change,
|
||||
&drain_script,
|
||||
fee_rate,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.selected_amount(), 100_000);
|
||||
assert_eq!(result.fee_amount, 186);
|
||||
assert_eq!(result.fee_amount, 136);
|
||||
}
|
||||
|
||||
// TODO: bnb() function should be optimized, and this test should be done with more utxos
|
||||
@ -1475,7 +1451,6 @@ mod test {
|
||||
curr_value,
|
||||
curr_available_value,
|
||||
target_amount,
|
||||
0,
|
||||
0.0,
|
||||
&drain_script,
|
||||
fee_rate,
|
||||
@ -1490,7 +1465,7 @@ mod test {
|
||||
let seed = [0; 32];
|
||||
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
||||
let mut utxos = generate_random_utxos(&mut rng, 300);
|
||||
let target_amount = sum_random_utxos(&mut rng, &mut utxos);
|
||||
let target_amount = sum_random_utxos(&mut rng, &mut utxos) + FEE_AMOUNT;
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_vb(1.0);
|
||||
let utxos: Vec<OutputGroup> = utxos
|
||||
@ -1505,12 +1480,11 @@ mod test {
|
||||
utxos,
|
||||
0,
|
||||
target_amount as i64,
|
||||
FEE_AMOUNT,
|
||||
&drain_script,
|
||||
fee_rate,
|
||||
);
|
||||
|
||||
assert!(result.selected_amount() > target_amount);
|
||||
assert_eq!(result.fee_amount, (50 + result.selected.len() * 68) as u64);
|
||||
assert_eq!(result.fee_amount, (result.selected.len() * 68) as u64);
|
||||
}
|
||||
}
|
||||
|
@ -802,11 +802,10 @@ where
|
||||
required_utxos,
|
||||
optional_utxos,
|
||||
fee_rate,
|
||||
outgoing,
|
||||
fee_amount,
|
||||
outgoing + fee_amount,
|
||||
&drain_script,
|
||||
)?;
|
||||
let mut fee_amount = coin_selection.fee_amount;
|
||||
fee_amount += coin_selection.fee_amount;
|
||||
let excess = &coin_selection.excess;
|
||||
|
||||
tx.input = coin_selection
|
||||
|
Loading…
x
Reference in New Issue
Block a user