]> Untitled Git - bdk/commitdiff
Include tests for new updates of TxGraph
authorrajarshimaitra <rajarshi149@gmail.com>
Sat, 8 Apr 2023 15:32:33 +0000 (21:02 +0530)
committer志宇 <hello@evanlinjin.me>
Wed, 12 Apr 2023 05:21:20 +0000 (13:21 +0800)
crates/chain/src/local_chain.rs
crates/chain/tests/test_tx_graph.rs

index 58cf923b47f67a712771b3e944bfea12d77efb8e..e3385e1b4fe0ce8b5414eca64f4cc794f3a2afe8 100644 (file)
@@ -67,6 +67,13 @@ impl LocalChain {
             .map(|(&height, &hash)| BlockId { height, hash })
     }
 
+    /// Get a block at the given height.
+    pub fn get_block(&self, height: u32) -> Option<BlockId> {
+        self.blocks
+            .get(&height)
+            .map(|&hash| BlockId { height, hash })
+    }
+
     /// This is like the sparsechain's logic, expect we must guarantee that all invalidated heights
     /// are to be re-filled.
     pub fn determine_changeset(&self, update: &Self) -> Result<ChangeSet, UpdateNotConnectedError> {
index 279ddb74bf64caa1453f3c82b0fd9e48467e88f0..e3bc0ac8e8a562ad2e00a6e2bce62fad33fa5ae6 100644 (file)
@@ -2,13 +2,18 @@
 mod common;
 use bdk_chain::{
     collections::*,
+    local_chain::LocalChain,
     tx_graph::{Additions, TxGraph},
+    BlockId, ObservedAs,
+};
+use bitcoin::{
+    hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
 };
-use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid};
 use core::iter;
 
 #[test]
 fn insert_txouts() {
+    // 2 (Outpoint, TxOut) tupples that denotes original data in the graph, as partial transactions.
     let original_ops = [
         (
             OutPoint::new(h!("tx1"), 1),
@@ -26,6 +31,7 @@ fn insert_txouts() {
         ),
     ];
 
+    // Another (OutPoint, TxOut) tupple to be used as update as partial transaction.
     let update_ops = [(
         OutPoint::new(h!("tx2"), 0),
         TxOut {
@@ -34,8 +40,32 @@ fn insert_txouts() {
         },
     )];
 
+    // One full transaction to be included in the update
+    let update_txs = Transaction {
+        version: 0x01,
+        lock_time: PackedLockTime(0),
+        input: vec![TxIn {
+            previous_output: OutPoint::null(),
+            ..Default::default()
+        }],
+        output: vec![TxOut {
+            value: 30_000,
+            script_pubkey: Script::new(),
+        }],
+    };
+
+    // Conf anchor used to mark the full transaction as confirmed.
+    let conf_anchor = ObservedAs::Confirmed(BlockId {
+        height: 100,
+        hash: h!("random blockhash"),
+    });
+
+    // Unconfirmed anchor to mark the partial transactions as unconfirmed
+    let unconf_anchor = ObservedAs::<BlockId>::Unconfirmed(1000000);
+
+    // Make the original graph
     let mut graph = {
-        let mut graph = TxGraph::<()>::default();
+        let mut graph = TxGraph::<ObservedAs<BlockId>>::default();
         for (outpoint, txout) in &original_ops {
             assert_eq!(
                 graph.insert_txout(*outpoint, txout.clone()),
@@ -48,9 +78,11 @@ fn insert_txouts() {
         graph
     };
 
+    // Make the update graph
     let update = {
         let mut graph = TxGraph::default();
         for (outpoint, txout) in &update_ops {
+            // Insert partials transactions
             assert_eq!(
                 graph.insert_txout(*outpoint, txout.clone()),
                 Additions {
@@ -58,25 +90,101 @@ fn insert_txouts() {
                     ..Default::default()
                 }
             );
+            // Mark them unconfirmed.
+            assert_eq!(
+                graph.insert_anchor(outpoint.txid, unconf_anchor),
+                Additions {
+                    tx: [].into(),
+                    txout: [].into(),
+                    anchors: [(unconf_anchor, outpoint.txid)].into(),
+                    last_seen: [].into()
+                }
+            );
+            // Mark them last seen at.
+            assert_eq!(
+                graph.insert_seen_at(outpoint.txid, 1000000),
+                Additions {
+                    tx: [].into(),
+                    txout: [].into(),
+                    anchors: [].into(),
+                    last_seen: [(outpoint.txid, 1000000)].into()
+                }
+            );
         }
+        // Insert the full transaction
+        assert_eq!(
+            graph.insert_tx(update_txs.clone()),
+            Additions {
+                tx: [update_txs.clone()].into(),
+                ..Default::default()
+            }
+        );
+
+        // Mark it as confirmed.
+        assert_eq!(
+            graph.insert_anchor(update_txs.txid(), conf_anchor),
+            Additions {
+                tx: [].into(),
+                txout: [].into(),
+                anchors: [(conf_anchor, update_txs.txid())].into(),
+                last_seen: [].into()
+            }
+        );
         graph
     };
 
+    // Check the resulting addition.
     let additions = graph.determine_additions(&update);
 
     assert_eq!(
         additions,
         Additions {
-            tx: [].into(),
+            tx: [update_txs.clone()].into(),
             txout: update_ops.into(),
-            ..Default::default()
+            anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
+            last_seen: [(h!("tx2"), 1000000)].into()
         }
     );
 
+    // Apply addition and check the new graph counts.
     graph.apply_additions(additions);
-    assert_eq!(graph.all_txouts().count(), 3);
-    assert_eq!(graph.full_transactions().count(), 0);
+    assert_eq!(graph.all_txouts().count(), 4);
+    assert_eq!(graph.full_transactions().count(), 1);
     assert_eq!(graph.partial_transactions().count(), 2);
+
+    // Check TxOuts are fetched correctly from the graph.
+    assert_eq!(
+        graph.txouts(h!("tx1")).expect("should exists"),
+        [
+            (
+                1u32,
+                &TxOut {
+                    value: 10_000,
+                    script_pubkey: Script::new(),
+                }
+            ),
+            (
+                2u32,
+                &TxOut {
+                    value: 20_000,
+                    script_pubkey: Script::new(),
+                }
+            )
+        ]
+        .into()
+    );
+
+    assert_eq!(
+        graph.txouts(update_txs.txid()).expect("should exists"),
+        [(
+            0u32,
+            &TxOut {
+                value: 30_000,
+                script_pubkey: Script::new()
+            }
+        )]
+        .into()
+    );
 }
 
 #[test]
@@ -511,3 +619,165 @@ fn test_descendants_no_repeat() {
     }
     assert!(expected_txids.is_empty());
 }
+
+#[test]
+fn test_chain_spends() {
+    let local_chain: LocalChain = (0..=100)
+        .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");
+
+    // The parent tx contains 2 outputs. Which are spent by one confirmed and one unconfirmed tx.
+    // The parent tx is confirmed at block 95.
+    let tx_0 = Transaction {
+        input: vec![],
+        output: vec![
+            TxOut {
+                value: 10_000,
+                script_pubkey: Script::new(),
+            },
+            TxOut {
+                value: 20_000,
+                script_pubkey: Script::new(),
+            },
+        ],
+        ..common::new_tx(0)
+    };
+
+    // The first confirmed transaction spends vout: 0. And is confirmed at block 98.
+    let tx_1 = Transaction {
+        input: vec![TxIn {
+            previous_output: OutPoint::new(tx_0.txid(), 0),
+            ..TxIn::default()
+        }],
+        output: vec![
+            TxOut {
+                value: 5_000,
+                script_pubkey: Script::new(),
+            },
+            TxOut {
+                value: 5_000,
+                script_pubkey: Script::new(),
+            },
+        ],
+        ..common::new_tx(0)
+    };
+
+    // The second transactions spends vout:1, and is unconfirmed.
+    let tx_2 = Transaction {
+        input: vec![TxIn {
+            previous_output: OutPoint::new(tx_0.txid(), 1),
+            ..TxIn::default()
+        }],
+        output: vec![
+            TxOut {
+                value: 10_000,
+                script_pubkey: Script::new(),
+            },
+            TxOut {
+                value: 10_000,
+                script_pubkey: Script::new(),
+            },
+        ],
+        ..common::new_tx(0)
+    };
+
+    let mut graph = TxGraph::<BlockId>::default();
+
+    let _ = graph.insert_tx(tx_0.clone());
+    let _ = graph.insert_tx(tx_1.clone());
+    let _ = graph.insert_tx(tx_2.clone());
+
+    [95, 98]
+        .iter()
+        .zip([&tx_0, &tx_1].into_iter())
+        .for_each(|(ht, tx)| {
+            let block_id = local_chain.get_block(*ht).expect("block expected");
+            let _ = graph.insert_anchor(tx.txid(), block_id);
+        });
+
+    // Assert that confirmed spends are returned correctly.
+    assert_eq!(
+        graph
+            .get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 0))
+            .unwrap(),
+        (
+            ObservedAs::Confirmed(&local_chain.get_block(98).expect("block expected")),
+            tx_1.txid()
+        )
+    );
+
+    // Check if chain position is returned correctly.
+    assert_eq!(
+        graph
+            .get_chain_position(&local_chain, tip, tx_0.txid())
+            .expect("position expected"),
+        ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))
+    );
+
+    // As long the unconfirmed tx isn't marked as seen, chain_spend will return None.
+    assert!(graph
+        .get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
+        .is_none());
+
+    // Mark the unconfirmed as seen and check correct ObservedAs status is returned.
+    let _ = graph.insert_seen_at(tx_2.txid(), 1234567);
+
+    // Check chain spend returned correctly.
+    assert_eq!(
+        graph
+            .get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
+            .unwrap(),
+        (ObservedAs::Unconfirmed(1234567), tx_2.txid())
+    );
+
+    // A conflicting transaction that conflicts with tx_1.
+    let tx_1_conflict = Transaction {
+        input: vec![TxIn {
+            previous_output: OutPoint::new(tx_0.txid(), 0),
+            ..Default::default()
+        }],
+        ..common::new_tx(0)
+    };
+    let _ = graph.insert_tx(tx_1_conflict.clone());
+
+    // Because this tx conflicts with an already confirmed transaction, chain position should return none.
+    assert!(graph
+        .get_chain_position(&local_chain, tip, tx_1_conflict.txid())
+        .is_none());
+
+    // Another conflicting tx that conflicts with tx_2.
+    let tx_2_conflict = Transaction {
+        input: vec![TxIn {
+            previous_output: OutPoint::new(tx_0.txid(), 1),
+            ..Default::default()
+        }],
+        ..common::new_tx(0)
+    };
+
+    // Insert in graph and mark it as seen.
+    let _ = graph.insert_tx(tx_2_conflict.clone());
+    let _ = graph.insert_seen_at(tx_2_conflict.txid(), 1234568);
+
+    // This should return a valid observation with correct last seen.
+    assert_eq!(
+        graph
+            .get_chain_position(&local_chain, tip, tx_2_conflict.txid())
+            .expect("position expected"),
+        ObservedAs::Unconfirmed(1234568)
+    );
+
+    // Chain_spend now catches the new transaction as the spend.
+    assert_eq!(
+        graph
+            .get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
+            .expect("expect observation"),
+        (ObservedAs::Unconfirmed(1234568), tx_2_conflict.txid())
+    );
+
+    // Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict`
+    assert!(graph
+        .get_chain_position(&local_chain, tip, tx_2.txid())
+        .is_none());
+}