Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/anti_fee_sniping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn main() -> anyhow::Result<()> {
},
)?;

let selection_inputs = selection.inputs.clone();
let selection_inputs = selection.inputs().to_vec();

let psbt = selection.create_psbt(PsbtParams {
anti_fee_sniping: Some(tip_height),
Expand Down
2 changes: 1 addition & 1 deletion examples/synopsis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ fn main() -> anyhow::Result<()> {
println!(
"selected inputs: {:?}",
selection
.inputs
.inputs()
.iter()
.map(|input| input.prev_outpoint())
.collect::<Vec<_>>()
Expand Down
22 changes: 4 additions & 18 deletions src/utils.rs → src/afs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::Input;
use crate::{
no_std_rand::{random_probability, random_range},
Input,
};
use alloc::vec::Vec;
use miniscript::bitcoin::{
absolute::{self, LockTime},
Expand Down Expand Up @@ -157,20 +160,3 @@ pub(crate) fn apply_anti_fee_sniping(

Ok(())
}

/// Returns true with probability 1/n.
fn random_probability(rng: &mut impl RngCore, n: u32) -> bool {
random_range(rng, n) == 0
}

/// Returns a random value in the range [0, n) using unbiased rejection sampling.
fn random_range(rng: &mut impl RngCore, n: u32) -> u32 {
let threshold = n.wrapping_neg() % n;

loop {
let value = rng.next_u32();
if value >= threshold {
return value % n;
}
}
}
41 changes: 16 additions & 25 deletions src/finalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use miniscript::{bitcoin, plan::Plan, psbt::PsbtInputSatisfier};
/// # use bdk_tx::PsbtParams;
/// # let secp = bitcoin::secp256k1::Secp256k1::new();
/// # let keymap = std::collections::BTreeMap::new();
/// # let selection = bdk_tx::Selection { inputs: vec![], outputs: vec![] };
/// # let selection: bdk_tx::Selection = unimplemented!();
/// // Create PSBT from a selection of inputs and outputs.
/// let mut psbt = selection.create_psbt(PsbtParams::default())?;
///
Expand Down Expand Up @@ -217,10 +217,7 @@ mod tests {
fn test_finalize_single_input() -> anyhow::Result<()> {
let (input, keymap) = create_input_from_descriptor_at(TR_XPRV, 0)?;
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
let selection = Selection {
inputs: vec![input],
outputs: vec![output],
};
let selection = Selection::new(vec![input], vec![output]);

let mut psbt = selection.create_psbt(PsbtParams::default())?;
let finalizer = selection.into_finalizer();
Expand All @@ -240,10 +237,7 @@ mod tests {
fn test_finalize_sets_final_script_sig() -> anyhow::Result<()> {
let (input, keymap) = create_input_from_descriptor_at(PKH_XPRV, 0)?;
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
let selection = Selection {
inputs: vec![input],
outputs: vec![output],
};
let selection = Selection::new(vec![input], vec![output]);

let mut psbt = selection.create_psbt(PsbtParams::default())?;
let finalizer = selection.into_finalizer();
Expand All @@ -266,13 +260,13 @@ mod tests {
let taproot_output_descriptor = derive_descriptor_at(TR_XPRV, 10)?;
let wpkh_output_descriptor = derive_descriptor_at(WPKH_XPRV, 11)?;

let selection = Selection {
inputs: vec![input_0, input_1, input_2],
outputs: vec![
let selection = Selection::new(
vec![input_0, input_1, input_2],
vec![
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
],
};
);

let mut psbt = selection.create_psbt(PsbtParams::default())?;
let finalizer = selection.into_finalizer();
Expand Down Expand Up @@ -321,13 +315,13 @@ mod tests {
input_0.plan().cloned().expect("plan must exist"),
)]);

let selection = Selection {
inputs: vec![input_0, input_1],
outputs: vec![
let selection = Selection::new(
vec![input_0, input_1],
vec![
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
],
};
);

let mut psbt = selection.create_psbt(PsbtParams::default())?;

Expand Down Expand Up @@ -361,13 +355,13 @@ mod tests {
let (input, _) = create_input_from_descriptor_at(TR_XPRV, 0)?;
let taproot_output_descriptor = derive_descriptor_at(TR_XPRV, 10)?;
let wpkh_output_descriptor = derive_descriptor_at(WPKH_XPRV, 11)?;
let selection = Selection {
inputs: vec![input],
outputs: vec![
let selection = Selection::new(
vec![input],
vec![
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
],
};
);

let mut psbt = selection.create_psbt(PsbtParams::default())?;
let finalizer = selection.into_finalizer();
Expand Down Expand Up @@ -395,10 +389,7 @@ mod tests {
fn test_already_finalized_input() -> anyhow::Result<()> {
let (input, keymap) = create_input_from_descriptor_at(TR_XPRV, 0)?;
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
let selection = Selection {
inputs: vec![input],
outputs: vec![output],
};
let selection = Selection::new(vec![input], vec![output]);

let mut psbt = selection.create_psbt(PsbtParams::default())?;
let finalizer = selection.into_finalizer();
Expand Down
33 changes: 33 additions & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,39 @@ impl Input {
}
}

/// Mutable handle to an [`Input`] held inside a [`Selection`].
///
/// Returned by [`Selection::input_mut`] and [`Selection::inputs_mut`]. This wrapper restricts
/// mutation to operations that preserve [`Selection`]'s coin-selection invariants.
///
/// Read-only access to the underlying [`Input`] is available via [`Deref`].
///
/// [`Selection`]: crate::Selection
/// [`Selection::input_mut`]: crate::Selection::input_mut
/// [`Selection::inputs_mut`]: crate::Selection::inputs_mut
/// [`Deref`]: core::ops::Deref
#[derive(Debug)]
pub struct InputMut<'a>(&'a mut Input);
Comment thread
evanlinjin marked this conversation as resolved.

impl<'a> InputMut<'a> {
pub(crate) fn new(input: &'a mut Input) -> Self {
Self(input)
}

/// See [`Input::set_sequence`].
pub fn set_sequence(&mut self, sequence: Sequence) -> Result<(), SetSequenceError> {
self.0.set_sequence(sequence)
}
}

impl core::ops::Deref for InputMut<'_> {
type Target = Input;

fn deref(&self) -> &Input {
self.0
}
}

/// Input group. Cannot be empty.
#[derive(Debug, Clone)]
pub struct InputGroup(Vec<Input>);
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,32 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

mod afs;
mod canonical_unspents;
mod finalizer;
mod input;
mod input_candidates;
mod no_std_rand;
mod output;
mod rbf;
mod selection;
mod selector;
mod signer;
mod utils;

pub use afs::*;
pub use canonical_unspents::*;
pub use finalizer::*;
pub use input::*;
pub use input_candidates::*;
pub use miniscript;
pub use miniscript::bitcoin;
use miniscript::{DefiniteDescriptorKey, Descriptor};
use no_std_rand::*;
pub use output::*;
pub use rbf::*;
pub use selection::*;
pub use selector::*;
pub use signer::*;
pub use utils::*;

#[cfg(feature = "std")]
pub(crate) mod collections {
Expand Down
44 changes: 44 additions & 0 deletions src/no_std_rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use rand_core::RngCore;

/// Returns true with probability 1/n.
pub(crate) fn random_probability(rng: &mut impl RngCore, n: u32) -> bool {
random_range(rng, n) == 0
}

/// Returns a random value in the range [0, n) using unbiased rejection sampling.
pub(crate) fn random_range(rng: &mut impl RngCore, n: u32) -> u32 {
let threshold = n.wrapping_neg() % n;

loop {
let value = rng.next_u32();
if value >= threshold {
return value % n;
}
}
}

/// Fisher-Yates in-place shuffle using unbiased rejection sampling.
pub(crate) fn fisher_yates_shuffle<T>(slice: &mut [T], rng: &mut impl RngCore) {
for i in (1..slice.len()).rev() {
// Unbiased index in [0, i+1) via rejection sampling.
let j = random_range(rng, (i + 1) as u32) as usize;
slice.swap(i, j);
}
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use rand_core::OsRng;

#[test]
fn test_fisher_yates_shuffle_preserves_multiset() {
let original: Vec<u32> = (0..32).collect();
let mut shuffled = original.clone();
fisher_yates_shuffle(&mut shuffled, &mut OsRng);
shuffled.sort();
assert_eq!(shuffled, original);
}
}
Loading