diff --git a/crates/chain/benches/canonicalization.rs b/crates/chain/benches/canonicalization.rs index 74e6c05fd9..acad87d681 100644 --- a/crates/chain/benches/canonicalization.rs +++ b/crates/chain/benches/canonicalization.rs @@ -2,10 +2,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; @@ -13,43 +11,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), @@ -59,7 +25,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); @@ -76,7 +42,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)), ) @@ -114,9 +80,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 { @@ -127,7 +94,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(); @@ -152,7 +119,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 { @@ -162,7 +129,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(); @@ -191,7 +158,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) { @@ -212,7 +179,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 97917ce35b..55dbbc4bed 100644 --- a/crates/chain/benches/indexer.rs +++ b/crates/chain/benches/indexer.rs @@ -3,11 +3,9 @@ use bdk_chain::{ local_chain::LocalChain, 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)), ) @@ -100,7 +75,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/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_canonical_view_task.rs b/crates/chain/tests/test_canonical_view_task.rs index 397819f4fc..731ebe699b 100644 --- a/crates/chain/tests/test_canonical_view_task.rs +++ b/crates/chain/tests/test_canonical_view_task.rs @@ -1,11 +1,10 @@ #![cfg(feature = "miniscript")] -mod common; - use bdk_chain::{CanonicalReason, ChainPosition}; -use bdk_testenv::{block_id, hash, local_chain}; +use bdk_testenv::{ + block_id, hash, init_graph, local_chain, TxInTemplate, TxOutTemplate, TxTemplate, +}; use bitcoin::Txid; -use common::*; use std::collections::HashSet; #[test] @@ -28,37 +27,23 @@ fn test_assumed_canonical_scenarios() { ]; let chain_tip = local_chain.tip().block_id(); - // create arrays before scenario to avoid lifetime issues let tx_templates = [ - TxTemplate { - tx_name: "txA", - inputs: &[TxInTemplate::Bogus], - outputs: &[TxOutTemplate::new(100000, Some(0))], - anchors: &[], - last_seen: None, - assume_canonical: false, - }, - TxTemplate { - tx_name: "txB", - inputs: &[TxInTemplate::PrevTx("txA", 0)], - outputs: &[TxOutTemplate::new(50000, Some(0))], - anchors: &[block_id!(5, "block5")], - last_seen: None, - assume_canonical: false, - }, - TxTemplate { - tx_name: "txC", - inputs: &[TxInTemplate::PrevTx("txB", 0)], - outputs: &[TxOutTemplate::new(25000, Some(0))], - anchors: &[], - last_seen: None, - assume_canonical: true, - }, + TxTemplate::new("txA") + .with_inputs(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(100000, Some(0))]), + TxTemplate::new("txB") + .with_inputs(vec![TxInTemplate::PrevTx("txA", 0)]) + .with_outputs(vec![TxOutTemplate::new(50000, Some(0))]) + .with_anchors(vec![block_id!(5, "block5")]), + TxTemplate::new("txC") + .with_inputs(vec![TxInTemplate::PrevTx("txB", 0)]) + .with_outputs(vec![TxOutTemplate::new(25000, Some(0))]) + .with_assume_canonical(true), ]; let exp_canonical_txs = HashSet::from(["txA", "txB", "txC"]); - let env = init_graph(&tx_templates); + let env = init_graph(tx_templates); // get the actual txid from given tx_name. let txid_c = *env.txid_to_name.get("txC").unwrap(); diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 96cafcb8ed..c31d687380 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::{ @@ -15,7 +12,7 @@ use bdk_chain::{ use bdk_testenv::{ anyhow::{self}, bitcoind::{Input, Output}, - block_id, hash, + block_id, hash, spk, utils::{new_tx, DESCRIPTORS}, TestEnv, }; @@ -25,16 +22,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 @@ -66,7 +53,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 621bd67067..60b2b56ed2 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, ConfirmationBlockTime}; use bdk_chain::{ local_chain::LocalChain, @@ -10,13 +8,13 @@ use bdk_chain::{ Anchor, 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; @@ -1097,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. @@ -1143,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())); } } @@ -1270,30 +1266,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", 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", 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 f1f1669844..0ca3e58cf2 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -1,26 +1,23 @@ #![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)] -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, } @@ -46,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", 0), + TxInTemplate::PrevTx("unconfirmed_coinbase", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]), + TxTemplate::new("confirmed_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("confirmed_genesis", 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)]), @@ -88,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", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(300), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(30000, Some(3))]) + .with_last_seen(300), ], // the txgraph is going to pick tx_conflict_2 because of higher lexicographical txid exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), @@ -124,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(200), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0), TxInTemplate::PrevTx("tx1", 1)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(300) ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx1", 1), ("tx_conflict_2", 0)]), @@ -160,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(30000, Some(2))]) + .with_last_seen(300), + TxTemplate::new("tx_conflict_3") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(40000, Some(3))]) + .with_last_seen(400), ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_3"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_3", 0)]), @@ -203,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("tx_orphaned_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_anchors(vec![block_id!(4, "Orphaned Block")]) + .with_last_seen(300), ], exp_chain_txs: HashSet::from(["tx1", "tx_orphaned_conflict"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_orphaned_conflict", 0)]), @@ -240,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("tx_orphaned_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_anchors(vec![block_id!(4, "Orphaned Block")]) + .with_last_seen(100), ], exp_chain_txs: HashSet::from(["tx1", "tx_conflict_1"]), exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_1", 0)]), @@ -277,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("tx_conflict_2") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_last_seen(300), + TxTemplate::new("tx_conflict_3") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 0)]) + .with_outputs(vec![TxOutTemplate::new(40_000, Some(3))]) + .with_last_seen(400), + TxTemplate::new("tx_confirmed_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("tx1", 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)]), @@ -327,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(22), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(23), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(2))]) + .with_last_seen(24), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(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 @@ -372,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(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", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 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", 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'"]), @@ -414,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", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(2), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 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'", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(3))]) + .with_last_seen(1) ], // B should not appear in the list methods exp_chain_txs: HashSet::from(["A", "B'", "C"]), @@ -461,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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0)]) + .with_outputs(vec![TxOutTemplate::new(30_000, Some(2))]) + .with_last_seen(300), + TxTemplate::new("C") + .with_inputs(vec![TxInTemplate::PrevTx("B", 0), TxInTemplate::PrevTx("B'", 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'"]), @@ -507,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(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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 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", 0), TxInTemplate::PrevTx("B'", 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'"]), @@ -553,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(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", 0), TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(200), + TxTemplate::new("B'") + .with_inputs(vec![TxInTemplate::PrevTx("A", 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", 0), TxInTemplate::PrevTx("B'", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(5))]), + TxTemplate::new("D") + .with_inputs(vec![TxInTemplate::PrevTx("C", 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'"]), @@ -605,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(vec![TxInTemplate::Bogus]) + .with_outputs(vec![TxOutTemplate::new(1_000, Some(0))]), + TxTemplate::new("second") + .with_inputs(vec![TxInTemplate::PrevTx("first", 0)]) + .with_outputs(vec![TxOutTemplate::new(900, Some(0))]), + TxTemplate::new("anchored") + .with_inputs(vec![TxInTemplate::PrevTx("second", 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)]), @@ -638,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(vec![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", 0)]) + .with_outputs(vec![TxOutTemplate::new(9_900, Some(1))]) + .with_last_seen(1_000), + TxTemplate::new("transitively_anchored_conflict") + .with_inputs(vec![TxInTemplate::PrevTx("root", 0)]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(1))]) + .with_last_seen(100), + TxTemplate::new("anchored") + .with_inputs(vec![TxInTemplate::PrevTx("transitively_anchored_conflict", 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)]), @@ -678,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", 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)]), @@ -701,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(vec![TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, None)]) + .with_last_seen(1), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, None)]) + .with_last_seen(2), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0), TxInTemplate::PrevTx("S1", 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, None)]) + .with_last_seen(3), ], exp_chain_txs: HashSet::from(["A", "S1"]), exp_chain_txouts: HashSet::from([]), @@ -731,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(vec![TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_last_seen(1), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(0))]) + .with_last_seen(3), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("S1", 0) ]) + .with_outputs(vec![TxOutTemplate::new(8_000, Some(0))]) + .with_last_seen(2), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("B", 0), TxInTemplate::PrevTx("A", 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, Some(0))]) + .with_last_seen(4), ], exp_chain_txs: HashSet::from(["A", "S1", "B"]), exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("S1", 0)]), @@ -768,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(vec![TxInTemplate::Bogus ]) + .with_outputs(vec![TxOutTemplate::new(10_000, Some(0))]) + .with_last_seen(1), + TxTemplate::new("S1") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0) ]) + .with_outputs(vec![TxOutTemplate::new(9_000, Some(0))]) + .with_last_seen(4), + TxTemplate::new("B") + .with_inputs(vec![TxInTemplate::PrevTx("S1", 0) ]) + .with_outputs(vec![TxOutTemplate::new(8_000, Some(0))]) + .with_last_seen(2), + TxTemplate::new("S2") + .with_inputs(vec![TxInTemplate::PrevTx("A", 0), TxInTemplate::PrevTx("B", 0) ]) + .with_outputs(vec![TxOutTemplate::new(17_000, Some(0))]) + .with_last_seen(3), ], exp_chain_txs: HashSet::from(["A", "S1", "B"]), exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("S1", 0)]), @@ -805,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(vec![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", 0) ]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_last_seen(2), + TxTemplate::new("unconfirmed_descendant") + .with_inputs(vec![TxInTemplate::PrevTx("unconfirmed", 0), + TxInTemplate::PrevTx("root", 1) ]) + .with_outputs(vec![TxOutTemplate::new(28_000, Some(2))]) + .with_last_seen(2), + TxTemplate::new("assume_canonical") + .with_inputs(vec![TxInTemplate::PrevTx("root", 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)]), @@ -853,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(vec![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", 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", 0), TxInTemplate::PrevTx("root", 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", 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)]), @@ -901,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(vec![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", 0)]) + .with_outputs(vec![TxOutTemplate::new(20_000, Some(1))]) + .with_assume_canonical(true), + TxTemplate::new("assume_b") + .with_inputs(vec![TxInTemplate::PrevTx("root", 0)]) + .with_outputs(vec![TxOutTemplate::new(19_000, Some(1))]) + .with_assume_canonical(true), + TxTemplate::new("assume_c") + .with_inputs(vec![TxInTemplate::PrevTx("root", 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)]), @@ -945,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(vec![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([]), @@ -968,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 canonical_view = local_chain.canonical_view( &env.tx_graph, @@ -983,7 +697,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, @@ -998,9 +712,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!( @@ -1016,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!( diff --git a/crates/testenv/Cargo.toml b/crates/testenv/Cargo.toml index 6cedcf4d30..361d265a0a 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.38.0", 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 3c3f8a6f48..d19600d5c3 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 52% rename from crates/chain/tests/common/tx_template.rs rename to crates/testenv/src/tx_template.rs index 7bdac78a50..8c2df439cf 100644 --- a/crates/chain/tests/common/tx_template.rs +++ b/crates/testenv/src/tx_template.rs @@ -1,69 +1,150 @@ -#![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, CanonicalParams}; +use crate::utils::DESCRIPTORS; +use bdk_chain::{ + miniscript::Descriptor, spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor, CanonicalParams, +}; 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. -#[derive(Clone, Copy, Default)] -pub struct TxTemplate<'a, A> { - /// Uniquely identifies the transaction, before it can have a txid. - pub tx_name: &'a str, - pub inputs: &'a [TxInTemplate<'a>], - pub outputs: &'a [TxOutTemplate], - pub anchors: &'a [A], +/// 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)] +pub struct TxTemplate { + /// A unique name used to refer to this transaction in other templates. + pub tx_name: &'static str, + + /// The inputs of this transaction. + pub inputs: Vec, + + /// The outputs of this transaction. + pub outputs: Vec, + + /// Anchors (confirmations) for this transaction. + pub anchors: Vec, + + /// 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`]. +#[derive(Clone, Debug)] #[allow(dead_code)] -pub enum TxInTemplate<'a> { - /// This will give a random txid and vout. +pub enum TxInTemplate { + /// 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`. - PrevTx(&'a str, usize), + /// 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(&'static str, usize), } +/// Describes an output in a [`TxTemplate`]. +#[derive(Clone, Copy, Debug)] 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, +} + +impl Default for TxTemplate { + fn default() -> Self { + Self { + tx_name: "", + inputs: Vec::new(), + outputs: Vec::new(), + anchors: Vec::new(), + last_seen: None, + assume_canonical: false, + } + } +} + +impl TxTemplate { + /// Create a new template with a name. + pub fn new(tx_name: &'static str) -> Self { + Self { + tx_name, + ..Default::default() + } + } + + /// Set the inputs of this transaction. + pub fn with_inputs(mut self, inputs: Vec) -> Self { + self.inputs = inputs; + self + } + + /// Set the outputs of this transaction. + pub fn with_outputs(mut self, outputs: Vec) -> Self { + self.outputs = outputs; + self + } + + /// Set the anchors (confirmations) of this transaction. + pub fn with_anchors(mut self, anchors: Vec) -> Self { + self.anchors = anchors; + 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: u64) -> Self { + self.last_seen = Some(last_seen); + self + } } -#[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 struct TxTemplateEnv { pub tx_graph: TxGraph, pub indexer: SpkTxOutIndex, - pub txid_to_name: HashMap<&'a str, Txid>, + pub txid_to_name: HashMap<&'static str, Txid>, pub canonicalization_params: CanonicalParams, } +/// 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. #[allow(dead_code)] -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(); let mut tx_graph = TxGraph::::default(); @@ -77,9 +158,9 @@ 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::<&'static str, Txid>::new(); let mut canonicalization_params = CanonicalParams::default(); + for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() { let tx = Transaction { version: transaction::Version::non_standard(0), 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/*)",