From 9c3ce396d39d3d986c8345f158217556358cd442 Mon Sep 17 00:00:00 2001 From: Vihiga Tyonum Date: Fri, 10 Apr 2026 21:50:43 +0100 Subject: [PATCH 1/4] refactor(test): move tx_template to testenv - move tx_template from chain to testenv - update imports from chain/common/ to bdk_testenv /tx_template/ --- crates/chain/tests/common/mod.rs | 5 -- crates/chain/tests/test_indexed_tx_graph.rs | 3 - crates/chain/tests/test_tx_graph.rs | 4 +- crates/chain/tests/test_tx_graph_conflicts.rs | 5 +- crates/testenv/Cargo.toml | 3 +- crates/testenv/src/lib.rs | 2 + .../common => testenv/src}/tx_template.rs | 75 ++++++++++++++----- 7 files changed, 61 insertions(+), 36 deletions(-) delete mode 100644 crates/chain/tests/common/mod.rs rename crates/{chain/tests/common => testenv/src}/tx_template.rs (69%) diff --git a/crates/chain/tests/common/mod.rs b/crates/chain/tests/common/mod.rs deleted file mode 100644 index cb3ee66f37..0000000000 --- a/crates/chain/tests/common/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![cfg(feature = "miniscript")] - -mod tx_template; -#[allow(unused_imports)] -pub use tx_template::*; diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 18d1ff1bf4..a6b84cf1c6 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -1,8 +1,5 @@ #![cfg(feature = "miniscript")] -#[macro_use] -mod common; - use std::{collections::BTreeSet, str::FromStr, sync::Arc}; use bdk_chain::{ diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index b2a3596085..720ff8fb76 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -1,7 +1,5 @@ #![cfg(feature = "miniscript")] -#[macro_use] -mod common; use bdk_chain::{collections::*, BlockId, CanonicalizationParams, ConfirmationBlockTime}; use bdk_chain::{ local_chain::LocalChain, @@ -10,13 +8,13 @@ use bdk_chain::{ Anchor, ChainOracle, ChainPosition, Merge, }; use bdk_testenv::{block_id, hash, utils::new_tx}; +use bdk_testenv::{init_graph, TxInTemplate, TxOutTemplate, TxTemplate}; use bitcoin::hex::FromHex; use bitcoin::Witness; use bitcoin::{ absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut, Txid, }; -use common::*; use core::iter; use rand::RngCore; use std::sync::Arc; diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 38f21365c3..5d26f86d85 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -1,12 +1,9 @@ #![cfg(feature = "miniscript")] -#[macro_use] -mod common; - use bdk_chain::{local_chain::LocalChain, Balance, BlockId}; use bdk_testenv::{block_id, hash, local_chain}; +use bdk_testenv::{init_graph, TxInTemplate, TxOutTemplate, TxTemplate}; use bitcoin::{Amount, BlockHash, OutPoint}; -use common::*; use std::collections::{BTreeSet, HashSet}; #[allow(dead_code)] diff --git a/crates/testenv/Cargo.toml b/crates/testenv/Cargo.toml index d24ae7e5fd..f0f9e623fa 100644 --- a/crates/testenv/Cargo.toml +++ b/crates/testenv/Cargo.toml @@ -16,9 +16,10 @@ readme = "README.md" workspace = true [dependencies] -bdk_chain = { path = "../chain", version = "0.23.1", default-features = false } +bdk_chain = { path = "../chain", version = "0.23.1", default-features = false, features = ["miniscript"]} electrsd = { version = "0.36.1", features = [ "legacy" ], default-features = false } bitcoin = { version = "0.32.0", default-features = false } +rand = { version = "0.8.0", default-features = false, features = ["std", "small_rng", "std_rng"] } [dev-dependencies] bdk_testenv = { path = "." } diff --git a/crates/testenv/src/lib.rs b/crates/testenv/src/lib.rs index eec99efa81..087fc8aef7 100644 --- a/crates/testenv/src/lib.rs +++ b/crates/testenv/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] +pub mod tx_template; pub mod utils; +pub use tx_template::*; use anyhow::Context; use bdk_chain::bitcoin::{ diff --git a/crates/chain/tests/common/tx_template.rs b/crates/testenv/src/tx_template.rs similarity index 69% rename from crates/chain/tests/common/tx_template.rs rename to crates/testenv/src/tx_template.rs index 29f36169ad..dac6712499 100644 --- a/crates/chain/tests/common/tx_template.rs +++ b/crates/testenv/src/tx_template.rs @@ -1,57 +1,79 @@ -#![cfg(feature = "miniscript")] +//! Transaction templates for constructing complex transaction histories for testing purposes. -use bdk_testenv::utils::DESCRIPTORS; -use rand::distributions::{Alphanumeric, DistString}; -use std::collections::HashMap; - -use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor, CanonicalizationParams}; +use crate::utils::DESCRIPTORS; +use bdk_chain::{ + miniscript::Descriptor, spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor, + CanonicalizationParams, +}; use bitcoin::{ locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, }; -use miniscript::Descriptor; +use rand::distributions::{Alphanumeric, DistString}; +use std::collections::HashMap; -/// Template for creating a transaction in `TxGraph`. +/// Template for creating a transaction in a [`TxGraph`]. /// -/// The incentive for transaction templates is to create a transaction history in a simple manner to -/// avoid having to explicitly hash previous transactions to form previous outpoints of later -/// transactions. +/// This is the main building block for constructing complex transaction histories +/// for tests. It allows you to refer to previous transactions by name instead of +/// manually managing txids and outpoints. #[derive(Clone, Copy, Default)] pub struct TxTemplate<'a, A> { - /// Uniquely identifies the transaction, before it can have a txid. + /// A unique name used to refer to this transaction in other templates. pub tx_name: &'a str, + + /// The inputs of this transaction. pub inputs: &'a [TxInTemplate<'a>], + + /// The outputs of this transaction. pub outputs: &'a [TxOutTemplate], + + /// Anchors (confirmations) for this transaction. pub anchors: &'a [A], + + /// Unix timestamp when this transaction was last seen in the mempool. pub last_seen: Option, + + /// If `true`, this transaction will be treated as canonical regardless of + /// conflict resolution rules (used for testing forced canonicalization). pub assume_canonical: bool, } +/// Describes how an input is created in a [`TxTemplate`]. #[allow(dead_code)] pub enum TxInTemplate<'a> { - /// This will give a random txid and vout. + /// A random (bogus) previous output. Useful when the actual prevout doesn't matter. Bogus, - /// This is used for coinbase transactions because they do not have previous outputs. + /// A coinbase input (no previous output). Coinbase, - /// Contains the `tx_name` and `vout` that we are spending. The rule is that we must only spend - /// from tx of a previous `TxTemplate`. + /// Spends from a previous transaction defined in the template list. + /// + /// The rule is that the referenced transaction (`prev_name`) must appear + /// earlier in the list passed to [`init_graph`]. PrevTx(&'a str, usize), } +/// Describes an output in a [`TxTemplate`]. pub struct TxOutTemplate { + /// Value in satoshis. pub value: u64, - pub spk_index: Option, // some = get spk from SpkTxOutIndex, none = random spk + /// If `Some(index)`, the output will use the script pubkey at that index + /// from the test descriptor set. If `None`, a random (empty) script is used. + pub spk_index: Option, } -#[allow(unused)] impl TxOutTemplate { pub fn new(value: u64, spk_index: Option) -> Self { TxOutTemplate { value, spk_index } } } +/// The result of calling [`init_graph`]. +/// +/// Contains the built [`TxGraph`], the associated indexer, and a mapping from +/// template names to their final txids. #[allow(dead_code)] pub struct TxTemplateEnv<'a, A> { pub tx_graph: TxGraph, @@ -60,14 +82,21 @@ pub struct TxTemplateEnv<'a, A> { pub canonicalization_params: CanonicalizationParams, } -#[allow(dead_code)] +/// Builds a [`TxGraph`] (and associated indexer) from a list of [`TxTemplate`]s. +/// +/// This is the main entry point for using transaction templates in tests. +/// It handles txid generation, outpoint wiring, anchor insertion, and last-seen +/// timestamps automatically. pub fn init_graph<'a, A: Anchor + Clone + 'a>( tx_templates: impl IntoIterator>, ) -> TxTemplateEnv<'a, A> { let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[2]).unwrap(); + let mut tx_graph = TxGraph::::default(); let mut indexer = SpkTxOutIndex::default(); + + // Pre-populate the indexer with 10 script pubkeys from the test descriptor (0..10).for_each(|index| { indexer.insert_spk( index, @@ -77,9 +106,10 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>( .script_pubkey(), ); }); - let mut txid_to_name = HashMap::<&'a str, Txid>::new(); + let mut txid_to_name = HashMap::<&'a str, Txid>::new(); let mut canonicalization_params = CanonicalizationParams::default(); + for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() { let tx = Transaction { version: transaction::Version::non_standard(0), @@ -137,19 +167,24 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>( }; let txid = tx.compute_txid(); + if tx_tmp.assume_canonical { canonicalization_params.assume_canonical.push(txid); } + txid_to_name.insert(tx_tmp.tx_name, txid); indexer.scan(&tx); let _ = tx_graph.insert_tx(tx.clone()); + for anchor in tx_tmp.anchors.iter() { let _ = tx_graph.insert_anchor(txid, anchor.clone()); } + if let Some(last_seen) = tx_tmp.last_seen { let _ = tx_graph.insert_seen_at(txid, last_seen); } } + TxTemplateEnv { tx_graph, indexer, From fd9ec55e8f11a1d7a9cfe78a2d042f0003368d75 Mon Sep 17 00:00:00 2001 From: Vihiga Tyonum Date: Sat, 11 Apr 2026 04:46:18 +0100 Subject: [PATCH 2/4] refactor(test):mv util fns from benches to testenv --- crates/chain/benches/canonicalization.rs | 61 +++++-------------- crates/chain/benches/indexer.rs | 35 ++--------- crates/chain/tests/test_indexed_tx_graph.rs | 14 +---- .../chain/tests/test_keychain_txout_index.rs | 11 +--- crates/chain/tests/test_tx_graph.rs | 4 +- crates/testenv/src/utils.rs | 59 +++++++++++++++++- 6 files changed, 80 insertions(+), 104 deletions(-) diff --git a/crates/chain/benches/canonicalization.rs b/crates/chain/benches/canonicalization.rs index 074e38cc42..7fcec92c3e 100644 --- a/crates/chain/benches/canonicalization.rs +++ b/crates/chain/benches/canonicalization.rs @@ -3,10 +3,8 @@ use bdk_chain::{keychain_txout::KeychainTxOutIndex, local_chain::LocalChain, Ind use bdk_core::{BlockId, CheckPoint}; use bdk_core::{ConfirmationBlockTime, TxUpdate}; use bdk_testenv::hash; -use bitcoin::{ - absolute, constants, hashes::Hash, key::Secp256k1, transaction, Amount, BlockHash, Network, - OutPoint, ScriptBuf, Transaction, TxIn, TxOut, -}; +use bdk_testenv::utils::{genesis_block_id, new_standard_tx, spk_at_index, tip_block_id}; +use bitcoin::{key::Secp256k1, Amount, OutPoint, Transaction, TxIn, TxOut}; use criterion::{criterion_group, criterion_main, Criterion}; use miniscript::{Descriptor, DescriptorPublicKey}; use std::sync::Arc; @@ -14,43 +12,11 @@ use std::sync::Arc; type Keychain = (); type KeychainTxGraph = IndexedTxGraph>; -/// New tx guaranteed to have at least one output -fn new_tx(lt: u32) -> Transaction { - Transaction { - version: transaction::Version::TWO, - lock_time: absolute::LockTime::from_consensus(lt), - input: vec![], - output: vec![TxOut::NULL], - } -} - -fn spk_at_index(txout_index: &KeychainTxOutIndex, index: u32) -> ScriptBuf { - txout_index - .get_descriptor(()) - .unwrap() - .at_derivation_index(index) - .unwrap() - .script_pubkey() -} - -fn genesis_block_id() -> BlockId { - BlockId { - height: 0, - hash: constants::genesis_block(Network::Regtest).block_hash(), - } -} - -fn tip_block_id() -> BlockId { - BlockId { - height: 100, - hash: BlockHash::all_zeros(), - } -} - /// Add ancestor tx confirmed at `block_id` with `locktime` (used for uniqueness). /// The transaction always pays 1 BTC to SPK 0. fn add_ancestor_tx(graph: &mut KeychainTxGraph, block_id: BlockId, locktime: u32) -> OutPoint { - let spk_0 = spk_at_index(&graph.index, 0); + let descriptor = graph.index.get_descriptor(()).unwrap(); + let spk_0 = spk_at_index(descriptor, 0); let tx = Transaction { input: vec![TxIn { previous_output: OutPoint::new(hash!("bogus"), locktime), @@ -60,7 +26,7 @@ fn add_ancestor_tx(graph: &mut KeychainTxGraph, block_id: BlockId, locktime: u32 value: Amount::ONE_BTC, script_pubkey: spk_0, }], - ..new_tx(locktime) + ..new_standard_tx(locktime) }; let txid = tx.compute_txid(); let _ = graph.insert_tx(tx); @@ -77,7 +43,7 @@ fn add_ancestor_tx(graph: &mut KeychainTxGraph, block_id: BlockId, locktime: u32 fn setup(f: F) -> (KeychainTxGraph, LocalChain) { const DESC: &str = "tr([ab28dc00/86h/1h/0h]tpubDCdDtzAMZZrkwKBxwNcGCqe4FRydeD9rfMisoi7qLdraG79YohRfPW4YgdKQhpgASdvh612xXNY5xYzoqnyCgPbkpK4LSVcH5Xv4cK7johH/0/*)"; let cp = CheckPoint::from_blocks( - [genesis_block_id(), tip_block_id()] + [genesis_block_id(), tip_block_id(100)] .into_iter() .map(|block_id| (block_id.height, block_id.hash)), ) @@ -127,9 +93,10 @@ fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp pub fn many_conflicting_unconfirmed(c: &mut Criterion) { const CONFLICTING_TX_COUNT: u32 = 2100; let (tx_graph, chain) = std::hint::black_box(setup(|tx_graph, _chain| { - let previous_output = add_ancestor_tx(tx_graph, tip_block_id(), 0); + let previous_output = add_ancestor_tx(tx_graph, tip_block_id(100), 0); + let descriptor = tx_graph.index.get_descriptor(()).unwrap(); // Create conflicting txs that spend from `previous_output`. - let spk_1 = spk_at_index(&tx_graph.index, 1); + let spk_1 = spk_at_index(descriptor, 1); for i in 1..=CONFLICTING_TX_COUNT { let tx = Transaction { input: vec![TxIn { @@ -140,7 +107,7 @@ pub fn many_conflicting_unconfirmed(c: &mut Criterion) { value: Amount::ONE_BTC - Amount::from_sat(i as u64 * 10), script_pubkey: spk_1.clone(), }], - ..new_tx(i) + ..new_standard_tx(i) }; let mut update = TxUpdate::default(); update.seen_ats = [(tx.compute_txid(), i as u64)].into(); @@ -165,7 +132,7 @@ pub fn many_conflicting_unconfirmed(c: &mut Criterion) { pub fn many_chained_unconfirmed(c: &mut Criterion) { const TX_CHAIN_COUNT: u32 = 2100; let (tx_graph, chain) = std::hint::black_box(setup(|tx_graph, _chain| { - let mut previous_output = add_ancestor_tx(tx_graph, tip_block_id(), 0); + let mut previous_output = add_ancestor_tx(tx_graph, tip_block_id(100), 0); // Create a chain of unconfirmed txs where each subsequent tx spends the output of the // previous one. for i in 0..TX_CHAIN_COUNT { @@ -175,7 +142,7 @@ pub fn many_chained_unconfirmed(c: &mut Criterion) { previous_output, ..Default::default() }], - ..new_tx(i) + ..new_standard_tx(i) }; let txid = tx.compute_txid(); let mut update = TxUpdate::default(); @@ -204,7 +171,7 @@ pub fn nested_conflicts(c: &mut Criterion) { const CONFLICTS_PER_OUTPUT: usize = 3; const GRAPH_DEPTH: usize = 7; let (tx_graph, chain) = std::hint::black_box(setup(|tx_graph, _chain| { - let mut prev_ops = core::iter::once(add_ancestor_tx(tx_graph, tip_block_id(), 0)) + let mut prev_ops = core::iter::once(add_ancestor_tx(tx_graph, tip_block_id(100), 0)) .collect::>(); for depth in 1..GRAPH_DEPTH { for previous_output in core::mem::take(&mut prev_ops) { @@ -225,7 +192,7 @@ pub fn nested_conflicts(c: &mut Criterion) { value, script_pubkey, }], - ..new_tx(conflict_i as _) + ..new_standard_tx(conflict_i as _) }; let txid = tx.compute_txid(); prev_ops.push(OutPoint::new(txid, 0)); diff --git a/crates/chain/benches/indexer.rs b/crates/chain/benches/indexer.rs index 5907c76a00..d1175684f9 100644 --- a/crates/chain/benches/indexer.rs +++ b/crates/chain/benches/indexer.rs @@ -3,11 +3,9 @@ use bdk_chain::{ local_chain::LocalChain, CanonicalizationParams, IndexedTxGraph, }; -use bdk_core::{BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate}; -use bitcoin::{ - absolute, constants, hashes::Hash, key::Secp256k1, transaction, Amount, BlockHash, Network, - Transaction, TxIn, TxOut, -}; +use bdk_core::{CheckPoint, ConfirmationBlockTime, TxUpdate}; +use bdk_testenv::utils::{genesis_block_id, new_standard_tx, tip_block_id}; +use bitcoin::{key::Secp256k1, Amount, Transaction, TxIn, TxOut}; use criterion::{criterion_group, criterion_main, Criterion}; use miniscript::Descriptor; use std::sync::Arc; @@ -22,36 +20,13 @@ const TX_CT: u32 = 21; const USE_SPK_CACHE: bool = true; const AMOUNT: Amount = Amount::from_sat(1_000); -fn new_tx(lt: u32) -> Transaction { - Transaction { - version: transaction::Version::TWO, - lock_time: absolute::LockTime::from_consensus(lt), - input: vec![], - output: vec![TxOut::NULL], - } -} - -fn genesis_block_id() -> BlockId { - BlockId { - height: 0, - hash: constants::genesis_block(Network::Regtest).block_hash(), - } -} - -fn tip_block_id() -> BlockId { - BlockId { - height: 100, - hash: BlockHash::all_zeros(), - } -} - fn setup(f: F) -> (KeychainTxGraph, LocalChain) { let desc = Descriptor::parse_descriptor(&Secp256k1::new(), DESC) .unwrap() .0; let cp = CheckPoint::from_blocks( - [genesis_block_id(), tip_block_id()] + [genesis_block_id(), tip_block_id(100)] .into_iter() .map(|block_id| (block_id.height, block_id.hash)), ) @@ -101,7 +76,7 @@ pub fn reindex_tx_graph(c: &mut Criterion) { script_pubkey, value: AMOUNT, }], - ..new_tx(i) + ..new_standard_tx(i) }; let txid = tx.compute_txid(); let mut update = TxUpdate::default(); diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index a6b84cf1c6..0a62f821a5 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -14,7 +14,7 @@ use bdk_testenv::{ anyhow::{self}, block_id, corepc_node::{Input, Output}, - hash, + hash, spk, utils::{new_tx, DESCRIPTORS}, TestEnv, }; @@ -24,16 +24,6 @@ use bitcoin::{ }; use miniscript::Descriptor; -fn gen_spk() -> ScriptBuf { - use bitcoin::secp256k1::{Secp256k1, SecretKey}; - - let secp = Secp256k1::new(); - let (x_only_pk, _) = SecretKey::new(&mut rand::thread_rng()) - .public_key(&secp) - .x_only_public_key(); - ScriptBuf::new_p2tr(&secp, x_only_pk, None) -} - /// Conflicts of relevant transactions must also be considered relevant. /// /// This allows the receiving structures to determine the reason why a given transaction is not part @@ -65,7 +55,7 @@ fn relevant_conflicts() -> anyhow::Result<()> { .address()? .require_network(Network::Regtest)?; - let recv_spk = gen_spk(); + let recv_spk = spk!(); let recv_addr = Address::from_script(&recv_spk, &bitcoin::params::REGTEST)?; let mut graph = SpkTxGraph::default(); diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 263a0fa86f..125b472482 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -7,9 +7,9 @@ use bdk_chain::{ }; use bdk_testenv::{ hash, - utils::{new_tx, DESCRIPTORS}, + utils::{new_tx, spk_at_index, DESCRIPTORS}, }; -use bitcoin::{secp256k1::Secp256k1, Amount, OutPoint, ScriptBuf, Transaction, TxOut}; +use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxOut}; use miniscript::{Descriptor, DescriptorPublicKey}; #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] @@ -52,13 +52,6 @@ fn init_txout_index( txout_index } -fn spk_at_index(descriptor: &Descriptor, index: u32) -> ScriptBuf { - descriptor - .derived_descriptor(&Secp256k1::verification_only(), index) - .expect("must derive") - .script_pubkey() -} - // We create two empty changesets lhs and rhs, we then insert various descriptors with various // last_revealed, merge rhs to lhs, and check that the result is consistent with these rules: // - Existing index doesn't update if the new index in `other` is lower than `self`. diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 720ff8fb76..3a6ab006ab 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -1095,9 +1095,7 @@ fn test_chain_spends() { // Because this tx conflicts with an already confirmed transaction, chain position should // return none. - assert!(canonical_positions - .get(&tx_1_conflict.compute_txid()) - .is_none()); + assert!(!canonical_positions.contains_key(&tx_1_conflict.compute_txid())); } // Another conflicting tx that conflicts with tx_2. diff --git a/crates/testenv/src/utils.rs b/crates/testenv/src/utils.rs index 93ca1f217f..fb5e37f4c4 100644 --- a/crates/testenv/src/utils.rs +++ b/crates/testenv/src/utils.rs @@ -1,4 +1,12 @@ -use bdk_chain::bitcoin; +use bdk_chain::{ + bitcoin, + miniscript::{Descriptor, DescriptorPublicKey}, + BlockId, +}; +use bitcoin::{ + absolute::LockTime, constants, hashes::Hash, key::Secp256k1, transaction::Version, BlockHash, + Network, ScriptBuf, Transaction, TxOut, +}; #[allow(unused_macros)] #[macro_export] @@ -67,9 +75,22 @@ macro_rules! changeset { }}; } +/// Generate a dummy script pubkey. +#[allow(unused_macros)] +#[macro_export] +macro_rules! spk { + () => {{ + let secp = bitcoin::secp256k1::Secp256k1::new(); + let (x_only_pk, _) = bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng()) + .public_key(&secp) + .x_only_public_key(); + bitcoin::ScriptBuf::new_p2tr(&secp, x_only_pk, None) + }}; +} + #[allow(unused)] -pub fn new_tx(lt: u32) -> bitcoin::Transaction { - bitcoin::Transaction { +pub fn new_tx(lt: u32) -> Transaction { + Transaction { version: bitcoin::transaction::Version::non_standard(0x00), lock_time: bitcoin::absolute::LockTime::from_consensus(lt), input: vec![], @@ -77,6 +98,38 @@ pub fn new_tx(lt: u32) -> bitcoin::Transaction { } } +/// Initialize a standard transaction with a guaranteed output. +pub fn new_standard_tx(lt: u32) -> Transaction { + Transaction { + version: Version::TWO, + lock_time: LockTime::from_consensus(lt), + input: vec![], + output: vec![TxOut::NULL], + } +} + +pub fn genesis_block_id() -> BlockId { + BlockId { + height: 0, + hash: constants::genesis_block(Network::Regtest).block_hash(), + } +} + +pub fn tip_block_id(height: u32) -> BlockId { + BlockId { + height, + hash: BlockHash::all_zeros(), + } +} + +/// Derives a [`ScriptBuf`] (scriptPubkey) from the provided descriptor at a specific index. +pub fn spk_at_index(descriptor: &Descriptor, index: u32) -> ScriptBuf { + descriptor + .derived_descriptor(&Secp256k1::verification_only(), index) + .expect("must derive") + .script_pubkey() +} + #[allow(unused)] pub const DESCRIPTORS: [&str; 7] = [ "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)", From 9761e3982001e7850b40e5632ea73fb6f544cbf5 Mon Sep 17 00:00:00 2001 From: Vihiga Tyonum Date: Wed, 15 Apr 2026 16:56:30 +0100 Subject: [PATCH 3/4] refactor(testenv): update `tx_template` to use Cow -update TxTemplate struct to use Cow pointer - add utility methods to TxTemplate --- crates/testenv/src/tx_template.rs | 94 ++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/crates/testenv/src/tx_template.rs b/crates/testenv/src/tx_template.rs index dac6712499..c75e6cf9c0 100644 --- a/crates/testenv/src/tx_template.rs +++ b/crates/testenv/src/tx_template.rs @@ -10,26 +10,29 @@ use bitcoin::{ Sequence, Transaction, TxIn, TxOut, Txid, Witness, }; use rand::distributions::{Alphanumeric, DistString}; -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; /// Template for creating a transaction in a [`TxGraph`]. /// /// This is the main building block for constructing complex transaction histories /// for tests. It allows you to refer to previous transactions by name instead of /// manually managing txids and outpoints. -#[derive(Clone, Copy, Default)] -pub struct TxTemplate<'a, A> { +#[derive(Clone)] +pub struct TxTemplate +where + A: Clone + 'static, +{ /// A unique name used to refer to this transaction in other templates. - pub tx_name: &'a str, + pub tx_name: Cow<'static, str>, /// The inputs of this transaction. - pub inputs: &'a [TxInTemplate<'a>], + pub inputs: Cow<'static, [TxInTemplate]>, /// The outputs of this transaction. - pub outputs: &'a [TxOutTemplate], + pub outputs: Cow<'static, [TxOutTemplate]>, /// Anchors (confirmations) for this transaction. - pub anchors: &'a [A], + pub anchors: Cow<'static, [A]>, /// Unix timestamp when this transaction was last seen in the mempool. pub last_seen: Option, @@ -39,9 +42,69 @@ pub struct TxTemplate<'a, A> { pub assume_canonical: bool, } +impl Default for TxTemplate +where + A: Clone + 'static, +{ + fn default() -> Self { + Self { + tx_name: Cow::Borrowed(""), + inputs: Cow::Borrowed(&[]), + outputs: Cow::Borrowed(&[]), + anchors: Cow::Borrowed(&[]), + last_seen: None, + assume_canonical: false, + } + } +} + +impl TxTemplate +where + A: Clone + 'static, +{ + /// Create a new template with a name. + pub fn new(name: impl Into>) -> Self { + Self { + tx_name: name.into(), + ..Default::default() + } + } + + //// Set inputs. Accepts `&[TxInTemplate]`, `Vec`, or a static slice. + pub fn with_inputs(mut self, inputs: impl Into>) -> Self { + self.inputs = inputs.into(); + self + } + + /// Set outputs with `Vec` or `&'static [TxOutTemplate]`. + pub fn with_outputs(mut self, outputs: impl Into>) -> Self { + self.outputs = outputs.into(); + self + } + + /// Set anchors. Supports `Vec` or `&'static [A]`. + pub fn with_anchors(mut self, anchors: impl Into>) -> Self { + self.anchors = anchors.into(); + self + } + + /// Mark this transaction as canonical. + pub fn with_assume_canonical(mut self, assume: bool) -> Self { + self.assume_canonical = assume; + self + } + + /// Set the last-seen mempool timestamp. + pub fn with_last_seen(mut self, last_seen: Option) -> Self { + self.last_seen = last_seen; + self + } +} + /// Describes how an input is created in a [`TxTemplate`]. +#[derive(Clone, Debug)] #[allow(dead_code)] -pub enum TxInTemplate<'a> { +pub enum TxInTemplate { /// A random (bogus) previous output. Useful when the actual prevout doesn't matter. Bogus, @@ -52,10 +115,11 @@ pub enum TxInTemplate<'a> { /// /// The rule is that the referenced transaction (`prev_name`) must appear /// earlier in the list passed to [`init_graph`]. - PrevTx(&'a str, usize), + PrevTx(Cow<'static, str>, usize), } /// Describes an output in a [`TxTemplate`]. +#[derive(Clone, Debug)] pub struct TxOutTemplate { /// Value in satoshis. pub value: u64, @@ -75,10 +139,10 @@ impl TxOutTemplate { /// Contains the built [`TxGraph`], the associated indexer, and a mapping from /// template names to their final txids. #[allow(dead_code)] -pub struct TxTemplateEnv<'a, A> { +pub struct TxTemplateEnv { pub tx_graph: TxGraph, pub indexer: SpkTxOutIndex, - pub txid_to_name: HashMap<&'a str, Txid>, + pub txid_to_name: HashMap, Txid>, pub canonicalization_params: CanonicalizationParams, } @@ -87,9 +151,9 @@ pub struct TxTemplateEnv<'a, A> { /// This is the main entry point for using transaction templates in tests. /// It handles txid generation, outpoint wiring, anchor insertion, and last-seen /// timestamps automatically. -pub fn init_graph<'a, A: Anchor + Clone + 'a>( - tx_templates: impl IntoIterator>, -) -> TxTemplateEnv<'a, A> { +pub fn init_graph( + tx_templates: impl IntoIterator>, +) -> TxTemplateEnv { let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[2]).unwrap(); @@ -107,7 +171,7 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>( ); }); - let mut txid_to_name = HashMap::<&'a str, Txid>::new(); + let mut txid_to_name = HashMap::, Txid>::new(); let mut canonicalization_params = CanonicalizationParams::default(); for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() { From a5d9f415e4f46a879745da6f5fdab3795e1dedf9 Mon Sep 17 00:00:00 2001 From: Vihiga Tyonum Date: Wed, 15 Apr 2026 18:43:48 +0100 Subject: [PATCH 4/4] refactor(test): update tests to use builder API - update test_tx_graph and test_tx_graph_conflicts to use builder API --- crates/chain/tests/test_tx_graph.rs | 38 +- crates/chain/tests/test_tx_graph_conflicts.rs | 1007 ++++++----------- 2 files changed, 376 insertions(+), 669 deletions(-) diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 3a6ab006ab..fbb52f57fb 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -1139,7 +1139,7 @@ fn test_chain_spends() { ); // Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict` - assert!(canonical_positions.get(&tx_2.compute_txid()).is_none()); + assert!(!canonical_positions.contains_key(&tx_2.compute_txid())); } } @@ -1278,30 +1278,20 @@ fn call_map_anchors_with_non_deterministic_anchor() { } let template = [ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(1))], - anchors: &[block_id!(1, "A")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx2", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(20000, Some(2))], - anchors: &[block_id!(2, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "tx3", - inputs: &[TxInTemplate::PrevTx("tx2", 0)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - anchors: &[block_id!(3, "C"), block_id!(4, "D")], - ..Default::default() - }, + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(1))]) + .with_anchors(vec![block_id!(1, "A")]), + TxTemplate::new("tx2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_anchors(vec![block_id!(2, "B")]), + TxTemplate::new("tx3") + .with_inputs(vec![TxInTemplate::PrevTx("tx2".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_anchors(vec![block_id!(3, "C"), block_id!(4, "D")]), ]; - let graph = init_graph(&template).tx_graph; + let graph = init_graph(template).tx_graph; let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor { anchor_block: a, // A non-deterministic value diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 5d26f86d85..0d073918b2 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -7,17 +7,17 @@ use bitcoin::{Amount, BlockHash, OutPoint}; use std::collections::{BTreeSet, HashSet}; #[allow(dead_code)] -struct Scenario<'a> { +struct Scenario { /// Name of the test scenario - name: &'a str, + name: &'static str, /// Transaction templates - tx_templates: &'a [TxTemplate<'a, BlockId>], + tx_templates: Vec>, /// Names of txs that must exist in the output of `list_canonical_txs` - exp_chain_txs: HashSet<&'a str>, + exp_chain_txs: HashSet<&'static str>, /// Outpoints that must exist in the output of `filter_chain_txouts` - exp_chain_txouts: HashSet<(&'a str, u32)>, + exp_chain_txouts: HashSet<(&'static str, u32)>, /// Outpoints of UTXOs that must exist in the output of `filter_chain_unspents` - exp_unspents: HashSet<(&'a str, u32)>, + exp_unspents: HashSet<(&'static str, u32)>, /// Expected balances exp_balance: Balance, } @@ -43,37 +43,22 @@ fn test_tx_conflict_handling() { let scenarios = [ Scenario { name: "coinbase tx cannot be in mempool and be unconfirmed", - tx_templates: &[ - TxTemplate { - tx_name: "unconfirmed_coinbase", - inputs: &[TxInTemplate::Coinbase], - outputs: &[TxOutTemplate::new(5000, Some(0))], - ..Default::default() - }, - TxTemplate { - tx_name: "confirmed_genesis", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(1))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "unconfirmed_conflict", - inputs: &[ - TxInTemplate::PrevTx("confirmed_genesis", 0), - TxInTemplate::PrevTx("unconfirmed_coinbase", 0) - ], - outputs: &[TxOutTemplate::new(20000, Some(2))], - ..Default::default() - }, - TxTemplate { - tx_name: "confirmed_conflict", - inputs: &[TxInTemplate::PrevTx("confirmed_genesis", 0)], - outputs: &[TxOutTemplate::new(20000, Some(3))], - anchors: &[block_id!(4, "E")], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("unconfirmed_coinbase") + .with_inputs(vec![TxInTemplate::Coinbase]) + .with_outputs(vec![TxOutTemplate::new(5_000, Some(0))]), + TxTemplate::new("confirmed_genesis") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(1))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("unconfirmed_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("confirmed_genesis".into(), 0), + TxInTemplate::PrevTx("unconfirmed_coinbase".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]), + TxTemplate::new("confirmed_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("confirmed_genesis".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(3))]) + .with_anchors(vec![block_id!(4, "E")]), ], exp_chain_txs: HashSet::from(["confirmed_genesis", "confirmed_conflict"]), exp_chain_txouts: HashSet::from([("confirmed_genesis", 0), ("confirmed_conflict", 0)]), @@ -85,28 +70,18 @@ fn test_tx_conflict_handling() { }, Scenario { name: "2 unconfirmed txs with same last_seens conflict", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - outputs: &[TxOutTemplate::new(40000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(20000, Some(2))], - last_seen: Some(300), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_2", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - last_seen: Some(300), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_outputs(vec![TxOutTemplate::new(40_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(Some(300)), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30000, Some(3))]) + .with_last_seen(Some(300)), ], // the txgraph is going to pick tx_conflict_2 because of higher lexicographical txid exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), @@ -121,29 +96,19 @@ fn test_tx_conflict_handling() { }, Scenario { name: "2 unconfirmed txs with different last_seens conflict", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0)), TxOutTemplate::new(10000, Some(1))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(2))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_2", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::PrevTx("tx1", 1)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - last_seen: Some(300), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0)), TxOutTemplate::new(10_000, Some(1))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(Some(200)), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::PrevTx("tx1".into(), 1)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(Some(300)) ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx1", 1), ("tx_conflict_2", 0)]), @@ -157,36 +122,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "3 unconfirmed txs with different last_seens conflict", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_2", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(30000, Some(2))], - last_seen: Some(300), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_3", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(40000, Some(3))], - last_seen: Some(400), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30000, Some(2))]) + .with_last_seen(Some(300)), + TxTemplate::new("tx_conflict_3") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(40000, Some(3))]) + .with_last_seen(Some(400)), ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_3"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_3", 0)]), @@ -200,30 +152,20 @@ fn test_tx_conflict_handling() { }, Scenario { name: "unconfirmed tx conflicts with tx in orphaned block, orphaned higher last_seen", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_orphaned_conflict", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(30000, Some(2))], - anchors: &[block_id!(4, "Orphaned Block")], - last_seen: Some(300), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("tx_orphaned_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_anchors(vec![block_id!(4, "Orphaned Block")]) + .with_last_seen(Some(300)), ], exp_chain_txs: HashSet::from(["tx1", "tx_orphaned_conflict"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_orphaned_conflict", 0)]), @@ -237,30 +179,20 @@ fn test_tx_conflict_handling() { }, Scenario { name: "unconfirmed tx conflicts with tx in orphaned block, orphaned lower last_seen", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_orphaned_conflict", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(30000, Some(2))], - anchors: &[block_id!(4, "Orphaned Block")], - last_seen: Some(100), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("tx_orphaned_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_anchors(vec![block_id!(4, "Orphaned Block")]) + .with_last_seen(Some(100)), ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_1"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_1", 0)]), @@ -274,43 +206,27 @@ fn test_tx_conflict_handling() { }, Scenario { name: "multiple unconfirmed txs conflict with a confirmed tx", - tx_templates: &[ - TxTemplate { - tx_name: "tx1", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_1", - inputs: &[TxInTemplate::PrevTx("tx1", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_2", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(30000, Some(2))], - last_seen: Some(300), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_conflict_3", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(40000, Some(3))], - last_seen: Some(400), - ..Default::default() - }, - TxTemplate { - tx_name: "tx_confirmed_conflict", - inputs: &[TxInTemplate::PrevTx("tx1", 0)], - outputs: &[TxOutTemplate::new(50000, Some(4))], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("tx1") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx_conflict_1") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_last_seen(Some(300)), + TxTemplate::new("tx_conflict_3") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(40_000, Some(3))]) + .with_last_seen(Some(400)), + TxTemplate::new("tx_confirmed_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(50_000, Some(4))]) + .with_anchors(vec![block_id!(1, "B")]), ], exp_chain_txs: HashSet::from(["tx1", "tx_confirmed_conflict"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_confirmed_conflict", 0)]), @@ -324,35 +240,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, C spends B, all the transactions are unconfirmed, B' has higher last_seen than B", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - last_seen: Some(22), - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(23), - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(2))], - last_seen: Some(24), - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[TxInTemplate::PrevTx("B", 0)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - last_seen: Some(25), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_last_seen(Some(22)), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(23)), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(Some(24)), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(Some(25)) ], // A, B, C will appear in the list methods // This is because B' has a higher last seen than B, but C has a higher @@ -369,34 +273,21 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, C spends B, A and B' are in best chain", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(1))], - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(2))], - anchors: &[block_id!(4, "E")], - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[TxInTemplate::PrevTx("B", 0)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20000, Some(2))]) + .with_anchors(vec![block_id!(4, "E")]), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]), ], // B and C should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'"]), @@ -411,35 +302,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, C spends B', A and B' are in best chain", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(20000, Some(2))], - anchors: &[block_id!(4, "E")], - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[TxInTemplate::PrevTx("B'", 0)], - outputs: &[TxOutTemplate::new(30000, Some(3))], - last_seen: Some(1), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(2)), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_anchors(vec![block_id!(4, "E")]), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B'".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(Some(1)) ], // B should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'", "C"]), @@ -458,38 +337,22 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, C spends both B and B', A is in best chain", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(30000, Some(2))], - last_seen: Some(300), - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[ - TxInTemplate::PrevTx("B", 0), - TxInTemplate::PrevTx("B'", 0), - ], - outputs: &[TxOutTemplate::new(20000, Some(3))], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_last_seen(Some(300)), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0), TxInTemplate::PrevTx("B'".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(3))]), ], // C should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'"]), @@ -504,38 +367,22 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, B' is confirmed, C spends both B and B', A is in best chain", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(50000, Some(4))], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[ - TxInTemplate::PrevTx("B", 0), - TxInTemplate::PrevTx("B'", 0), - ], - outputs: &[TxOutTemplate::new(20000, Some(5))], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(50_000, Some(4))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0), TxInTemplate::PrevTx("B'".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(5))]), ], // C should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'"]), @@ -550,44 +397,25 @@ fn test_tx_conflict_handling() { }, Scenario { name: "B and B' spend A and conflict, B' is confirmed, C spends both B and B', D spends C, A is in best chain", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10000, Some(0))], - anchors: &[block_id!(1, "B")], - last_seen: None, - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(20000, Some(1))], - last_seen: Some(200), - ..Default::default() - }, - TxTemplate { - tx_name: "B'", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(50000, Some(4))], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "C", - inputs: &[ - TxInTemplate::PrevTx("B", 0), - TxInTemplate::PrevTx("B'", 0), - ], - outputs: &[TxOutTemplate::new(20000, Some(5))], - ..Default::default() - }, - TxTemplate { - tx_name: "D", - inputs: &[TxInTemplate::PrevTx("C", 0)], - outputs: &[TxOutTemplate::new(20000, Some(6))], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(200)), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(50_000, Some(4))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0), TxInTemplate::PrevTx("B'".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(5))]), + TxTemplate::new("D") + .with_inputs(vec![TxInTemplate::PrevTx("C".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(6))]), ], // D should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'"]), @@ -602,26 +430,17 @@ fn test_tx_conflict_handling() { }, Scenario { name: "transitively confirmed ancestors", - tx_templates: &[ - TxTemplate { - tx_name: "first", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(1000, Some(0))], - ..Default::default() - }, - TxTemplate { - tx_name: "second", - inputs: &[TxInTemplate::PrevTx("first", 0)], - outputs: &[TxOutTemplate::new(900, Some(0))], - ..Default::default() - }, - TxTemplate { - tx_name: "anchored", - inputs: &[TxInTemplate::PrevTx("second", 0)], - outputs: &[TxOutTemplate::new(800, Some(0))], - anchors: &[block_id!(3, "D")], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("first") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(1_000, Some(0))]), + TxTemplate::new("second") + .with_inputs(vec![TxInTemplate::PrevTx("first".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(900, Some(0))]), + TxTemplate::new("anchored") + .with_inputs(vec![TxInTemplate::PrevTx("second".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(800, Some(0))]) + .with_anchors(vec![block_id!(3, "D")]), ], exp_chain_txs: HashSet::from(["first", "second", "anchored"]), exp_chain_txouts: HashSet::from([("first", 0), ("second", 0), ("anchored", 0)]), @@ -635,35 +454,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "transitively anchored txs should have priority over last seen", - tx_templates: &[ - TxTemplate { - tx_name: "root", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10_000, Some(0))], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "last_seen_conflict", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(9900, Some(1))], - last_seen: Some(1000), - ..Default::default() - }, - TxTemplate { - tx_name: "transitively_anchored_conflict", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(9000, Some(1))], - last_seen: Some(100), - ..Default::default() - }, - TxTemplate { - tx_name: "anchored", - inputs: &[TxInTemplate::PrevTx("transitively_anchored_conflict", 0)], - outputs: &[TxOutTemplate::new(8000, Some(2))], - anchors: &[block_id!(4, "E")], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("root") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("last_seen_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(9_900, Some(1))]) + .with_last_seen(Some(1_000)), + TxTemplate::new("transitively_anchored_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(1))]) + .with_last_seen(Some(100)), + TxTemplate::new("anchored") + .with_inputs(vec![TxInTemplate::PrevTx("transitively_anchored_conflict".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(8_000, Some(2))]) + .with_anchors(vec![block_id!(4, "E")]), ], exp_chain_txs: HashSet::from(["root", "transitively_anchored_conflict", "anchored"]), exp_chain_txouts: HashSet::from([("root", 0), ("transitively_anchored_conflict", 0), ("anchored", 0)]), @@ -675,21 +482,15 @@ fn test_tx_conflict_handling() { }, Scenario { name: "tx anchored in orphaned block and not seen in mempool should be canon", - tx_templates: &[ - TxTemplate { - tx_name: "root", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10_000, None)], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "tx", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(9000, Some(0))], - anchors: &[block_id!(6, "not G")], - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("root") + .with_inputs(vec![TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, None)]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("tx") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(0))]) + .with_anchors(vec![block_id!(6, "not G")]), ], exp_chain_txs: HashSet::from(["root", "tx"]), exp_chain_txouts: HashSet::from([("tx", 0)]), @@ -698,28 +499,19 @@ fn test_tx_conflict_handling() { }, Scenario { name: "tx spends from 2 conflicting transactions where a conflict spends another", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10_000, None)], - last_seen: Some(1), - ..Default::default() - }, - TxTemplate { - tx_name: "S1", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(9_000, None)], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "S2", - inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::PrevTx("S1", 0)], - outputs: &[TxOutTemplate::new(17_000, None)], - last_seen: Some(3), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, None)]) + .with_last_seen(Some(1)), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, None)]) + .with_last_seen(Some(2)), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0), TxInTemplate::PrevTx("S1".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, None)]) + .with_last_seen(Some(3)), ], exp_chain_txs: HashSet::from(["A", "S1"]), exp_chain_txouts: HashSet::from([]), @@ -728,35 +520,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "tx spends from 2 conflicting transactions where the conflict is nested", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10_000, Some(0))], - last_seen: Some(1), - ..Default::default() - }, - TxTemplate { - tx_name: "S1", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(9_000, Some(0))], - last_seen: Some(3), - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("S1", 0)], - outputs: &[TxOutTemplate::new(8_000, Some(0))], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "S2", - inputs: &[TxInTemplate::PrevTx("B", 0), TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(17_000, Some(0))], - last_seen: Some(4), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_last_seen(Some(1)), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(0))]) + .with_last_seen(Some(3)), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("S1".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(8_000, Some(0))]) + .with_last_seen(Some(2)), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("B".into(), 0), TxInTemplate::PrevTx("A".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, Some(0))]) + .with_last_seen(Some(4)), ], exp_chain_txs: HashSet::from(["A", "S1", "B"]), exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("S1", 0)]), @@ -765,35 +545,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "tx spends from 2 conflicting transactions where the conflict is nested (different last_seens)", - tx_templates: &[ - TxTemplate { - tx_name: "A", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(10_000, Some(0))], - last_seen: Some(1), - ..Default::default() - }, - TxTemplate { - tx_name: "S1", - inputs: &[TxInTemplate::PrevTx("A", 0)], - outputs: &[TxOutTemplate::new(9_000, Some(0))], - last_seen: Some(4), - ..Default::default() - }, - TxTemplate { - tx_name: "B", - inputs: &[TxInTemplate::PrevTx("S1", 0)], - outputs: &[TxOutTemplate::new(8_000, Some(0))], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "S2", - inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::PrevTx("B", 0)], - outputs: &[TxOutTemplate::new(17_000, Some(0))], - last_seen: Some(3), - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("A") + .with_inputs(&[TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_last_seen(Some(1)), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(0))]) + .with_last_seen(Some(4)), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("S1".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(8_000, Some(0))]) + .with_last_seen(Some(2)), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("A".into(), 0), TxInTemplate::PrevTx("B".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, Some(0))]) + .with_last_seen(Some(3)), ], exp_chain_txs: HashSet::from(["A", "S1", "B"]), exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("S1", 0)]), @@ -802,41 +570,25 @@ fn test_tx_conflict_handling() { }, Scenario { name: "assume-canonical-tx displaces unconfirmed chain", - tx_templates: &[ - TxTemplate { - tx_name: "root", - inputs: &[TxInTemplate::Bogus], - outputs: &[ - TxOutTemplate::new(21_000, Some(0)), - TxOutTemplate::new(21_000, Some(1)), - ], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "unconfirmed", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(20_000, Some(1))], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "unconfirmed_descendant", - inputs: &[ - TxInTemplate::PrevTx("unconfirmed", 0), - TxInTemplate::PrevTx("root", 1), - ], - outputs: &[TxOutTemplate::new(28_000, Some(2))], - last_seen: Some(2), - ..Default::default() - }, - TxTemplate { - tx_name: "assume_canonical", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(19_000, Some(3))], - assume_canonical: true, - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("root") + .with_inputs(&[TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(21_000, Some(0)), + TxOutTemplate::new(21_000, Some(1))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("unconfirmed") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0) ]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(Some(2)), + TxTemplate::new("unconfirmed_descendant") + .with_inputs(vec![TxInTemplate::PrevTx("unconfirmed".into(), 0), + TxInTemplate::PrevTx("root".into(), 1) ]) + .with_outputs(vec![TxOutTemplate::new(28_000, Some(2))]) + .with_last_seen(Some(2)), + TxTemplate::new("assume_canonical") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(19_000, Some(3))]) + .with_assume_canonical(true), ], exp_chain_txs: HashSet::from(["root", "assume_canonical"]), exp_chain_txouts: HashSet::from([("root", 0), ("root", 1), ("assume_canonical", 0)]), @@ -850,41 +602,24 @@ fn test_tx_conflict_handling() { }, Scenario { name: "assume-canonical-tx displaces confirmed chain", - tx_templates: &[ - TxTemplate { - tx_name: "root", - inputs: &[TxInTemplate::Bogus], - outputs: &[ - TxOutTemplate::new(21_000, Some(0)), - TxOutTemplate::new(21_000, Some(1)), - ], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "confirmed", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(20_000, Some(1))], - anchors: &[block_id!(2, "C")], - ..Default::default() - }, - TxTemplate { - tx_name: "confirmed_descendant", - inputs: &[ - TxInTemplate::PrevTx("confirmed", 0), - TxInTemplate::PrevTx("root", 1), - ], - outputs: &[TxOutTemplate::new(28_000, Some(2))], - anchors: &[block_id!(3, "D")], - ..Default::default() - }, - TxTemplate { - tx_name: "assume_canonical", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(19_000, Some(3))], - assume_canonical: true, - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("root") + .with_inputs(&[TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(21_000, Some(0)), + TxOutTemplate::new(21_000, Some(1))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("confirmed") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_anchors(vec![block_id!(2, "C")]), + TxTemplate::new("confirmed_descendant") + .with_inputs(vec![TxInTemplate::PrevTx("confirmed".into(), 0), TxInTemplate::PrevTx("root".into(), 1)]) + .with_outputs(vec![TxOutTemplate::new(28_000, Some(2))]) + .with_anchors(vec![block_id!(3, "D")]), + TxTemplate::new("assume_canonical") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(19_000, Some(3))]) + .with_assume_canonical(true), ], exp_chain_txs: HashSet::from(["root", "assume_canonical"]), exp_chain_txouts: HashSet::from([("root", 0), ("root", 1), ("assume_canonical", 0)]), @@ -898,37 +633,23 @@ fn test_tx_conflict_handling() { }, Scenario { name: "assume-canonical txs respects order", - tx_templates: &[ - TxTemplate { - tx_name: "root", - inputs: &[TxInTemplate::Bogus], - outputs: &[ - TxOutTemplate::new(21_000, Some(0)), - ], - anchors: &[block_id!(1, "B")], - ..Default::default() - }, - TxTemplate { - tx_name: "assume_a", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(20_000, Some(1))], - assume_canonical: true, - ..Default::default() - }, - TxTemplate { - tx_name: "assume_b", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(19_000, Some(1))], - assume_canonical: true, - ..Default::default() - }, - TxTemplate { - tx_name: "assume_c", - inputs: &[TxInTemplate::PrevTx("root", 0)], - outputs: &[TxOutTemplate::new(18_000, Some(1))], - assume_canonical: true, - ..Default::default() - }, + tx_templates: vec![ + TxTemplate::new("root") + .with_inputs(&[TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(21_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B")]), + TxTemplate::new("assume_a") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_assume_canonical(true), + TxTemplate::new("assume_b") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(19_000, Some(1))]) + .with_assume_canonical(true), + TxTemplate::new("assume_c") + .with_inputs(vec![TxInTemplate::PrevTx("root".into(), 0)]) + .with_outputs(vec![TxOutTemplate::new(18_000, Some(1))]) + .with_assume_canonical(true), ], exp_chain_txs: HashSet::from(["root", "assume_c"]), exp_chain_txouts: HashSet::from([("root", 0), ("assume_c", 0)]), @@ -942,15 +663,11 @@ fn test_tx_conflict_handling() { }, Scenario { name: "coinbase tx must not become unconfirmed", - tx_templates: &[ - TxTemplate { - tx_name: "coinbase", - inputs: &[TxInTemplate::Coinbase], - outputs: &[TxOutTemplate::new(21_000, Some(0))], - // Stale block - anchors: &[block_id!(1, "B-prime")], - ..Default::default() - } + tx_templates: vec![ + TxTemplate::new("coinbase") + .with_inputs(&[TxInTemplate::Coinbase]) + .with_outputs(vec![TxOutTemplate::new(21_000, Some(0))]) + .with_anchors(vec![block_id!(1, "B-prime")]) ], exp_chain_txs: HashSet::from([]), exp_chain_txouts: HashSet::from([]), @@ -965,7 +682,7 @@ fn test_tx_conflict_handling() { ]; for scenario in scenarios { - let env = init_graph(scenario.tx_templates.iter()); + let env = init_graph(scenario.tx_templates); let txs = env .tx_graph @@ -976,7 +693,7 @@ fn test_tx_conflict_handling() { let exp_txs = scenario .exp_chain_txs .iter() - .map(|txid| *env.txid_to_name.get(txid).expect("txid must exist")) + .map(|&txid| *env.txid_to_name.get(txid).expect("txid must exist")) .collect::>(); assert_eq!( txs, exp_txs, @@ -993,9 +710,9 @@ fn test_tx_conflict_handling() { let exp_txouts = scenario .exp_chain_txouts .iter() - .map(|(txid, vout)| OutPoint { + .map(|&(txid, vout)| OutPoint { txid: *env.txid_to_name.get(txid).expect("txid must exist"), - vout: *vout, + vout, }) .collect::>(); assert_eq!( @@ -1013,9 +730,9 @@ fn test_tx_conflict_handling() { let exp_utxos = scenario .exp_unspents .iter() - .map(|(txid, vout)| OutPoint { + .map(|&(txid, vout)| OutPoint { txid: *env.txid_to_name.get(txid).expect("txid must exist"), - vout: *vout, + vout, }) .collect::>(); assert_eq!(