//! # Ok::<(), anyhow::Error>(())
//! ```
-use crate::chain::collections::HashSet;
use crate::wallet::utils::IsDust;
use crate::Utxo;
use crate::WeightedUtxo;
use alloc::vec::Vec;
use bitcoin::consensus::encode::serialize;
-use bitcoin::OutPoint;
use bitcoin::TxIn;
use bitcoin::{Script, Weight};
}
}
-/// Remove duplicate UTXOs.
-///
-/// If a UTXO appears in both `required` and `optional`, the appearance in `required` is kept.
-pub(crate) fn filter_duplicates<I>(required: I, optional: I) -> (I, I)
-where
- I: IntoIterator<Item = WeightedUtxo> + FromIterator<WeightedUtxo>,
-{
- let mut visited = HashSet::<OutPoint>::new();
- let required = required
- .into_iter()
- .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
- .collect::<I>();
- let optional = optional
- .into_iter()
- .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
- .collect::<I>();
- (required, optional)
-}
-
#[cfg(test)]
mod test {
use assert_matches::assert_matches;
use bitcoin::hashes::Hash;
- use chain::{BlockId, ChainPosition, ConfirmationBlockTime};
+ use bitcoin::OutPoint;
+ use chain::{ChainPosition, ConfirmationBlockTime};
use core::str::FromStr;
use rand::rngs::StdRng;
use super::*;
use crate::types::*;
- use crate::wallet::coin_selection::filter_duplicates;
use rand::prelude::SliceRandom;
use rand::{thread_rng, Rng, RngCore, SeedableRng};
assert_eq!(res.selected_amount(), Amount::from_sat(200_000));
}
- #[test]
- fn test_filter_duplicates() {
- fn utxo(txid: &str, value: u64) -> WeightedUtxo {
- WeightedUtxo {
- satisfaction_weight: Weight::ZERO,
- utxo: Utxo::Local(LocalOutput {
- outpoint: OutPoint::new(bitcoin::hashes::Hash::hash(txid.as_bytes()), 0),
- txout: TxOut {
- value: Amount::from_sat(value),
- script_pubkey: ScriptBuf::new(),
- },
- keychain: KeychainKind::External,
- is_spent: false,
- derivation_index: 0,
- chain_position: ChainPosition::Confirmed {
- anchor: ConfirmationBlockTime {
- block_id: BlockId {
- height: 12345,
- hash: BlockHash::all_zeros(),
- },
- confirmation_time: 12345,
- },
- transitively: None,
- },
- }),
- }
- }
-
- fn to_utxo_vec(utxos: &[(&str, u64)]) -> Vec<WeightedUtxo> {
- let mut v = utxos
- .iter()
- .map(|&(txid, value)| utxo(txid, value))
- .collect::<Vec<_>>();
- v.sort_by_key(|u| u.utxo.outpoint());
- v
- }
-
- struct TestCase<'a> {
- name: &'a str,
- required: &'a [(&'a str, u64)],
- optional: &'a [(&'a str, u64)],
- exp_required: &'a [(&'a str, u64)],
- exp_optional: &'a [(&'a str, u64)],
- }
-
- let test_cases = [
- TestCase {
- name: "no_duplicates",
- required: &[("A", 1000), ("B", 2100)],
- optional: &[("C", 1000)],
- exp_required: &[("A", 1000), ("B", 2100)],
- exp_optional: &[("C", 1000)],
- },
- TestCase {
- name: "duplicate_required_utxos",
- required: &[("A", 3000), ("B", 1200), ("C", 1234), ("A", 3000)],
- optional: &[("D", 2100)],
- exp_required: &[("A", 3000), ("B", 1200), ("C", 1234)],
- exp_optional: &[("D", 2100)],
- },
- TestCase {
- name: "duplicate_optional_utxos",
- required: &[("A", 3000), ("B", 1200)],
- optional: &[("C", 5000), ("D", 1300), ("C", 5000)],
- exp_required: &[("A", 3000), ("B", 1200)],
- exp_optional: &[("C", 5000), ("D", 1300)],
- },
- TestCase {
- name: "duplicate_across_required_and_optional_utxos",
- required: &[("A", 3000), ("B", 1200), ("C", 2100)],
- optional: &[("A", 3000), ("D", 1200), ("E", 5000)],
- exp_required: &[("A", 3000), ("B", 1200), ("C", 2100)],
- exp_optional: &[("D", 1200), ("E", 5000)],
- },
- ];
-
- for (i, t) in test_cases.into_iter().enumerate() {
- let (required, optional) =
- filter_duplicates(to_utxo_vec(t.required), to_utxo_vec(t.optional));
- assert_eq!(
- required,
- to_utxo_vec(t.exp_required),
- "[{}:{}] unexpected `required` result",
- i,
- t.name
- );
- assert_eq!(
- optional,
- to_utxo_vec(t.exp_optional),
- "[{}:{}] unexpected `optional` result",
- i,
- t.name
- );
- }
- }
-
#[test]
fn test_deterministic_coin_selection_picks_same_utxos() {
enum CoinSelectionAlgo {