Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Feb 21, 2024
1 parent 843eaf4 commit 510dbc6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 282 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ keywords = ["crypto", "bitcoin"]
readme = "README.md"

[dependencies]
bitcoin = { git="https://github.com/yancyribbens/rust-bitcoin", rev="426408" }
bitcoin = { git="https://github.com/yancyribbens/rust-bitcoin", rev="975ada" }
rand = {version = "0.8.5", default-features = false, optional = true}

[dev-dependencies]
Expand All @@ -24,10 +24,10 @@ rust-bitcoin-coin-selection = {path = ".", features = ["rand"]}
rand = "0.8.5"

[patch.crates-io]
bitcoin_hashes = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="426408" }
bitcoin-io = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="426408" }
bitcoin-units = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="426408" }
bitcoin-internals = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="426408" }
bitcoin_hashes = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="975ada" }
bitcoin-io = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="975ada" }
bitcoin-units = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="975ada" }
bitcoin-internals = { git = "https://github.com/yancyribbens/rust-bitcoin", rev="975ada" }

[[bench]]
name = "coin_selection"
Expand Down
26 changes: 17 additions & 9 deletions src/branch_and_bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
//!
//! This module introduces the Branch and Bound Coin Selection Algorithm.
use crate::calculate_waste;
use crate::WeightedUtxo;
use bitcoin::amount::CheckedSum;
use bitcoin::blockdata::transaction::effective_value;
use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::SignedAmount;
use bitcoin::blockdata::transaction::effective_value;
use bitcoin::Weight;

/// Select coins bnb performs a depth first branch and bound search. The search traverses a
/// binary tree with a maximum depth n where n is the size of the UTXO pool to be searched.
Expand Down Expand Up @@ -122,13 +124,13 @@ use bitcoin::blockdata::transaction::effective_value;
// b) It's a high fee environment such that adding more utxos will increase current_waste.
//
// If either a or b is true, we consider the search path no longer viable to continue down.
pub fn select_coins_bnb(
pub fn select_coins_bnb<T: WeightedUtxo>(
target: Amount,
cost_of_change: Amount,
fee_rate: FeeRate,
long_term_fee_rate: FeeRate,
weighted_utxos: &[WeightedUtxo],
) -> Option<std::vec::IntoIter<&'_ WeightedUtxo>> {
weighted_utxos: &[T],
) -> Option<std::vec::IntoIter<&'_ T>> {
// Total_Tries in Core:
// https://github.com/bitcoin/bitcoin/blob/1d9da8da309d1dbf9aef15eb8dc43b4a2dc3d309/src/wallet/coinselection.cpp#L74
const ITERATION_LIMIT: i32 = 100_000;
Expand Down Expand Up @@ -157,10 +159,16 @@ pub fn select_coins_bnb(
// Creates a tuple of (effective_value, waste, weighted_utxo)
// * filter out effective values and wastes that are None (error).
// * filter out negative effective values.
let mut w_utxos: Vec<(Amount, SignedAmount, &WeightedUtxo)> = weighted_utxos
let mut w_utxos: Vec<(Amount, SignedAmount, &T)> = weighted_utxos
.iter()
// calculate effective_value and waste for each w_utxo.
.map(|wu| (effective_value(fee_rate, wu.satisfaction_weight, wu.utxo.value), wu.waste(fee_rate, long_term_fee_rate), wu))
.map(|wu| {
(
effective_value(fee_rate, wu.satisfaction_weight(), wu.utxo().value),
calculate_waste(Weight::ZERO, fee_rate, long_term_fee_rate),
wu,
)
})
// remove utxos that either had an error in the effective_value or waste calculation.
.filter(|(eff_val, waste, _)| !eff_val.is_none() && !waste.is_none())
// unwrap the option type since we know they are not None (see previous step).
Expand Down Expand Up @@ -340,10 +348,10 @@ pub fn select_coins_bnb(

// Copy the index list into a list such that for each
// index, the corresponding w_utxo is copied.
fn index_to_utxo_list(
fn index_to_utxo_list<T: WeightedUtxo>(
index_list: Option<Vec<usize>>,
wu: Vec<(Amount, SignedAmount, &WeightedUtxo)>,
) -> Option<std::vec::IntoIter<&'_ WeightedUtxo>> {
wu: Vec<(Amount, SignedAmount, &T)>,
) -> Option<std::vec::IntoIter<&'_ T>> {
// Doing this to satisfy the borrow checker such that the
// refs &WeightedUtxo in `wu` have the same lifetime as the
// returned &WeightedUtxo.
Expand Down
68 changes: 37 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,54 @@ use bitcoin::TxOut;
use bitcoin::Weight;

pub use crate::branch_and_bound::select_coins_bnb;
use crate::single_random_draw::select_coins_srd;
// use crate::single_random_draw::select_coins_srd;
use rand::thread_rng;

// https://github.com/bitcoin/bitcoin/blob/f722a9bd132222d9d5cd503b5af25c905b205cdb/src/wallet/coinselection.h#L20
const CHANGE_LOWER: Amount = Amount::from_sat(50_000);

/// This struct contains the weight of all params needed to satisfy the UTXO.
///
/// The idea of using a WeightUtxo type was inspired by the BDK implementation:
/// <https://github.com/bitcoindevkit/bdk/blob/feafaaca31a0a40afc03ce98591d151c48c74fa2/crates/bdk/src/types.rs#L181>
#[derive(Clone, Debug, PartialEq)]
// note, change this to private? No good reason to be public.
pub struct WeightedUtxo {
/// The satisfaction_weight is the size of the required params to satisfy the UTXO.
pub satisfaction_weight: Weight,
/// The corresponding UTXO.
pub utxo: TxOut,
}

// Serialized length of a u32.
const SEQUENCE_SIZE: u64 = 4;
// The serialized lengths of txid and vout.
const OUTPOINT_SIZE: u64 = 32 + 4;
const TX_IN_BASE_WEIGHT: Weight =
Weight::from_vb_unwrap(OUTPOINT_SIZE + SEQUENCE_SIZE);
const TX_IN_BASE_WEIGHT: Weight = Weight::from_vb_unwrap(OUTPOINT_SIZE + SEQUENCE_SIZE);

// Predict the fee Amount to spend a UTXO.
//
// To predict the fee, the predicted weight is:
// weight = satisfaction_weight + TX_IN base weight.
//
//
// The fee is then calculated as:
// fee = weight * fee_rate
fn calculate_fee_prediction(satisfaction_weight: Weight, fee_rate: FeeRate) -> Option<Amount> {
let weight = satisfaction_weight.checked_add(TX_IN_BASE_WEIGHT)?;
fee_rate.checked_mul_by_weight(weight)
}

impl WeightedUtxo {
fn waste(&self, fee_rate: FeeRate, long_term_fee_rate: FeeRate) -> Option<SignedAmount> {
let fee: SignedAmount = calculate_fee_prediction(self.satisfaction_weight, fee_rate)?.to_signed().ok()?;
let lt_fee: SignedAmount = calculate_fee_prediction(self.satisfaction_weight, long_term_fee_rate)?.to_signed().ok()?;
fee.checked_sub(lt_fee)
}
// Calculates how wasteful a UTXO is to spend.
//
// The wastefulness of a UTXO is depends on the current fee environment.
// To calculate how wasteful spending a UTXO (now) is:
//
// waste = current_fee - long_term_fee
fn calculate_waste(
satisfaction_weight: Weight,
fee_rate: FeeRate,
long_term_fee_rate: FeeRate,
) -> Option<SignedAmount> {
let fee: SignedAmount =
calculate_fee_prediction(satisfaction_weight, fee_rate)?.to_signed().ok()?;
let lt_fee: SignedAmount =
calculate_fee_prediction(satisfaction_weight, long_term_fee_rate)?.to_signed().ok()?;
fee.checked_sub(lt_fee)
}

/// The WeightedUtxo trait provides the necessary interfaces for coin-selection.
pub trait WeightedUtxo {
/// The weight of a UTXO once the spending conditions are satisfied.
fn satisfaction_weight(&self) -> Weight;
/// The concrete UTXO.
fn utxo(&self) -> TxOut;
}

/// Select coins first using BnB algorithm similar to what is done in bitcoin
Expand All @@ -79,21 +83,23 @@ impl WeightedUtxo {
/// Requires compilation with the "rand" feature.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn select_coins(
pub fn select_coins<T: WeightedUtxo>(
target: Amount,
cost_of_change: Amount,
fee_rate: FeeRate,
long_term_fee_rate: FeeRate,
weighted_utxos: &[WeightedUtxo],
) -> Option<std::vec::IntoIter<&WeightedUtxo>> {
weighted_utxos: &[T],
) -> Option<std::vec::IntoIter<&T>> {
{
let bnb =
select_coins_bnb(target, cost_of_change, fee_rate, long_term_fee_rate, weighted_utxos);

if bnb.is_some() {
bnb
} else {
select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng())
}
//if bnb.is_some() {
//bnb
//} else {
//select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng())
//}

bnb
}
}
Loading

0 comments on commit 510dbc6

Please sign in to comment.