test(chain): Add test for conflicting transactions
Co-authored-by: Wei Chen <wzc110@gmail.com>
This commit is contained in:
		
							parent
							
								
									48ca95b541
								
							
						
					
					
						commit
						6d601a7e88
					
				
							
								
								
									
										629
									
								
								crates/chain/tests/test_tx_graph_conflicts.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										629
									
								
								crates/chain/tests/test_tx_graph_conflicts.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,629 @@ | |||||||
|  | #[macro_use] | ||||||
|  | mod common; | ||||||
|  | 
 | ||||||
|  | use std::collections::{BTreeSet, HashSet}; | ||||||
|  | 
 | ||||||
|  | use bdk_chain::{keychain::Balance, BlockId}; | ||||||
|  | use bitcoin::{OutPoint, Script}; | ||||||
|  | use common::*; | ||||||
|  | 
 | ||||||
|  | #[allow(dead_code)] | ||||||
|  | struct Scenario<'a> { | ||||||
|  |     /// Name of the test scenario
 | ||||||
|  |     name: &'a str, | ||||||
|  |     /// Transaction templates
 | ||||||
|  |     tx_templates: &'a [TxTemplate<'a, BlockId>], | ||||||
|  |     /// Names of txs that must exist in the output of `list_chain_txs`
 | ||||||
|  |     exp_chain_txs: HashSet<&'a str>, | ||||||
|  |     /// Outpoints that must exist in the output of `filter_chain_txouts`
 | ||||||
|  |     exp_chain_txouts: HashSet<(&'a str, u32)>, | ||||||
|  |     /// Outpoints of UTXOs that must exist in the output of `filter_chain_unspents`
 | ||||||
|  |     exp_unspents: HashSet<(&'a str, u32)>, | ||||||
|  |     /// Expected balances
 | ||||||
|  |     exp_balance: Balance, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// This test ensures that [`TxGraph`] will reliably filter out irrelevant transactions when
 | ||||||
|  | /// presented with multiple conflicting transaction scenarios using the [`TxTemplate`] structure.
 | ||||||
|  | /// This test also checks that [`TxGraph::list_chain_txs`], [`TxGraph::filter_chain_txouts`],
 | ||||||
|  | /// [`TxGraph::filter_chain_unspents`], and [`TxGraph::balance`] return correct data.
 | ||||||
|  | #[test] | ||||||
|  | fn test_tx_conflict_handling() { | ||||||
|  |     // Create Local chains
 | ||||||
|  |     let local_chain = local_chain!( | ||||||
|  |         (0, h!("A")), | ||||||
|  |         (1, h!("B")), | ||||||
|  |         (2, h!("C")), | ||||||
|  |         (3, h!("D")), | ||||||
|  |         (4, h!("E")), | ||||||
|  |         (5, h!("F")), | ||||||
|  |         (6, h!("G")) | ||||||
|  |     ); | ||||||
|  |     let chain_tip = local_chain | ||||||
|  |         .tip() | ||||||
|  |         .map(|cp| cp.block_id()) | ||||||
|  |         .unwrap_or_default(); | ||||||
|  | 
 | ||||||
|  |     let scenarios = [ | ||||||
|  |         Scenario { | ||||||
|  |             name: "2 unconfirmed txs with same last_seens conflict", | ||||||
|  |             tx_templates: &[ | ||||||
|  |                 TxTemplate { | ||||||
|  |                     tx_name: "tx1", | ||||||
|  |                     inputs: &[TxInTemplate::Bogus], | ||||||
|  |                     outputs: &[TxOutTemplate::new(40000, Some(0))], | ||||||
|  |                     anchors: &[block_id!(1, "B")], | ||||||
|  |                     last_seen: None, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // correct output if filtered by fee rate: tx1, tx_conflict_1
 | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_conflict_1", "tx_conflict_2"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_1", 0), ("tx_conflict_2", 0)]), | ||||||
|  |             // correct output if filtered by fee rate: tx_conflict_1
 | ||||||
|  |             exp_unspents: HashSet::from([("tx_conflict_1", 0), ("tx_conflict_2", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 50000, // correct output if filtered by fee rate: 20000
 | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_conflict_2"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx1", 1), ("tx_conflict_2", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("tx_conflict_2", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 30000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_conflict_3"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_3", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("tx_conflict_3", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 40000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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), | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_orphaned_conflict"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_orphaned_conflict", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("tx_orphaned_conflict", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 30000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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), | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_conflict_1"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_conflict_1", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("tx_conflict_1", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 20000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             exp_chain_txs: HashSet::from(["tx1", "tx_confirmed_conflict"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("tx1", 0), ("tx_confirmed_conflict", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("tx_confirmed_conflict", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 0, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 50000, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // 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
 | ||||||
|  |             // last seen than B', so B and C are considered canonical
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B", "C"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("C", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("C", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 30000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // B and C should not appear in the list methods
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B'"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("A", 0), ("B'", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("B'", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 0, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 20000, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // B should not appear in the list methods
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B'", "C"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([ | ||||||
|  |                 ("A", 0), | ||||||
|  |                 ("B'", 0), | ||||||
|  |                 ("C", 0), | ||||||
|  |             ]), | ||||||
|  |             exp_unspents: HashSet::from([("C", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 30000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // C should not appear in the list methods
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B'"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("A", 0), ("B'", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("B'", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 30000, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 0, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // C should not appear in the list methods
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B'"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("A", 0), ("B'", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("B'", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 0, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 50000, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         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, | ||||||
|  |                 }, | ||||||
|  |                 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() | ||||||
|  |                 }, | ||||||
|  |             ], | ||||||
|  |             // D should not appear in the list methods
 | ||||||
|  |             exp_chain_txs: HashSet::from(["A", "B'"]), | ||||||
|  |             exp_chain_txouts: HashSet::from([("A", 0), ("B'", 0)]), | ||||||
|  |             exp_unspents: HashSet::from([("B'", 0)]), | ||||||
|  |             exp_balance: Balance { | ||||||
|  |                 immature: 0, | ||||||
|  |                 trusted_pending: 0, | ||||||
|  |                 untrusted_pending: 0, | ||||||
|  |                 confirmed: 50000, | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     for scenario in scenarios { | ||||||
|  |         let (tx_graph, spk_index, exp_tx_ids) = init_graph(scenario.tx_templates.iter()); | ||||||
|  | 
 | ||||||
|  |         let txs = tx_graph | ||||||
|  |             .list_chain_txs(&local_chain, chain_tip) | ||||||
|  |             .map(|tx| tx.tx_node.txid) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         let exp_txs = scenario | ||||||
|  |             .exp_chain_txs | ||||||
|  |             .iter() | ||||||
|  |             .map(|txid| *exp_tx_ids.get(txid).expect("txid must exist")) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         assert_eq!( | ||||||
|  |             txs, exp_txs, | ||||||
|  |             "\n[{}] 'list_chain_txs' failed", | ||||||
|  |             scenario.name | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let txouts = tx_graph | ||||||
|  |             .filter_chain_txouts( | ||||||
|  |                 &local_chain, | ||||||
|  |                 chain_tip, | ||||||
|  |                 spk_index.outpoints().iter().cloned(), | ||||||
|  |             ) | ||||||
|  |             .map(|(_, full_txout)| full_txout.outpoint) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         let exp_txouts = scenario | ||||||
|  |             .exp_chain_txouts | ||||||
|  |             .iter() | ||||||
|  |             .map(|(txid, vout)| OutPoint { | ||||||
|  |                 txid: *exp_tx_ids.get(txid).expect("txid must exist"), | ||||||
|  |                 vout: *vout, | ||||||
|  |             }) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         assert_eq!( | ||||||
|  |             txouts, exp_txouts, | ||||||
|  |             "\n[{}] 'filter_chain_txouts' failed", | ||||||
|  |             scenario.name | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let utxos = tx_graph | ||||||
|  |             .filter_chain_unspents( | ||||||
|  |                 &local_chain, | ||||||
|  |                 chain_tip, | ||||||
|  |                 spk_index.outpoints().iter().cloned(), | ||||||
|  |             ) | ||||||
|  |             .map(|(_, full_txout)| full_txout.outpoint) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         let exp_utxos = scenario | ||||||
|  |             .exp_unspents | ||||||
|  |             .iter() | ||||||
|  |             .map(|(txid, vout)| OutPoint { | ||||||
|  |                 txid: *exp_tx_ids.get(txid).expect("txid must exist"), | ||||||
|  |                 vout: *vout, | ||||||
|  |             }) | ||||||
|  |             .collect::<BTreeSet<_>>(); | ||||||
|  |         assert_eq!( | ||||||
|  |             utxos, exp_utxos, | ||||||
|  |             "\n[{}] 'filter_chain_unspents' failed", | ||||||
|  |             scenario.name | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let balance = tx_graph.balance( | ||||||
|  |             &local_chain, | ||||||
|  |             chain_tip, | ||||||
|  |             spk_index.outpoints().iter().cloned(), | ||||||
|  |             |_, spk: &Script| spk_index.index_of_spk(spk).is_some(), | ||||||
|  |         ); | ||||||
|  |         assert_eq!( | ||||||
|  |             balance, scenario.exp_balance, | ||||||
|  |             "\n[{}] 'balance' failed", | ||||||
|  |             scenario.name | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user