Skip to content

Commit

Permalink
draft: Add arb_tests for bnb
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Sep 15, 2024
1 parent 331cb81 commit fb7133c
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 2 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ keywords = ["bitcoin", "coin-selection", "coin", "coinselection", "utxo"]
readme = "README.md"

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

[dev-dependencies]
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610", features = ["arbitrary"] }
criterion = "0.3"
bitcoin-coin-selection = {path = ".", features = ["rand"]}
rand = "0.8.5"
arbitrary = { version = "1", features = ["derive"] }
arbtest = "0.3.1"

[[bench]]
name = "coin_selection"
Expand Down
162 changes: 161 additions & 1 deletion src/branch_and_bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ mod tests {
use core::str::FromStr;
use std::iter::{once, zip};

use arbitrary::{Arbitrary, Result, Unstructured};
use arbtest::arbtest;
use bitcoin::{Amount, ScriptBuf, TxOut, Weight};

use super::*;
Expand All @@ -330,7 +332,11 @@ mod tests {
weighted_utxos: Vec<&'a str>,
}

#[derive(Debug)]
const PROPTEST_POOL_SIZE: usize = 5;
const PROPTEST_MAX_SAT_AMOUNT: u64 = 100_000;
const PROPTEST_MIN_SAT_AMOUNT: u64 = 161; //tx_in base_weight + 1

#[derive(Debug, Clone, PartialEq, Ord, Eq, PartialOrd, Arbitrary)]
pub struct Utxo {
output: TxOut,
satisfaction_weight: Weight,
Expand Down Expand Up @@ -415,6 +421,55 @@ mod tests {
}
}

// Use in place of arbitrary_in_range()
// see: https://github.com/rust-fuzz/arbitrary/pull/192
fn arb_amount_in_range(u: &mut Unstructured, r: std::ops::RangeInclusive<u64>) -> Amount {
let u = u.int_in_range::<u64>(r).unwrap();
Amount::from_sat(u)
}

// Use in place of arbitrary_in_range()
// see: https://github.com/rust-fuzz/arbitrary/pull/192
fn arb_fee_rate_in_range(u: &mut Unstructured, r: std::ops::RangeInclusive<u64>) -> FeeRate {
let u = u.int_in_range::<u64>(r).unwrap();
FeeRate::from_sat_per_kwu(u)
}

const TX_IN_BASE_WEIGHT: u64 = 160;
fn calculate_max_fee_rate(amount: Amount, weight: Weight) -> FeeRate {
amount / (weight + Weight::from_wu(TX_IN_BASE_WEIGHT))
}

#[derive(Debug)]
pub struct UtxoPool {
utxos: Vec<Utxo>,
}

impl<'a> Arbitrary<'a> for UtxoPool
where
Utxo: Arbitrary<'a>,
{
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let mut p = Vec::with_capacity(PROPTEST_POOL_SIZE);

for _ in 0..PROPTEST_POOL_SIZE {
let amount_int = u.int_in_range::<u64>(PROPTEST_MIN_SAT_AMOUNT..=PROPTEST_MAX_SAT_AMOUNT).unwrap();
let amount = Amount::from_sat(amount_int);

let weight_int = u.int_in_range::<u64>(1..=amount_int).unwrap();
let weight = Weight::from_wu(weight_int);

let utxos = build_utxo(amount, weight);

p.push(utxos);
}

let pool = UtxoPool { utxos: p };

Ok(pool)
}
}

#[test]
fn select_coins_bnb_one() { assert_coin_select("1 cBTC", &["1 cBTC"]); }

Expand Down Expand Up @@ -684,4 +739,109 @@ mod tests {
assert_eq!(list.len(), 1);
assert_eq!(list.next().unwrap().value(), Amount::from_sat(target));
}

#[test]
fn select_one_of_one_idealized() {
let minimal_non_dust: u64 = 1;
let effective_value_max: u64 = SignedAmount::MAX.to_sat() as u64;

arbtest(|u| {
let a = arb_amount_in_range(u, minimal_non_dust..=effective_value_max);
let u = build_utxo(a, Weight::ZERO);
let pool = vec![u.clone()];

let coins = select_coins_bnb(
u.value(),
Amount::ZERO,
FeeRate::ZERO,
FeeRate::ZERO,
&pool,
);
assert!(coins.is_some());

Ok(())
});
}

#[test]
fn select_one_of_many() {
arbtest(|u| {
let pool = UtxoPool::arbitrary(u)?;
let utxo = u.choose(&pool.utxos)?;

let max_fee_rate = calculate_max_fee_rate(utxo.value(), utxo.satisfaction_weight());
let fee_rate = arb_fee_rate_in_range(u, 1..=max_fee_rate.to_sat_per_kwu());

let target = bitcoin::transaction::effective_value(
fee_rate,
utxo.satisfaction_weight(),
utxo.value(),
)
.unwrap()
.to_unsigned()
.unwrap();

let coins: Vec<_> = select_coins_bnb(
target,
Amount::ZERO,
fee_rate,
fee_rate,
&pool.utxos,
).unwrap().collect();
assert_eq!(vec![utxo], coins);

Ok(())
});
}

#[test]
fn select_many_of_many() {
arbtest(|u| {
let pool = UtxoPool::arbitrary(u)?;

let mut selection_size = u.int_in_range::<u64>(1..=10).unwrap();
let mut solution_set: Vec<Utxo> = Vec::new();

let mut max_fee_rate = FeeRate::MAX;
while selection_size > 0 {
let utxo = u.choose(&pool.utxos)?;

let fee_rate: FeeRate = calculate_max_fee_rate(utxo.value(), utxo.satisfaction_weight());
if fee_rate < max_fee_rate {
max_fee_rate = fee_rate;
}

if !solution_set.contains(utxo) {
solution_set.push(utxo.clone());
}

selection_size -= 1;
}

let fee_rate = arb_fee_rate_in_range(u, 1..=max_fee_rate.to_sat_per_kwu()); // up to 8 sat/vB

let target: SignedAmount = solution_set.iter().map(|u| {
bitcoin::transaction::effective_value(
fee_rate,
u.satisfaction_weight(),
u.value(),
)
.unwrap()
}).sum();

let coins = select_coins_bnb(
target.to_unsigned().unwrap(),
Amount::ZERO,
fee_rate,
fee_rate,
&pool.utxos,
);

let mut inputs: Vec<Utxo> = coins.unwrap().map(|x| x.clone()).collect();
let inputs_sort = inputs.sort();
assert_eq!(inputs_sort, solution_set.sort());

Ok(())
});
}
}

0 comments on commit fb7133c

Please sign in to comment.