]> Untitled Git - bdk/commitdiff
test(chain): Add test for walk_ancestors
authorDaniela Brozzoni <danielabrozzoni@protonmail.com>
Fri, 29 Sep 2023 13:54:38 +0000 (15:54 +0200)
committerDaniela Brozzoni <danielabrozzoni@protonmail.com>
Wed, 4 Oct 2023 12:00:47 +0000 (14:00 +0200)
Co-authored-by: Wei Chen <wzc110@gmail.com>
crates/chain/tests/test_tx_graph.rs

index 36a27a589d2b4a14ebd442f81b68627e9d5b4df9..a0efd1004c4714add05183a629f218fd371f3db5 100644 (file)
@@ -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::<BTreeMap<u32, BlockHash>>()
+        .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::<BlockId>::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::<Vec<_>>(),
+        graph
+            .walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx)))
+            .collect::<Vec<_>>(),
+        graph
+            .walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx)))
+            .collect::<Vec<_>>(),
+        // 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::<Vec<_>>(),
+    ];
+
+    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);