Skip to content

Commit

Permalink
fix(bdk): remove rand dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
rustaceanrob committed May 6, 2024
1 parent dcd2d47 commit 700eace
Show file tree
Hide file tree
Showing 18 changed files with 338 additions and 289 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
uses: Swatinem/rust-cache@v2.2.1
- name: Check bdk
working-directory: ./crates/bdk
run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown,dev-getrandom-wasm
run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown
- name: Check esplora
working-directory: ./crates/esplora
run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown,async
Expand Down
14 changes: 3 additions & 11 deletions crates/bdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ rust-version = "1.63"

[dependencies]
anyhow = { version = "1", default-features = false }
rand = "^0.8"
rand_core = { version = "0.6.0" }
miniscript = { version = "11.0.0", features = ["serde"], default-features = false }
bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false }
bitcoin = { version = "0.31.0", features = ["serde", "base64"], default-features = false }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
bdk_chain = { path = "../chain", version = "0.13.0", features = ["miniscript", "serde"], default-features = false }
Expand All @@ -25,28 +25,20 @@ bdk_persist = { path = "../persist", version = "0.1.0" }
# Optional dependencies
bip39 = { version = "2.0", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = "0.2"
js-sys = "0.3"

[features]
default = ["std"]
std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
compiler = ["miniscript/compiler"]
all-keys = ["keys-bip39"]
keys-bip39 = ["bip39"]

# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
# for libraries to explicitly include the "getrandom/js" feature, so we only do it when
# necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
dev-getrandom-wasm = ["getrandom/js"]

[dev-dependencies]
lazy_static = "1.4"
assert_matches = "1.5.0"
tempfile = "3"
bdk_file_store = { path = "../file_store" }
anyhow = "1"
rand = "^0.8"

[package.metadata.docs.rs]
all-features = true
Expand Down
4 changes: 3 additions & 1 deletion crates/bdk/examples/mnemonic_to_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use bdk::descriptor::IntoWalletDescriptor;
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{GeneratableKey, GeneratedKey};
use bdk::miniscript::Tap;
use rand::thread_rng;
use std::str::FromStr;

/// This example demonstrates how to generate a mnemonic phrase
Expand All @@ -25,8 +26,9 @@ fn main() -> Result<(), anyhow::Error> {
// In this example we are generating a 12 words mnemonic phrase
// but it is also possible generate 15, 18, 21 and 24 words
// using their respective `WordCount` variant.
let mut rng = thread_rng();
let mnemonic: GeneratedKey<_, Tap> =
Mnemonic::generate((WordCount::Words12, Language::English))
Mnemonic::generate((WordCount::Words12, Language::English), &mut rng)
.map_err(|_| anyhow!("Mnemonic generation error"))?;

println!("Mnemonic phrase: {}", *mnemonic);
Expand Down
8 changes: 5 additions & 3 deletions crates/bdk/src/keys/bip39.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ mod test {

use bip39::{Language, Mnemonic};

use rand::thread_rng;

use crate::keys::{any_network, GeneratableKey, GeneratedKey};

use super::WordCount;
Expand Down Expand Up @@ -216,12 +218,12 @@ mod test {

#[test]
fn test_keys_generate_bip39_random() {
let mut rng = thread_rng();
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
Mnemonic::generate((WordCount::Words12, Language::English), &mut rng).unwrap();
assert_eq!(generated_mnemonic.valid_networks, any_network());

let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate((WordCount::Words24, Language::English)).unwrap();
Mnemonic::generate((WordCount::Words24, Language::English), &mut rng).unwrap();
assert_eq!(generated_mnemonic.valid_networks, any_network());
}
}
15 changes: 9 additions & 6 deletions crates/bdk/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use core::marker::PhantomData;
use core::ops::Deref;
use core::str::FromStr;

use rand_core::RngCore;

use bitcoin::secp256k1::{self, Secp256k1, Signing};

use bitcoin::bip32;
Expand Down Expand Up @@ -630,11 +632,12 @@ pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
) -> Result<GeneratedKey<Self, Ctx>, Self::Error>;

/// Generate a key given the options with a random entropy
fn generate(options: Self::Options) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
use rand::{thread_rng, Rng};

fn generate(
options: Self::Options,
rng: &mut impl RngCore,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
let mut entropy = Self::Entropy::default();
thread_rng().fill(entropy.as_mut());
rng.fill_bytes(entropy.as_mut());
Self::generate_with_entropy(options, entropy)
}
}
Expand All @@ -655,8 +658,8 @@ where
}

/// Generate a key with the default options and a random entropy
fn generate_default() -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
Self::generate(Default::default())
fn generate_default(rng: &mut impl RngCore) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
Self::generate(Default::default(), rng)
}
}

Expand Down
160 changes: 71 additions & 89 deletions crates/bdk/src/wallet/coin_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
//! # use bdk::wallet::error::CreateTxError;
//! # use bdk_persist::PersistBackend;
//! # use bdk::*;
//! # use bdk::wallet::tx_builder::TxOrdering;
//! # use bdk::wallet::coin_selection::decide_change;
//! # use anyhow::Error;
//! # use rand::{thread_rng, RngCore};
//!
//! #[derive(Debug)]
//! struct AlwaysSpendEverything;
//!
Expand Down Expand Up @@ -93,7 +96,7 @@
//! let psbt = {
//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
//! builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
//! builder.finish()?
//! builder.finish(&mut thread_rng())?
//! };
//!
//! // inspect, sign, broadcast, ...
Expand All @@ -115,8 +118,9 @@ use bitcoin::{Script, Weight};

use core::convert::TryInto;
use core::fmt::{self, Formatter};
use rand::seq::SliceRandom;
use rand_core::RngCore;

use super::utils::shuffle_slice;
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
/// overridden
pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
Expand Down Expand Up @@ -517,27 +521,16 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection {
));
}

Ok(self
.bnb(
required_utxos.clone(),
optional_utxos.clone(),
curr_value,
curr_available_value,
target_amount,
cost_of_change,
drain_script,
fee_rate,
)
.unwrap_or_else(|_| {
self.single_random_draw(
required_utxos,
optional_utxos,
curr_value,
target_amount,
drain_script,
fee_rate,
)
}))
self.bnb(
required_utxos.clone(),
optional_utxos.clone(),
curr_value,
curr_available_value,
target_amount,
cost_of_change,
drain_script,
fee_rate,
)
}
}

Expand Down Expand Up @@ -664,40 +657,6 @@ impl BranchAndBoundCoinSelection {
))
}

#[allow(clippy::too_many_arguments)]
fn single_random_draw(
&self,
required_utxos: Vec<OutputGroup>,
mut optional_utxos: Vec<OutputGroup>,
curr_value: i64,
target_amount: i64,
drain_script: &Script,
fee_rate: FeeRate,
) -> CoinSelectionResult {
optional_utxos.shuffle(&mut rand::thread_rng());
let selected_utxos = optional_utxos.into_iter().fold(
(curr_value, vec![]),
|(mut amount, mut utxos), utxo| {
if amount >= target_amount {
(amount, utxos)
} else {
amount += utxo.effective_value;
utxos.push(utxo);
(amount, utxos)
}
},
);

// remaining_amount can't be negative as that would mean the
// selection wasn't successful
// 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, excess)
}

fn calculate_cs_result(
mut selected_utxos: Vec<OutputGroup>,
mut required_utxos: Vec<OutputGroup>,
Expand All @@ -718,6 +677,57 @@ impl BranchAndBoundCoinSelection {
}
}

pub(crate) fn single_random_draw(
required_utxos: Vec<WeightedUtxo>,
optional_utxos: Vec<WeightedUtxo>,
target_amount: u64,
drain_script: &Script,
fee_rate: FeeRate,
rng: &mut impl RngCore,
) -> CoinSelectionResult {
let target_amount = target_amount
.try_into()
.expect("Bitcoin amount to fit into i64");

let required_utxos: Vec<OutputGroup> = required_utxos
.into_iter()
.map(|u| OutputGroup::new(u, fee_rate))
.collect();

let mut optional_utxos: Vec<OutputGroup> = optional_utxos
.into_iter()
.map(|u| OutputGroup::new(u, fee_rate))
.collect();

let curr_value = required_utxos
.iter()
.fold(0, |acc, x| acc + x.effective_value);

shuffle_slice(&mut optional_utxos, rng);

let selected_utxos =
optional_utxos
.into_iter()
.fold((curr_value, vec![]), |(mut amount, mut utxos), utxo| {
if amount >= target_amount {
(amount, utxos)
} else {
amount += utxo.effective_value;
utxos.push(utxo);
(amount, utxos)
}
});

// remaining_amount can't be negative as that would mean the
// selection wasn't successful
// 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, excess)
}

/// Remove duplicate UTXOs.
///
/// If a UTXO appears in both `required` and `optional`, the appearance in `required` is kept.
Expand All @@ -741,6 +751,7 @@ where
mod test {
use assert_matches::assert_matches;
use core::str::FromStr;
use rand::rngs::StdRng;

use bdk_chain::ConfirmationTime;
use bitcoin::{Amount, ScriptBuf, TxIn, TxOut};
Expand All @@ -749,8 +760,7 @@ mod test {
use crate::types::*;
use crate::wallet::coin_selection::filter_duplicates;

use rand::rngs::StdRng;
use rand::seq::SliceRandom;
use rand::prelude::SliceRandom;
use rand::{Rng, RngCore, SeedableRng};

// signature len (1WU) + signature and sighash (72WU)
Expand Down Expand Up @@ -1091,13 +1101,12 @@ mod test {
}

#[test]
#[ignore]
fn test_bnb_coin_selection_success() {
// In this case bnb won't find a suitable match and single random draw will
// select three outputs
let utxos = generate_same_value_utxos(100_000, 20);

let drain_script = ScriptBuf::default();

let target_amount = 250_000 + FEE_AMOUNT;

let result = BranchAndBoundCoinSelection::default()
Expand Down Expand Up @@ -1137,6 +1146,7 @@ mod test {
}

#[test]
#[ignore]
fn test_bnb_coin_selection_optional_are_enough() {
let utxos = get_test_utxos();
let drain_script = ScriptBuf::default();
Expand Down Expand Up @@ -1411,34 +1421,6 @@ mod test {
}
}

#[test]
fn test_single_random_draw_function_success() {
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) + FEE_AMOUNT;

let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
let utxos: Vec<OutputGroup> = utxos
.into_iter()
.map(|u| OutputGroup::new(u, fee_rate))
.collect();

let drain_script = ScriptBuf::default();

let result = BranchAndBoundCoinSelection::default().single_random_draw(
vec![],
utxos,
0,
target_amount as i64,
&drain_script,
fee_rate,
);

assert!(result.selected_amount() > target_amount);
assert_eq!(result.fee_amount, (result.selected.len() * 68) as u64);
}

#[test]
fn test_bnb_exclude_negative_effective_value() {
let utxos = get_test_utxos();
Expand Down
Loading

0 comments on commit 700eace

Please sign in to comment.