From 388b7cc6ca13b0c6e8a20a59595b5af46d72a883 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:59:38 -0300 Subject: [PATCH] test(wallet): check there are no duplicates across required and optional utxos This test replaces the one used to test `coin_selection::filter_duplicates` introduced in 5299db34cb9117ad1b66a6afcb51f6ca7e1f0d95. As the code changed and there is not a single point to verificate the following properties: - there are no duplicates in required utxos - there are no duplicates in optional utxos - there are no duplicates across optional and required utxos anymore, test have been prefixed with `not_duplicated_utxos*` to allow its joint execution by using the following command: cargo test -- not_duplicated_utxos --- crates/wallet/src/wallet/mod.rs | 134 +++++++++++++++++++++++++ crates/wallet/src/wallet/tx_builder.rs | 13 +++ 2 files changed, 147 insertions(+) diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 8a949eb90..2bcce11a3 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -2557,3 +2557,137 @@ macro_rules! doctest_wallet { wallet }} } + +#[cfg(test)] +mod test { + use super::*; + use crate::test_utils::get_test_tr_single_sig_xprv_and_change_desc; + use crate::test_utils::insert_anchor; + use crate::test_utils::insert_tx; + + #[test] + fn not_duplicated_utxos_across_optional_and_required() { + let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); + + // create new wallet + let mut wallet = Wallet::create(external_desc, internal_desc) + .network(Network::Testnet) + .create_wallet_no_persist() + .unwrap(); + + let two_output_tx = Transaction { + input: vec![], + output: vec![ + TxOut { + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + value: Amount::from_sat(25_000), + }, + TxOut { + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + value: Amount::from_sat(75_000), + }, + ], + version: transaction::Version::non_standard(0), + lock_time: absolute::LockTime::ZERO, + }; + + let txid = two_output_tx.compute_txid(); + insert_tx(&mut wallet, two_output_tx); + + let expected_anchor = ConfirmationBlockTime { + block_id: wallet.latest_checkpoint().block_id(), + confirmation_time: 200, + }; + + insert_anchor(&mut wallet, txid, expected_anchor); + + let mut params = TxParams::default(); + wallet + .get_utxo(OutPoint { txid, vout: 0 }) + .map(|outpoint| { + params.utxos.insert(outpoint); + }) + .unwrap(); + // enforce selection of first output in transaction + let received = wallet.filter_utxos(¶ms, wallet.latest_checkpoint().block_id().height); + // notice expected doesn't include the first output from two_output_tx as it should be + // filtered out + let expected = vec![wallet + .get_utxo(OutPoint { txid, vout: 1 }) + .map(|utxo| WeightedUtxo { + satisfaction_weight: wallet + .public_descriptor(utxo.keychain) + .max_weight_to_satisfy() + .unwrap(), + utxo: Utxo::Local(utxo), + }) + .unwrap()]; + + assert_eq!(expected, received); + } + + #[test] + fn not_duplicated_utxos_in_optional_list() { + let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); + + // create new wallet + let mut wallet = Wallet::create(external_desc, internal_desc) + .network(Network::Testnet) + .create_wallet_no_persist() + .unwrap(); + + let two_output_tx = Transaction { + input: vec![], + output: vec![ + TxOut { + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + value: Amount::from_sat(25_000), + }, + TxOut { + script_pubkey: wallet + .next_unused_address(KeychainKind::External) + .script_pubkey(), + value: Amount::from_sat(75_000), + }, + ], + version: transaction::Version::non_standard(0), + lock_time: absolute::LockTime::ZERO, + }; + + let txid = two_output_tx.compute_txid(); + + for _ in 0..3 { + insert_tx(&mut wallet, two_output_tx.clone()); + } + + let expected_anchor = ConfirmationBlockTime { + block_id: wallet.latest_checkpoint().block_id(), + confirmation_time: 200, + }; + + insert_anchor(&mut wallet, txid, expected_anchor); + + // enforce selection of first output in transaction + let received = wallet + .indexed_graph + .graph() + .filter_chain_unspents( + &wallet.chain, + wallet.chain.tip().block_id(), + wallet.indexed_graph.index.outpoints().iter().cloned(), + ) + .map(|(_, full_txout)| full_txout.outpoint) + .collect::>(); + // notice expected doesn't include the first output from two_output_tx as it should be + // filtered out + let expected = vec![OutPoint { txid, vout: 0 }, OutPoint { txid, vout: 1 }]; + + assert_eq!(expected, received); + } +} diff --git a/crates/wallet/src/wallet/tx_builder.rs b/crates/wallet/src/wallet/tx_builder.rs index a91590207..f28400f89 100644 --- a/crates/wallet/src/wallet/tx_builder.rs +++ b/crates/wallet/src/wallet/tx_builder.rs @@ -1087,4 +1087,17 @@ mod test { builder.fee_rate(FeeRate::from_sat_per_kwu(feerate + 250)); let _ = builder.finish().unwrap(); } + + #[test] + fn not_duplicated_utxos_in_required_list() { + let mut params = TxParams::default(); + let test_utxos = get_test_utxos(); + for _ in 0..3 { + params.utxos.insert(test_utxos[0].clone()); + } + assert_eq!( + vec![test_utxos[0].clone()], + params.utxos.into_iter().collect::>() + ); + } }