Skip to content

Commit

Permalink
Use effective_value in Rust Bitcoin
Browse files Browse the repository at this point in the history
This PR removes the effective_value calculation instead prefering to
outsource the calculation to Rust Bitcoin.  Also, temporarily remove the
error mod for now since Rust Bitcoin returns Option instead of Result.
  • Loading branch information
yancyribbens committed Dec 7, 2023
1 parent 17fbd82 commit e2446b1
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 94 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ keywords = ["crypto", "bitcoin"]
readme = "README.md"

[dependencies]
bitcoin = { git="https://github.com/rust-bitcoin/rust-bitcoin", branch="master" }
bitcoin = { git="https://github.com/yancyribbens/rust-bitcoin", branch="add-effective-value-calculation" }
rand = {version = "0.8.5", default-features = false, optional = true}

[dev-dependencies]
rust-bitcoin-coin-selection = {path = ".", features = ["rand"]}
rand = "0.8.5"

[patch.crates-io]
bitcoin_hashes = { git="https://github.com/rust-bitcoin/rust-bitcoin.git" }
bitcoin-io = { git = "https://github.com/yancyribbens/rust-bitcoin", branch = "add-effective-value-calculation" }
26 changes: 0 additions & 26 deletions src/errors.rs

This file was deleted.

17 changes: 5 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#![deny(non_snake_case)]
#![deny(unused_mut)]
#![deny(missing_docs)]

// Experimental features we need.
#![cfg_attr(bench, feature(test))]
#![cfg_attr(docsrs, feature(doc_cfg))]
Expand All @@ -19,15 +18,13 @@ extern crate test;

use std::cmp::Reverse;

mod errors;
mod single_random_draw;

use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::TxOut;
use bitcoin::Weight;

use crate::errors::Error;
use crate::single_random_draw::select_coins_srd;
use rand::thread_rng;

Expand All @@ -47,9 +44,9 @@ const CHANGE_LOWER: Amount = Amount::from_sat(50_000);
/// <https://github.com/bitcoindevkit/bdk/blob/feafaaca31a0a40afc03ce98591d151c48c74fa2/crates/bdk/src/types.rs#L181>
#[derive(Clone, Debug, PartialEq)]
pub struct WeightedUtxo {
/// The satisfaction_weight is the size of the required params to satisfy the UTXO.
/// The satisfaction_weight is the size of the required params to satisfy the UTXO.
pub satisfaction_weight: Weight,
/// The corresponding UTXO.
/// The corresponding UTXO.
pub utxo: TxOut,
}

Expand All @@ -66,14 +63,10 @@ pub fn select_coins<T: Utxo>(
fee_rate: FeeRate,
weighted_utxos: &mut [WeightedUtxo],
utxo_pool: &mut [T],
) -> Result<Vec<TxOut>, Error> {
) -> Option<Vec<WeightedUtxo>> {
match select_coins_bnb(target.to_sat(), cost_of_change, utxo_pool) {
Some(_res) => Ok(Vec::new()),
None => Ok(select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng())
.unwrap()
.into_iter()
.map(|w| w.utxo)
.collect()),
Some(_res) => Some(Vec::new()),
None => select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()),
}
}

Expand Down
85 changes: 31 additions & 54 deletions src/single_random_draw.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
//! This library provides efficient algorithms to compose a set of unspent transaction outputs
//! (UTXOs).

use crate::errors::Error;
use crate::WeightedUtxo;
use crate::CHANGE_LOWER;
use bitcoin::blockdata::effective_value;
use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::TxIn;
use rand::seq::SliceRandom;

/// Calculates the effective_value of an input.
///
/// Returns `Ok(None)` if the effective_value is negative. If the effective_value is positive, return `Ok(Some(Amount))`.
///
/// ## Errors
///
/// Returns `Err(Error::Multiplication)` if `FeeRate` * `Weight` overflows.
///
fn get_effective_value(
weighted_utxo: &WeightedUtxo,
fee_rate: FeeRate,
) -> Result<Option<Amount>, Error> {
let satisfaction_weight = weighted_utxo.satisfaction_weight;

let weight = satisfaction_weight
.checked_add(TxIn::BASE_WEIGHT)
.ok_or(Error::AdditionOverflow(satisfaction_weight, TxIn::BASE_WEIGHT))?;

let input_fee = fee_rate
.checked_mul_by_weight(weight)
.ok_or(Error::MultiplicationOverflow(satisfaction_weight, fee_rate))?;

Ok(weighted_utxo.utxo.value.checked_sub(input_fee))
}

/// Randomly select coins for the given target by shuffling the UTXO pool and
/// taking UTXOs until the given target is reached.
///
Expand All @@ -59,7 +33,7 @@ pub fn select_coins_srd<R: rand::Rng + ?Sized>(
fee_rate: FeeRate,
weighted_utxos: &mut [WeightedUtxo],
rng: &mut R,
) -> Result<Vec<WeightedUtxo>, Error> {
) -> Option<Vec<WeightedUtxo>> {
let mut result: Vec<WeightedUtxo> = Vec::new();

weighted_utxos.shuffle(rng);
Expand All @@ -68,22 +42,22 @@ pub fn select_coins_srd<R: rand::Rng + ?Sized>(
let mut value = Amount::ZERO;

for w_utxo in weighted_utxos {
let effective_value: Option<Amount> = get_effective_value(w_utxo, fee_rate)?;
let utxo_value = w_utxo.utxo.value;
let effective_value = effective_value(fee_rate, w_utxo.satisfaction_weight, utxo_value)?;

// skip if effective_value is negative.
match effective_value {
Some(e) => value += e,
None => continue,
}
value += match effective_value.to_unsigned() {
Ok(amt) => amt,
Err(_) => continue,
};

result.push(w_utxo.clone());

if value >= threshold {
return Ok(result);
return Some(result);
}
}

Ok(Vec::new())
Some(Vec::new())
}

#[cfg(test)]
Expand Down Expand Up @@ -171,32 +145,33 @@ mod tests {

#[test]
fn select_coins_skip_negative_effective_value() {
let target: Amount = Amount::from_str("1 cBTC").unwrap() - CHANGE_LOWER;
let target: Amount = Amount::from_str("2 cBTC").unwrap() - CHANGE_LOWER;

let mut weighted_utxos: Vec<WeightedUtxo> = vec![WeightedUtxo {
let mut weighted_utxos = create_weighted_utxos();
weighted_utxos.push(WeightedUtxo {
satisfaction_weight: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("1 sat").unwrap(),
script_pubkey: ScriptBuf::new(),
},
}];
});

let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng())
let mut rng = get_rng();
let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut rng)
.expect("unexpected error");

assert!(result.is_empty());
let mut expected_utxos = create_weighted_utxos();
expected_utxos.shuffle(&mut rng);
assert_eq!(result, expected_utxos);
}

#[test]
fn select_coins_srd_fee_rate_error() {
let target: Amount = Amount::from_str("2 cBTC").unwrap();
let mut weighted_utxos: Vec<WeightedUtxo> = create_weighted_utxos();

let result: Error =
select_coins_srd(target, FeeRate::MAX, &mut weighted_utxos, &mut get_rng())
.expect_err("expected error");

assert_eq!(result.to_string(), "204 * 18446744073709551615 exceeds u64 Max");
let result = select_coins_srd(target, FeeRate::MAX, &mut weighted_utxos, &mut get_rng());
assert!(result.is_none());
}

#[test]
Expand All @@ -212,10 +187,14 @@ mod tests {

#[test]
fn select_coins_srd_with_high_fee() {
let target: Amount = Amount::from_str("1.905 cBTC").unwrap();
// The high fee_rate will cause both utxos to be consumed
// instead of just one.
let fee_rate: FeeRate = FeeRate::from_sat_per_kwu(250);
// the first UTXO is 2 cBTC. If the fee is greater than 10 sats,
// then more than the single 2 cBTC output will need to be selected
// if the target is 1.99999 cBTC. That is, 2 cBTC - 1.9999 cBTC = 10 sats.
let target: Amount = Amount::from_str("1.99999 cBTC").unwrap();

// fee = 15 sats, since
// 40 sat/kwu * (204 + BASE_WEIGHT) = 15 sats
let fee_rate: FeeRate = FeeRate::from_sat_per_kwu(40);
let mut weighted_utxos: Vec<WeightedUtxo> = create_weighted_utxos();

let result = select_coins_srd(target, fee_rate, &mut weighted_utxos, &mut get_rng())
Expand All @@ -236,9 +215,7 @@ mod tests {
},
}];

let result: Error = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng())
.expect_err("expected error");

assert_eq!(result.to_string(), "18446744073709551615 + 40 exceeds u64 Max");
let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng());
assert!(result.is_none());
}
}

0 comments on commit e2446b1

Please sign in to comment.