From 48ca95b5412fd3719b749d33c85572941817e967 Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Fri, 29 Sep 2023 15:54:38 +0200 Subject: [PATCH] test(chain): Add test for walk_ancestors Co-authored-by: Wei Chen --- crates/chain/tests/test_tx_graph.rs | 204 +++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 36a27a58..a0efd100 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -5,7 +5,7 @@ use bdk_chain::{ collections::*, local_chain::LocalChain, tx_graph::{ChangeSet, TxGraph}, - Anchor, Append, BlockId, ChainPosition, ConfirmationHeightAnchor, + Anchor, Append, BlockId, ChainOracle, ChainPosition, ConfirmationHeightAnchor, }; use bitcoin::{ absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, @@ -496,6 +496,208 @@ fn test_calculate_fee_on_coinbase() { assert_eq!(graph.calculate_fee(&tx), Ok(0)); } +// `test_walk_ancestors` uses the following transaction structure: +// +// a0 +// / \ +// b0 b1 b2 +// / \ \ / +// c0 c1 c2 c3 +// / \ / +// d0 d1 +// \ +// e0 +// +// where b0 and b1 spend a0, c0 and c1 spend b0, d0 spends c1, etc. +#[test] +fn test_walk_ancestors() { + let local_chain: LocalChain = (0..=20) + .map(|ht| (ht, BlockHash::hash(format!("Block Hash {}", ht).as_bytes()))) + .collect::>() + .into(); + let tip = local_chain.tip().expect("must have tip"); + + let tx_a0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(h!("op0"), 0), + ..TxIn::default() + }], + output: vec![TxOut::default(), TxOut::default()], + ..common::new_tx(0) + }; + + // tx_b0 spends tx_a0 + let tx_b0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_a0.txid(), 0), + ..TxIn::default() + }], + output: vec![TxOut::default(), TxOut::default()], + ..common::new_tx(0) + }; + + // tx_b1 spends tx_a0 + let tx_b1 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_a0.txid(), 1), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + let tx_b2 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(h!("op1"), 0), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_c0 spends tx_b0 + let tx_c0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_b0.txid(), 0), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_c1 spends tx_b0 + let tx_c1 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_b0.txid(), 1), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_c2 spends tx_b1 and tx_b2 + let tx_c2 = Transaction { + input: vec![ + TxIn { + previous_output: OutPoint::new(tx_b1.txid(), 0), + ..TxIn::default() + }, + TxIn { + previous_output: OutPoint::new(tx_b2.txid(), 0), + ..TxIn::default() + }, + ], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + let tx_c3 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(h!("op2"), 0), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_d0 spends tx_c1 + let tx_d0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_c1.txid(), 0), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_d1 spends tx_c2 and tx_c3 + let tx_d1 = Transaction { + input: vec![ + TxIn { + previous_output: OutPoint::new(tx_c2.txid(), 0), + ..TxIn::default() + }, + TxIn { + previous_output: OutPoint::new(tx_c3.txid(), 0), + ..TxIn::default() + }, + ], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + // tx_e0 spends tx_d1 + let tx_e0 = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(tx_d1.txid(), 0), + ..TxIn::default() + }], + output: vec![TxOut::default()], + ..common::new_tx(0) + }; + + let mut graph = TxGraph::::new(vec![ + tx_a0.clone(), + tx_b0.clone(), + tx_b1.clone(), + tx_b2.clone(), + tx_c0.clone(), + tx_c1.clone(), + tx_c2.clone(), + tx_c3.clone(), + tx_d0.clone(), + tx_d1.clone(), + tx_e0.clone(), + ]); + + [&tx_a0, &tx_b1].iter().for_each(|&tx| { + let _ = graph.insert_anchor(tx.txid(), tip.block_id()); + }); + + let ancestors = [ + graph + .walk_ancestors(&tx_c0, |depth, tx| Some((depth, tx))) + .collect::>(), + graph + .walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx))) + .collect::>(), + graph + .walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx))) + .collect::>(), + // Only traverse unconfirmed ancestors of tx_e0 this time + graph + .walk_ancestors(&tx_e0, |depth, tx| { + let tx_node = graph.get_tx_node(tx.txid())?; + for block in tx_node.anchors { + match local_chain.is_block_in_chain(block.anchor_block(), tip.block_id()) { + Ok(Some(true)) => return None, + _ => continue, + } + } + Some((depth, tx_node.tx)) + }) + .collect::>(), + ]; + + let expected_ancestors = [ + vec![(1, &tx_b0), (2, &tx_a0)], + vec![(1, &tx_c1), (2, &tx_b0), (3, &tx_a0)], + vec![ + (1, &tx_d1), + (2, &tx_c2), + (2, &tx_c3), + (3, &tx_b1), + (3, &tx_b2), + (4, &tx_a0), + ], + vec![(1, &tx_d1), (2, &tx_c2), (2, &tx_c3), (3, &tx_b2)], + ]; + + for (txids, expected_txids) in ancestors.iter().zip(expected_ancestors.iter()) { + assert_eq!(txids, expected_txids); + } +} + #[test] fn test_conflicting_descendants() { let previous_output = OutPoint::new(h!("op"), 2);