From 29055658a643f72923882785fbd9894ba38b27cf Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Sat, 29 Apr 2023 17:27:12 +0530 Subject: [PATCH] Test cleanup and added documentation. - Explainer doc added for the test. - Test code simplified. - One more edge cases added. --- crates/chain/tests/test_indexed_tx_graph.rs | 350 +++++++------------- 1 file changed, 124 insertions(+), 226 deletions(-) diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 4e847aec..78f3c3a6 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -6,6 +6,7 @@ use std::collections::{BTreeMap, BTreeSet}; use bdk_chain::{ indexed_tx_graph::{IndexedAdditions, IndexedTxGraph}, keychain::{Balance, DerivationAdditions, KeychainTxOutIndex}, + local_chain::LocalChain, tx_graph::Additions, BlockId, ObservedAs, }; @@ -76,36 +77,66 @@ fn insert_relevant_txs() { } #[test] -fn test_list_owned_txouts() { - let mut local_chain = local_chain![ - (0, h!("Block 0")), - (1, h!("Block 1")), - (2, h!("Block 2")), - (3, h!("Block 3")) - ]; +/// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists +/// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain). +/// +/// Test Setup: +/// +/// Local Chain => <0> ----- <1> ----- <2> ----- <3> ---- ... ---- <150> +/// +/// Keychains: +/// +/// keychain_1: Trusted +/// keychain_2: Untrusted +/// +/// Transactions: +/// +/// tx1: A Coinbase, sending 70000 sats to "trusted" address. [Block 0] +/// tx2: A external Receive, sending 30000 sats to "untrusted" address. [Block 1] +/// tx3: Internal Spend. Spends tx2 and returns change of 10000 to "trusted" address. [Block 2] +/// tx4: Mempool tx, sending 20000 sats to "trusted" address. +/// tx5: Mempool tx, sending 15000 sats to "untested" address. +/// tx6: Complete unrelated tx. [Block 3] +/// +/// Different transactions are added via `insert_relevant_txs`. +/// `list_owned_txout`, `list_owned_utxos` and `balance` method is asserted +/// with expected values at Block height 0, 1, and 2. +/// +/// Finally Add more blocks to local chain until tx1 coinbase maturity hits. +/// Assert maturity at coinbase maturity inflection height. Block height 98 and 99. - let desc_1 : &str = "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)"; - let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), desc_1).unwrap(); - let desc_2 : &str = "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)"; - let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), desc_2).unwrap(); +fn test_list_owned_txouts() { + // Create Local chains + + let local_chain = (0..150) + .map(|i| (i as u32, h!("random"))) + .collect::>(); + let local_chain = LocalChain::from(local_chain); + + // Initiate IndexedTxGraph + + let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap(); + let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap(); let mut graph = IndexedTxGraph::>::default(); graph.index.add_keychain("keychain_1".into(), desc_1); graph.index.add_keychain("keychain_2".into(), desc_2); - graph.index.set_lookahead_for_all(10); + // Get trusted and untrusted addresses + let mut trusted_spks = Vec::new(); let mut untrusted_spks = Vec::new(); { + // we need to scope here to take immutanble reference of the graph for _ in 0..10 { let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_1".to_string()); + // TODO Assert indexes trusted_spks.push(script.clone()); } } - { for _ in 0..10 { let ((_, script), _) = graph.index.reveal_next_spk(&"keychain_2".to_string()); @@ -113,9 +144,9 @@ fn test_list_owned_txouts() { } } - let trust_predicate = |spk: &Script| trusted_spks.contains(spk); + // Create test transactions - // tx1 is coinbase transaction received at trusted keychain at block 0. + // tx1 is the genesis coinbase let tx1 = Transaction { input: vec![TxIn { previous_output: OutPoint::null(), @@ -171,6 +202,9 @@ fn test_list_owned_txouts() { // tx6 is an unrelated transaction confirmed at 3. let tx6 = common::new_tx(0); + // Insert transactions into graph with respective anchors + // For unconfirmed txs we pass in `None`. + let _ = graph.insert_relevant_txs( [&tx1, &tx2, &tx3, &tx6] .iter() @@ -181,22 +215,25 @@ fn test_list_owned_txouts() { let _ = graph.insert_relevant_txs([&tx4, &tx5].iter().map(|tx| (*tx, None)), Some(100)); - // AT Block 0 - { + // A helper lambda to extract and filter data from the graph. + let fetch = |ht: u32, graph: &IndexedTxGraph>| { let txouts = graph - .list_owned_txouts(&local_chain, local_chain.get_block(0).unwrap()) + .list_owned_txouts(&local_chain, local_chain.get_block(ht).unwrap()) .collect::>(); let utxos = graph - .list_owned_unspents(&local_chain, local_chain.get_block(0).unwrap()) + .list_owned_unspents(&local_chain, local_chain.get_block(ht).unwrap()) .collect::>(); let balance = graph.balance( &local_chain, - local_chain.get_block(0).unwrap(), - trust_predicate, + local_chain.get_block(ht).unwrap(), + |spk: &Script| trusted_spks.contains(spk), ); + assert_eq!(txouts.len(), 5); + assert_eq!(utxos.len(), 4); + let confirmed_txouts_txid = txouts .iter() .filter_map(|full_txout| { @@ -208,7 +245,7 @@ fn test_list_owned_txouts() { }) .collect::>(); - let unconfirmed_txout_txid = txouts + let unconfirmed_txouts_txid = txouts .iter() .filter_map(|full_txout| { if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { @@ -241,12 +278,30 @@ fn test_list_owned_txouts() { }) .collect::>(); - assert_eq!(txouts.len(), 5); - assert_eq!(utxos.len(), 4); + ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) + }; + + // ----- TEST BLOCK ----- + + // AT Block 0 + { + let ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) = fetch(0, &graph); assert_eq!(confirmed_txouts_txid, [tx1.txid()].into()); assert_eq!( - unconfirmed_txout_txid, + unconfirmed_txouts_txid, [tx2.txid(), tx3.txid(), tx4.txid(), tx5.txid()].into() ); @@ -269,71 +324,18 @@ fn test_list_owned_txouts() { // AT Block 1 { - let txouts = graph - .list_owned_txouts(&local_chain, local_chain.get_block(1).unwrap()) - .collect::>(); - - let utxos = graph - .list_owned_unspents(&local_chain, local_chain.get_block(1).unwrap()) - .collect::>(); - - let balance = graph.balance( - &local_chain, - local_chain.get_block(1).unwrap(), - trust_predicate, - ); - - let confirmed_txouts_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_txout_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let confirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - assert_eq!(txouts.len(), 5); - assert_eq!(utxos.len(), 4); + let ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) = fetch(1, &graph); // tx2 gets into confirmed txout set assert_eq!(confirmed_txouts_txid, [tx1.txid(), tx2.txid()].into()); assert_eq!( - unconfirmed_txout_txid, + unconfirmed_txouts_txid, [tx3.txid(), tx4.txid(), tx5.txid()].into() ); @@ -344,7 +346,6 @@ fn test_list_owned_txouts() { [tx3.txid(), tx4.txid(), tx5.txid()].into() ); - // Balance breakup remains same assert_eq!( balance, Balance { @@ -358,73 +359,20 @@ fn test_list_owned_txouts() { // AT Block 2 { - let txouts = graph - .list_owned_txouts(&local_chain, local_chain.get_block(2).unwrap()) - .collect::>(); - - let utxos = graph - .list_owned_unspents(&local_chain, local_chain.get_block(2).unwrap()) - .collect::>(); - - let balance = graph.balance( - &local_chain, - local_chain.get_block(2).unwrap(), - trust_predicate, - ); - - let confirmed_txouts_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_txout_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let confirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - assert_eq!(txouts.len(), 5); - assert_eq!(utxos.len(), 4); + let ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) = fetch(2, &graph); // tx3 now gets into the confirmed txout set assert_eq!( confirmed_txouts_txid, [tx1.txid(), tx2.txid(), tx3.txid()].into() ); - assert_eq!(unconfirmed_txout_txid, [tx4.txid(), tx5.txid()].into()); + assert_eq!(unconfirmed_txouts_txid, [tx4.txid(), tx5.txid()].into()); // tx3 also gets into confirmed utxo set assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into()); @@ -441,99 +389,49 @@ fn test_list_owned_txouts() { ); } - // AT Block 110 + // AT Block 98 { - let mut local_chain_extension = (4..150) - .map(|i| (i as u32, h!("random"))) - .collect::>(); - - local_chain_extension.insert(3, h!("Block 3")); - - local_chain - .apply_update(local_chain_extension.into()) - .unwrap(); - - let txouts = graph - .list_owned_txouts(&local_chain, local_chain.get_block(110).unwrap()) - .collect::>(); - - let utxos = graph - .list_owned_unspents(&local_chain, local_chain.get_block(110).unwrap()) - .collect::>(); - - let balance = graph.balance( - &local_chain, - local_chain.get_block(110).unwrap(), - trust_predicate, - ); - - let confirmed_txouts_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_txout_txid = txouts - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let confirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - let unconfirmed_utxos_txid = utxos - .iter() - .filter_map(|full_txout| { - if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) { - Some(full_txout.outpoint.txid) - } else { - None - } - }) - .collect::>(); - - println!("TxOuts : {:#?}", txouts); - println!("UTXOS {:#?}", utxos); - println!("{:#?}", balance); - - assert_eq!(txouts.len(), 5); - assert_eq!(utxos.len(), 4); + let ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) = fetch(98, &graph); assert_eq!( confirmed_txouts_txid, [tx1.txid(), tx2.txid(), tx3.txid()].into() ); - assert_eq!(unconfirmed_txout_txid, [tx4.txid(), tx5.txid()].into()); + assert_eq!(unconfirmed_txouts_txid, [tx4.txid(), tx5.txid()].into()); assert_eq!(confirmed_utxos_txid, [tx1.txid(), tx3.txid()].into()); assert_eq!(unconfirmed_utxos_txid, [tx4.txid(), tx5.txid()].into()); + // Coinbase is still immature assert_eq!( balance, Balance { - immature: 0, // immature coinbase + immature: 70000, // immature coinbase trusted_pending: 15000, // tx5 untrusted_pending: 20000, // tx4 - confirmed: 80000 // tx1 got matured + confirmed: 10000 // tx1 got matured + } + ); + } + + // AT Block 99 + { + let (_, _, _, _, balance) = fetch(100, &graph); + + // Coinbase maturity hits + assert_eq!( + balance, + Balance { + immature: 0, // coinbase matured + trusted_pending: 15000, // tx5 + untrusted_pending: 20000, // tx4 + confirmed: 80000 // tx1 + tx3 } ); }