diff --git a/.github/ci-groups.yml b/.github/ci-groups.yml index d1ab4e8a2..1636a99e4 100644 --- a/.github/ci-groups.yml +++ b/.github/ci-groups.yml @@ -25,7 +25,6 @@ groups: - dashcore-rpc-json tools: - - dashcore-test-utils - dash-fuzz # Crates intentionally not tested (with reason) diff --git a/Cargo.toml b/Cargo.toml index 42b96ab0d..425fbeda4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi", "test-utils"] +members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi"] resolver = "2" [workspace.package] diff --git a/dash-spv-ffi/Cargo.toml b/dash-spv-ffi/Cargo.toml index 15714da06..e2a142f64 100644 --- a/dash-spv-ffi/Cargo.toml +++ b/dash-spv-ffi/Cargo.toml @@ -37,7 +37,6 @@ clap = { version = "4.5", features = ["derive"] } tempfile = "3.8" serial_test = "3.0" env_logger = "0.10" -dashcore-test-utils = { path = "../test-utils" } [build-dependencies] cbindgen = "0.29" diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index 36064d816..6f03bf239 100644 --- a/dash-spv/Cargo.toml +++ b/dash-spv/Cargo.toml @@ -59,13 +59,15 @@ hickory-resolver = "0.25" log = "0.4" [dev-dependencies] +dash-spv = { path = ".", features = ["test-utils"] } +dashcore = { path = "../dash", features = ["test-utils"] } +key-wallet-manager = { path = "../key-wallet-manager", features = ["test-utils"] } criterion = { version = "0.8.1", features = ["async_tokio"] } tempfile = "3.0" tokio-test = "0.4" env_logger = "0.10" hex = "0.4" test-case = "3.3" -dashcore-test-utils = { path = "../test-utils" } [[bench]] name = "storage" @@ -83,3 +85,4 @@ path = "src/lib.rs" [features] # Terminal UI feature (off by default, for use by binary only) terminal-ui = ["dep:crossterm"] +test-utils = [] diff --git a/dash-spv/src/chain/chain_tip.rs b/dash-spv/src/chain/chain_tip.rs index 4195b24bc..e3509c955 100644 --- a/dash-spv/src/chain/chain_tip.rs +++ b/dash-spv/src/chain/chain_tip.rs @@ -195,19 +195,6 @@ impl ChainTipManager { #[cfg(test)] mod tests { use super::*; - use dashcore::blockdata::constants::genesis_block; - use dashcore::Network; - - fn create_test_tip(height: u32, work_value: u8) -> ChainTip { - let mut header = genesis_block(Network::Dash).header; - header.nonce = height; // Make it unique - - let mut work_bytes = [0u8; 32]; - work_bytes[31] = work_value; - let chain_work = ChainWork::from_bytes(work_bytes); - - ChainTip::new(header, height, chain_work) - } #[test] fn test_tip_manager() { @@ -215,7 +202,7 @@ mod tests { // Add some tips with different work for i in 0..3 { - let tip = create_test_tip(i, i as u8); + let tip = ChainTip::dummy(i, i as u8); manager.add_tip(tip).expect("Failed to add tip"); } @@ -227,7 +214,7 @@ mod tests { assert!(active.is_active); // Add a tip with more work - let better_tip = create_test_tip(1, 10); + let better_tip = ChainTip::dummy(1, 10); manager.add_tip(better_tip).expect("Failed to add better tip"); // Active tip should update @@ -240,11 +227,11 @@ mod tests { let mut manager = ChainTipManager::new(2); // Fill to capacity - manager.add_tip(create_test_tip(1, 5)).expect("Failed to add first tip"); - manager.add_tip(create_test_tip(2, 10)).expect("Failed to add second tip"); + manager.add_tip(ChainTip::dummy(1, 5)).expect("Failed to add first tip"); + manager.add_tip(ChainTip::dummy(2, 10)).expect("Failed to add second tip"); // Adding another should evict the weakest - manager.add_tip(create_test_tip(3, 7)).expect("Failed to add third tip"); + manager.add_tip(ChainTip::dummy(3, 7)).expect("Failed to add third tip"); assert_eq!(manager.tip_count(), 2); @@ -258,18 +245,16 @@ mod tests { let mut manager = ChainTipManager::new(2); // Add two tips to fill capacity - let tip1 = create_test_tip(1, 5); + let tip1 = ChainTip::dummy(1, 5); let tip1_hash = tip1.hash; manager.add_tip(tip1).expect("Failed to add tip1"); - let tip2 = create_test_tip(2, 10); + let tip2 = ChainTip::dummy(2, 10); manager.add_tip(tip2).expect("Failed to add tip2"); // Extend tip1 successfully - since we remove tip1 first, there's room for the new tip - let new_header = create_test_tip(3, 6).header; - let mut work_bytes = [0u8; 32]; - work_bytes[31] = 7; // Give it some work value - let new_work = ChainWork::from_bytes(work_bytes); + let new_header = ChainTip::dummy(3, 6).header; + let new_work = ChainWork::dummy(7); // The extend operation should succeed let result = manager.extend_tip(&tip1_hash, new_header, new_work); @@ -295,14 +280,14 @@ mod tests { let mut manager = ChainTipManager::new(3); // Add three tips - let tip1 = create_test_tip(1, 5); + let tip1 = ChainTip::dummy(1, 5); let tip1_hash = tip1.hash; manager.add_tip(tip1).expect("Failed to add tip1"); - let tip2 = create_test_tip(2, 10); + let tip2 = ChainTip::dummy(2, 10); manager.add_tip(tip2).expect("Failed to add tip2"); - let tip3 = create_test_tip(3, 8); + let tip3 = ChainTip::dummy(3, 8); manager.add_tip(tip3).expect("Failed to add tip3"); // Verify initial state @@ -310,10 +295,8 @@ mod tests { assert!(manager.get_tip(&tip1_hash).is_some()); // Extend tip1 - this should work and be atomic - let new_header = create_test_tip(4, 6).header; - let mut work_bytes = [0u8; 32]; - work_bytes[31] = 6; - let new_work = ChainWork::from_bytes(work_bytes); + let new_header = ChainTip::dummy(4, 6).header; + let new_work = ChainWork::dummy(6); let result = manager.extend_tip(&tip1_hash, new_header, new_work); assert!(result.is_ok()); diff --git a/dash-spv/src/chain/chain_work.rs b/dash-spv/src/chain/chain_work.rs index f4135a6cc..3ca54c91c 100644 --- a/dash-spv/src/chain/chain_work.rs +++ b/dash-spv/src/chain/chain_work.rs @@ -155,10 +155,8 @@ mod tests { #[test] fn test_chain_work_comparison() { - let work1 = ChainWork::from_bytes([0u8; 32]); - let mut bytes2 = [0u8; 32]; - bytes2[31] = 1; - let work2 = ChainWork::from_bytes(bytes2); + let work1 = ChainWork::dummy(0); + let work2 = ChainWork::dummy(1); assert!(work1 < work2); assert!(work2 > work1); @@ -167,13 +165,8 @@ mod tests { #[test] fn test_chain_work_addition() { - let mut bytes1 = [0u8; 32]; - bytes1[31] = 100; - let work1 = ChainWork::from_bytes(bytes1); - - let mut bytes2 = [0u8; 32]; - bytes2[31] = 200; - let work2 = ChainWork::from_bytes(bytes2); + let work1 = ChainWork::dummy(100); + let work2 = ChainWork::dummy(200); let sum = work1.add(work2); assert_eq!(sum.work[31], 44); // 100 + 200 = 300, which is 44 + 256 @@ -189,13 +182,7 @@ mod tests { #[test] fn test_chain_work_ordering() { - let works: Vec = (0..5) - .map(|i| { - let mut bytes = [0u8; 32]; - bytes[31] = i; - ChainWork::from_bytes(bytes) - }) - .collect(); + let works: Vec = (0..5).map(ChainWork::dummy).collect(); for i in 0..4 { assert!(works[i] < works[i + 1]); diff --git a/dash-spv/src/chain/chainlock_test.rs b/dash-spv/src/chain/chainlock_test.rs index a8870472c..96f3b2cae 100644 --- a/dash-spv/src/chain/chainlock_test.rs +++ b/dash-spv/src/chain/chainlock_test.rs @@ -5,17 +5,7 @@ mod tests { storage::{BlockHeaderStorage, DiskStorageManager}, types::ChainState, }; - use dashcore::{constants::genesis_block, ChainLock, Network}; - use dashcore_test_utils::fixtures::test_block_hash; - - /// Create a test ChainLock with minimal valid data - fn create_test_chainlock(height: u32, block_hash: BlockHash) -> ChainLock { - ChainLock { - block_height: height, - block_hash, - signature: dashcore::bls_sig_utils::BLSSignature::from([0u8; 96]), // BLS signature placeholder - } - } + use dashcore::{Header, Network}; #[tokio::test] async fn test_chainlock_processing() { @@ -25,12 +15,7 @@ mod tests { let chainlock_manager = ChainLockManager::new(true); let chain_state = ChainState::new_for_network(Network::Testnet); - // Create a test ChainLock - let chainlock = ChainLock { - block_height: 1000, - block_hash: test_block_hash(1), - signature: dashcore::bls_sig_utils::BLSSignature::from([0; 96]), - }; + let chainlock = ChainLock::dummy(1000); // Process the ChainLock let result = chainlock_manager @@ -58,20 +43,15 @@ mod tests { let chainlock_manager = ChainLockManager::new(true); let chain_state = ChainState::new_for_network(Network::Testnet); - // Process first ChainLock at height 1000 - let chainlock1 = create_test_chainlock(1000, test_block_hash(1)); + let chainlock1 = ChainLock::dummy(1000); chainlock_manager .process_chain_lock(chainlock1.clone(), &chain_state, &mut storage) .await .expect("First ChainLock should process successfully"); - // Process second ChainLock at height 2000 - let chainlock2 = ChainLock { - block_height: 2000, - block_hash: test_block_hash(2), - signature: dashcore::bls_sig_utils::BLSSignature::from([1; 96]), - }; + let chainlock2 = ChainLock::dummy(2000); + chainlock_manager .process_chain_lock(chainlock2.clone(), &chain_state, &mut storage) .await @@ -95,11 +75,7 @@ mod tests { // Add ChainLocks at heights 1000, 2000, 3000 for height in [1000, 2000, 3000] { - let chainlock = ChainLock { - block_height: height, - block_hash: test_block_hash(height), - signature: dashcore::bls_sig_utils::BLSSignature::from([0; 96]), - }; + let chainlock = ChainLock::dummy(height); chainlock_manager .process_chain_lock(chainlock, &chain_state, &mut storage) .await @@ -119,9 +95,9 @@ mod tests { let chainlock_manager = ChainLockManager::new(true); // Queue multiple ChainLocks - let chain_lock1 = create_test_chainlock(100, BlockHash::from([1u8; 32])); - let chain_lock2 = create_test_chainlock(200, BlockHash::from([2u8; 32])); - let chain_lock3 = create_test_chainlock(300, BlockHash::from([3u8; 32])); + let chain_lock1 = ChainLock::dummy(100); + let chain_lock2 = ChainLock::dummy(200); + let chain_lock3 = ChainLock::dummy(300); chainlock_manager.queue_pending_chainlock(chain_lock1).unwrap(); chainlock_manager.queue_pending_chainlock(chain_lock2).unwrap(); @@ -145,11 +121,11 @@ mod tests { let chainlock_manager = ChainLockManager::new(true); // Add test headers - let genesis = genesis_block(Network::Dash).header; - storage.store_headers_at_height(&[genesis], 0).await.unwrap(); + let header = Header::dummy(0); + storage.store_headers_at_height(&[header], 0).await.unwrap(); // Create and process a ChainLock - let chain_lock = create_test_chainlock(0, genesis.block_hash()); + let chain_lock = ChainLock::dummy(0); let chain_state = ChainState::new(); let _ = chainlock_manager .process_chain_lock(chain_lock.clone(), &chain_state, &mut storage) @@ -162,7 +138,7 @@ mod tests { assert!(entry.is_some()); assert_eq!(entry.unwrap().chain_lock.block_height, 0); - let entry_by_hash = chainlock_manager.get_chain_lock_by_hash(&genesis.block_hash()); + let entry_by_hash = chainlock_manager.get_chain_lock_by_hash(&header.block_hash()); assert!(entry_by_hash.is_some()); assert_eq!(entry_by_hash.unwrap().chain_lock.block_height, 0); } diff --git a/dash-spv/src/chain/checkpoint_test.rs b/dash-spv/src/chain/checkpoint_test.rs index bdc96746f..f7d2d9268 100644 --- a/dash-spv/src/chain/checkpoint_test.rs +++ b/dash-spv/src/chain/checkpoint_test.rs @@ -3,74 +3,39 @@ #[cfg(test)] mod tests { use super::super::checkpoints::*; - use dashcore::{BlockHash, CompactTarget, Target}; - use dashcore_hashes::Hash; - use dashcore_test_utils::fixtures::test_block_hash; - - fn create_test_checkpoint(height: u32, timestamp: u32) -> Checkpoint { - let block_hash = test_block_hash(height); - let prev_blockhash = if height > 0 { - test_block_hash(height - 1) - } else { - BlockHash::all_zeros() - }; - - Checkpoint { - height, - block_hash, - prev_blockhash, - timestamp, - target: Target::from_compact(CompactTarget::from_consensus(0x1d00ffff)), - merkle_root: Some(block_hash), - chain_work: format!("0x{:064x}", height * 1000), - masternode_list_name: if height.is_multiple_of(100000) && height > 0 { - Some(format!("ML{}__70230", height)) - } else { - None - }, - protocol_version: None, - nonce: height * 123, - } - } + use dashcore::BlockHash; #[test] fn test_wallet_creation_time_checkpoint_selection() { - let checkpoints = vec![ - create_test_checkpoint(0, 1000000), // Jan 1970 - create_test_checkpoint(100000, 1500000000), // July 2017 - create_test_checkpoint(200000, 1600000000), // Sept 2020 - create_test_checkpoint(300000, 1700000000), // Nov 2023 - ]; + let checkpoints = + [1500000000, 1600000000, 1700000000, 1800000000].map(Checkpoint::dummy).to_vec(); let manager = CheckpointManager::new(checkpoints); // Test wallet created in 2019 - let wallet_time_2019 = 1550000000u32; + let wallet_time_2019 = 1550000000; let checkpoint = manager.get_sync_checkpoint(Some(wallet_time_2019)); - assert_eq!(checkpoint.unwrap().height, 100000); + assert_eq!(checkpoint.unwrap().height, 1500000000); // Test wallet created in 2022 - let wallet_time_2022 = 1650000000u32; + let wallet_time_2022 = 1650000000; let checkpoint = manager.get_sync_checkpoint(Some(wallet_time_2022)); - assert_eq!(checkpoint.unwrap().height, 200000); + assert_eq!(checkpoint.unwrap().height, 1600000000); // Test wallet created before any checkpoint - should return None - let wallet_time_ancient = 500000u32; + let wallet_time_ancient = 0; let checkpoint = manager.get_sync_checkpoint(Some(wallet_time_ancient)); assert!(checkpoint.is_none()); // Test no wallet creation time (should use latest) let checkpoint = manager.get_sync_checkpoint(None); - assert_eq!(checkpoint.unwrap().height, 300000); + assert_eq!(checkpoint.unwrap().height, 1800000000); } #[test] fn test_fork_rejection_logic() { - let checkpoints = vec![ - create_test_checkpoint(0, 1000000), - create_test_checkpoint(100000, 1500000000), - create_test_checkpoint(200000, 1600000000), - ]; + let checkpoints = + vec![Checkpoint::dummy(0), Checkpoint::dummy(100000), Checkpoint::dummy(200000)]; let manager = CheckpointManager::new(checkpoints.clone()); @@ -87,7 +52,7 @@ mod tests { #[test] fn test_checkpoint_protocol_version_extraction() { - let mut checkpoint = create_test_checkpoint(100000, 1500000000); + let mut checkpoint = Checkpoint::dummy(100000); // Test with masternode list name checkpoint.masternode_list_name = Some("ML100000__70227".to_string()); @@ -110,10 +75,7 @@ mod tests { #[test] fn test_checkpoint_binary_search_efficiency() { // Create many checkpoints to test binary search - let mut checkpoints = Vec::new(); - for i in 0..1000 { - checkpoints.push(create_test_checkpoint(i * 1000, 1000000 + i * 86400)); - } + let checkpoints = [0, 100, 5000, 90000, 999000, 1000000].map(Checkpoint::dummy).to_vec(); let manager = CheckpointManager::new(checkpoints.clone()); @@ -140,7 +102,7 @@ mod tests { #[test] fn test_checkpoint_validation_edge_cases() { - let checkpoints = vec![create_test_checkpoint(100000, 1500000000)]; + let checkpoints = vec![Checkpoint::dummy(100000)]; let manager = CheckpointManager::new(checkpoints.clone()); let correct_hash = manager.get_checkpoint(100000).unwrap().block_hash; @@ -159,10 +121,10 @@ mod tests { fn test_checkpoint_sorting_and_lookup() { // Create checkpoints in random order let checkpoints = vec![ - create_test_checkpoint(200000, 1600000000), - create_test_checkpoint(0, 1000000), - create_test_checkpoint(300000, 1700000000), - create_test_checkpoint(100000, 1500000000), + Checkpoint::dummy(200000), + Checkpoint::dummy(0), + Checkpoint::dummy(300000), + Checkpoint::dummy(100000), ]; let manager = CheckpointManager::new(checkpoints.clone()); diff --git a/dash-spv/src/client/message_handler_test.rs b/dash-spv/src/client/message_handler_test.rs index cdbc7de87..63ef8b434 100644 --- a/dash-spv/src/client/message_handler_test.rs +++ b/dash-spv/src/client/message_handler_test.rs @@ -3,9 +3,9 @@ #[cfg(test)] mod tests { use crate::client::{ClientConfig, MessageHandler}; - use crate::network::mock::MockNetworkManager; use crate::storage::DiskStorageManager; use crate::sync::SyncManager; + use crate::test_utils::MockNetworkManager; use crate::types::{MempoolState, SpvEvent, SpvStats}; use crate::ChainState; use dashcore::block::Header as BlockHeader; diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index ec6432b43..bd6be889d 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -64,9 +64,9 @@ mod message_handler_test; #[cfg(test)] mod tests { use super::{ClientConfig, DashSpvClient}; - use crate::types::UnconfirmedTransaction; - use crate::{network::mock::MockNetworkManager, storage::DiskStorageManager}; - use dashcore::{Amount, Network, Transaction, TxOut}; + use crate::storage::DiskStorageManager; + use crate::{test_utils::MockNetworkManager, types::UnconfirmedTransaction}; + use dashcore::{Address, Amount, Network, Transaction, TxOut}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; @@ -121,6 +121,8 @@ mod tests { DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let test_address = Address::dummy(config.network, 0); + let mut client = DashSpvClient::new(config, network_manager, storage, wallet) .await .expect("client construction must succeed"); @@ -131,13 +133,6 @@ mod tests { .await .expect("enable mempool tracking must succeed"); - // Create a test address (testnet address to match Network::Testnet config) - let test_address_str = "yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr"; - let test_address = test_address_str - .parse::>() - .expect("valid address") - .assume_checked(); - // Create a transaction that sends 10 Dash to the test address let tx = Transaction { version: 2, diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index b7d8cc2fe..ca9ac22a4 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -57,6 +57,9 @@ //! - **Persistent storage**: Save and restore state between runs //! - **Extensive logging**: Built-in tracing support for debugging +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + pub mod chain; pub mod client; pub mod error; diff --git a/dash-spv/src/mempool_filter.rs b/dash-spv/src/mempool_filter.rs index e50098c73..fc92161e5 100644 --- a/dash-spv/src/mempool_filter.rs +++ b/dash-spv/src/mempool_filter.rs @@ -184,170 +184,9 @@ impl MempoolFilter { #[cfg(test)] mod tests { use super::*; - use dashcore::{Network, OutPoint, ScriptBuf, TxIn, TxOut, Witness}; + use dashcore::Network; use std::str::FromStr; - // Stub types for ignored tests - #[derive(Clone)] - enum WatchItem { - Address(Address), - // Keep placeholders as unit to avoid dead_code warnings - Script(()), - Outpoint(()), - } - - impl WatchItem { - fn address(addr: Address) -> Self { - WatchItem::Address(addr) - } - - fn address_from_height(addr: Address, _height: u32) -> Self { - WatchItem::Address(addr) - } - - fn script(_script: ScriptBuf) -> Self { - WatchItem::Script(()) - } - - fn outpoint(_outpoint: OutPoint) -> Self { - WatchItem::Outpoint(()) - } - } - - struct MockWallet { - watched_addresses: HashSet
, - } - - impl MockWallet { - fn new(_network: Network) -> Self { - Self { - watched_addresses: HashSet::new(), - } - } - - fn add_watched_address(&mut self, address: Address) { - self.watched_addresses.insert(address); - } - - // Accessor omitted; tests use add_watched_address directly - } - - // Helper to create deterministically generated test addresses - fn create_test_addresses(network: Network, count: usize) -> Vec
{ - use dashcore::hashes::{sha256, Hash}; - use dashcore::{secp256k1, PrivateKey, PublicKey}; - - let mut addresses = Vec::new(); - let secp = secp256k1::Secp256k1::new(); - - // Fixed seed for deterministic generation - let seed = b"dash-spv-test-seed"; - - let mut index: u64 = 0; - while addresses.len() < count { - // Create deterministic secret: SHA256(fixed_seed + index) - let mut data = seed.to_vec(); - data.extend_from_slice(&index.to_le_bytes()); - - let secret_bytes = sha256::Hash::hash(&data).to_byte_array(); - - // Try to create secp256k1 SecretKey from the hash - match secp256k1::SecretKey::from_byte_array(&secret_bytes) { - Ok(secret_key) => { - // Create PrivateKey from SecretKey - let private_key = PrivateKey::new(secret_key, network); - - // Create PublicKey from PrivateKey - let public_key = PublicKey::from_private_key(&secp, &private_key); - - // Create P2PKH address from PublicKey - let address = Address::p2pkh(&public_key, network); - - addresses.push(address); - } - Err(_) => { - // Skip this index if key derivation fails - tracing::trace!("Skipping index {}: invalid secret key", index); - } - } - - index += 1; - } - - addresses - } - - // Test function to verify deterministic address generation - #[cfg(test)] - fn test_deterministic_addresses() { - use dashcore::{AddressType, Network}; - - // Generate addresses twice with the same parameters - let addresses1 = create_test_addresses(Network::Dash, 3); - let addresses2 = create_test_addresses(Network::Dash, 3); - - // Verify they are identical - assert_eq!(addresses1.len(), 3); - assert_eq!(addresses2.len(), 3); - assert_eq!(addresses1, addresses2); - - // Verify they are valid P2PKH addresses - for addr in &addresses1 { - assert_eq!(addr.address_type(), Some(AddressType::P2pkh)); - assert_eq!(*addr.network(), Network::Dash); - } - - // Test with different networks - let testnet_addresses = create_test_addresses(Network::Testnet, 2); - assert_eq!(testnet_addresses.len(), 2); - for addr in &testnet_addresses { - assert_eq!(addr.address_type(), Some(AddressType::P2pkh)); - assert_eq!(*addr.network(), Network::Testnet); - } - - println!("Deterministic address generation test passed!"); - println!("Dash addresses: {:?}", addresses1); - println!("Testnet addresses: {:?}", testnet_addresses); - } - - // Helper to create a test transaction - fn create_test_transaction(outputs: Vec<(Address, u64)>, inputs: Vec) -> Transaction { - let mut tx_outputs = vec![]; - for (addr, amount) in outputs { - tx_outputs.push(TxOut { - value: amount, - script_pubkey: addr.script_pubkey(), - }); - } - - let mut tx_inputs = vec![]; - for outpoint in inputs { - tx_inputs.push(TxIn { - previous_output: outpoint, - script_sig: ScriptBuf::new(), - sequence: 0xffffffff, - witness: Witness::new(), - }); - } - - Transaction { - version: 1, - lock_time: 0, - input: tx_inputs, - output: tx_outputs, - special_transaction_payload: None, - } - } - - // Stub implementations for ignored tests - fn test_address(network: Network) -> Address { - create_test_addresses(network, 1)[0].clone() - } - - fn test_address2(network: Network) -> Address { - create_test_addresses(network, 2)[1].clone() - } - #[tokio::test] async fn test_fetch_all_strategy() { let mempool_state = Arc::new(RwLock::new(MempoolState::default())); @@ -409,10 +248,8 @@ mod tests { async fn test_is_transaction_relevant_with_address() { let network = Network::Dash; - // Create a wallet and get addresses from it - let addresses = create_test_addresses(network, 2); - let addr1 = &addresses[0]; - let addr2 = &addresses[1]; + let addr1 = Address::dummy(network, 0); + let addr2 = Address::dummy(network, 1); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); let watched_addresses = vec![addr1.clone()].into_iter().collect(); @@ -426,11 +263,11 @@ mod tests { ); // Transaction sending to watched address should be relevant - let tx1 = create_test_transaction(vec![(addr1.clone(), 50000)], vec![]); + let tx1 = Transaction::dummy(&addr1.clone(), 0..0, &[50000]); assert!(filter.is_transaction_relevant(&tx1)); // Transaction sending to unwatched address should not be relevant - let tx2 = create_test_transaction(vec![(addr2.clone(), 50000)], vec![]); + let tx2 = Transaction::dummy(&addr2.clone(), 0..0, &[50000]); assert!(!filter.is_transaction_relevant(&tx2)); } @@ -438,13 +275,11 @@ mod tests { async fn test_is_transaction_relevant_with_script() { let network = Network::Dash; - // Create a wallet and get addresses from it - let addresses = create_test_addresses(network, 2); - let addr = &addresses[0]; - let addr2 = &addresses[1]; + let addr1 = Address::dummy(network, 0); + let addr2 = Address::dummy(network, 1); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); - let watched_addresses = vec![addr.clone()].into_iter().collect(); + let watched_addresses = vec![addr1.clone()].into_iter().collect(); let filter = MempoolFilter::new( MempoolStrategy::FetchAll, @@ -455,11 +290,11 @@ mod tests { ); // Transaction with watched script should be relevant - let tx = create_test_transaction(vec![(addr.clone(), 50000)], vec![]); + let tx = Transaction::dummy(&addr1.clone(), 0..0, &[50000]); assert!(filter.is_transaction_relevant(&tx)); // Transaction without watched script should not be relevant - let tx2 = create_test_transaction(vec![(addr2.clone(), 50000)], vec![]); + let tx2 = Transaction::dummy(&addr2.clone(), 0..0, &[50000]); assert!(!filter.is_transaction_relevant(&tx2)); } @@ -467,12 +302,10 @@ mod tests { async fn test_is_transaction_relevant_with_outpoint() { let network = Network::Dash; - // Create a wallet and get an address from it - let addresses = create_test_addresses(network, 1); - let addr = &addresses[0]; + let addr1 = Address::dummy(network, 0); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); - let watched_addresses = vec![addr.clone()].into_iter().collect(); + let watched_addresses = vec![addr1.clone()].into_iter().collect(); let filter = MempoolFilter::new( MempoolStrategy::FetchAll, @@ -483,19 +316,13 @@ mod tests { ); // Transaction receiving to watched address should be relevant - let tx = create_test_transaction(vec![(addr.clone(), 50000)], vec![]); + let tx = Transaction::dummy(&addr1.clone(), 0..0, &[50000]); assert!(filter.is_transaction_relevant(&tx)); // Transaction not involving watched address should not be relevant // Create a completely different address not in our watched list - let other_addr = { - // Create from script to ensure it's different from watched addresses - use dashcore::script::ScriptBuf; - let script = - ScriptBuf::from_hex("76a914123456789012345678901234567890123456789088ac").unwrap(); - Address::from_script(&script, network).unwrap() - }; - let tx2 = create_test_transaction(vec![(other_addr, 50000)], vec![]); + let addr2 = Address::dummy(network, 1); + let tx2 = Transaction::dummy(&addr2, 0..0, &[50000]); assert!(!filter.is_transaction_relevant(&tx2)); } @@ -623,11 +450,6 @@ mod tests { assert!(pruned.is_empty() || !pruned.is_empty()); // Tautology, but shows the test ran } - #[test] - fn test_deterministic_address_generation() { - test_deterministic_addresses(); - } - #[tokio::test] async fn test_bloom_filter_strategy() { let mempool_state = Arc::new(RwLock::new(MempoolState::default())); @@ -647,20 +469,14 @@ mod tests { } #[tokio::test] - #[ignore = "requires MockWallet implementation"] async fn test_address_with_earliest_height() { let network = Network::Dash; - let addr = test_address(network); + let addr1 = Address::dummy(network, 0); + let addr2 = Address::dummy(network, 1); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); - let watch_items = vec![WatchItem::address_from_height(addr.clone(), 100000)]; - let watched_addresses: HashSet
= watch_items - .into_iter() - .filter_map(|item| match item { - WatchItem::Address(addr) => Some(addr), - _ => None, - }) - .collect(); + let mut watched_addresses: HashSet
= HashSet::new(); + watched_addresses.insert(addr1.clone()); let filter = MempoolFilter::new( MempoolStrategy::FetchAll, @@ -670,73 +486,16 @@ mod tests { network, ); - let mut wallet = MockWallet::new(network); - wallet.add_watched_address(addr.clone()); - // Transaction to watched address should still be relevant - let tx = create_test_transaction(vec![(addr.clone(), 50000)], vec![]); + let tx = Transaction::dummy(&addr1.clone(), 0..0, &[50000]); assert!(filter.is_transaction_relevant(&tx)); - } - - #[tokio::test] - #[ignore = "requires MockWallet implementation"] - async fn test_multiple_watch_items() { - let network = Network::Dash; - let addr1 = test_address(network); - let addr2 = test_address2(network); - - let mempool_state = Arc::new(RwLock::new(MempoolState::default())); - let dummy_script = ScriptBuf::new(); - let dummy_outpoint = OutPoint { - txid: Txid::from_str( - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - ) - .unwrap(), - vout: 0, - }; - let watch_items = vec![ - WatchItem::address(addr1.clone()), - WatchItem::script(dummy_script), - WatchItem::outpoint(dummy_outpoint), - ]; - let watched_addresses: HashSet
= watch_items - .into_iter() - .filter_map(|item| match item { - WatchItem::Address(addr) => Some(addr), - _ => None, - }) - .collect(); - - let filter = MempoolFilter::new( - MempoolStrategy::FetchAll, - 1000, - mempool_state, - watched_addresses, - network, - ); - - let mut wallet = MockWallet::new(network); - wallet.add_watched_address(addr1.clone()); - - // Transaction matching any watch item should be relevant - - // Match by address - let tx1 = create_test_transaction(vec![(addr1.clone(), 1000)], vec![]); - assert!(filter.is_transaction_relevant(&tx1)); // TODO: Match by outpoint - requires OutPoint to be stored in WatchItem::Outpoint variant - // let tx2 = create_test_transaction(vec![(addr2.clone(), 2000)], vec![outpoint]); + // let tx2 = Transaction::dummy_with_address(&addr2.clone(), vec![outpoint]); // assert!(filter.is_transaction_relevant(&tx2)); // No match - let other_outpoint = OutPoint { - txid: Txid::from_str( - "5858585858585858585858585858585858585858585858585858585858585858", - ) - .unwrap(), - vout: 0, - }; - let tx3 = create_test_transaction(vec![(addr2, 3000)], vec![other_outpoint]); + let tx3 = Transaction::dummy(&addr2, 0..1, &[50000]); assert!(!filter.is_transaction_relevant(&tx3)); } } diff --git a/dash-spv/src/network/mod.rs b/dash-spv/src/network/mod.rs index 2da61f6f9..7f8409773 100644 --- a/dash-spv/src/network/mod.rs +++ b/dash-spv/src/network/mod.rs @@ -13,9 +13,6 @@ pub mod reputation; #[cfg(test)] mod tests; -#[cfg(test)] -pub mod mock; - use async_trait::async_trait; use crate::error::NetworkResult; diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 6c212acbf..a67d2ec98 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -393,32 +393,8 @@ mod tests { use crate::ChainState; use super::*; - use dashcore::{block::Version, pow::CompactTarget, BlockHash, Header as BlockHeader}; - use dashcore_hashes::Hash; - use tempfile::TempDir; - - fn build_headers(count: usize) -> Vec { - let mut headers = Vec::with_capacity(count); - let mut prev_hash = BlockHash::from_byte_array([0u8; 32]); - - for i in 0..count { - let header = BlockHeader { - version: Version::from_consensus(1), - prev_blockhash: prev_hash, - merkle_root: dashcore::hashes::sha256d::Hash::from_byte_array( - [(i % 255) as u8; 32], - ) - .into(), - time: 1 + i as u32, - bits: CompactTarget::from_consensus(0x1d00ffff), - nonce: i as u32, - }; - prev_hash = header.block_hash(); - headers.push(header); - } - - headers - } + use dashcore::Header as BlockHeader; + use tempfile::{tempdir, TempDir}; #[tokio::test] async fn test_load_headers() -> Result<(), Box> { @@ -429,14 +405,7 @@ mod tests { .expect("Unable to create storage"); // Create a test header - let test_header = BlockHeader { - version: Version::from_consensus(1), - prev_blockhash: BlockHash::from_byte_array([1; 32]), - merkle_root: dashcore::hashes::sha256d::Hash::from_byte_array([2; 32]).into(), - time: 12345, - bits: CompactTarget::from_consensus(0x1d00ffff), - nonce: 67890, - }; + let test_header = BlockHeader::dummy(1); // Store just one header storage.store_headers(&[test_header]).await?; @@ -452,24 +421,12 @@ mod tests { #[tokio::test] async fn test_checkpoint_storage_indexing() -> StorageResult<()> { - use dashcore::TxMerkleNode; - use tempfile::tempdir; - let temp_dir = tempdir().expect("Failed to create temp dir"); let mut storage = DiskStorageManager::new(temp_dir.path().to_path_buf()).await?; // Create test headers starting from checkpoint height let checkpoint_height = 1_100_000; - let headers: Vec = (0..100) - .map(|i| BlockHeader { - version: Version::from_consensus(1), - prev_blockhash: BlockHash::from_byte_array([i as u8; 32]), - merkle_root: TxMerkleNode::from_byte_array([(i + 1) as u8; 32]), - time: 1234567890 + i, - bits: CompactTarget::from_consensus(0x1a2b3c4d), - nonce: 67890 + i, - }) - .collect(); + let headers = BlockHeader::dummy_batch(checkpoint_height..checkpoint_height + 100); let mut base_state = ChainState::new(); base_state.sync_base_height = checkpoint_height; @@ -544,7 +501,7 @@ mod tests { async fn test_shutdown_flushes_index() -> Result<(), Box> { let temp_dir = TempDir::new()?; let base_path = temp_dir.path().to_path_buf(); - let headers = build_headers(11_000); + let headers = BlockHeader::dummy_batch(0..11_000); let last_hash = headers.last().unwrap().block_hash(); { diff --git a/dash-spv/src/storage/segments.rs b/dash-spv/src/storage/segments.rs index 905b63cbf..d98b55d96 100644 --- a/dash-spv/src/storage/segments.rs +++ b/dash-spv/src/storage/segments.rs @@ -495,24 +495,10 @@ impl Segment { #[cfg(test)] mod tests { - use dashcore_hashes::Hash; use tempfile::TempDir; use super::*; - trait TestStruct { - fn new_test(id: u32) -> Self; - } - - impl TestStruct for FilterHeader { - fn new_test(id: u32) -> Self { - let mut bytes = [0u8; 32]; - bytes[0] = 1; - bytes[1..5].copy_from_slice(&id.to_le_bytes()); - FilterHeader::from_raw_hash(dashcore_hashes::sha256d::Hash::from_byte_array(bytes)) - } - } - #[tokio::test] async fn test_segment_cache_eviction() { let tmp_dir = TempDir::new().unwrap(); @@ -533,7 +519,7 @@ mod tests { let segment = cache.get_segment_mut(&i).await.expect("Failed to create a new segment"); assert!(segment.state == SegmentState::Dirty); - segment.insert(FilterHeader::new_test(i), 0); + segment.insert(FilterHeader::dummy(i), 0); } for i in 0..=MAX_SEGMENTS { @@ -541,7 +527,7 @@ mod tests { let segment = cache.get_segment_mut(&i).await.expect("Failed to load segment"); - assert_eq!(segment.get(0..1), [FilterHeader::new_test(i)]); + assert_eq!(segment.get(0..1), [FilterHeader::dummy(i)]); } } @@ -549,7 +535,7 @@ mod tests { async fn test_segment_cache_persist_load() { let tmp_dir = TempDir::new().unwrap(); - let items: Vec<_> = (0..10).map(FilterHeader::new_test).collect(); + let items = FilterHeader::dummy_batch(0..10); let mut cache = SegmentCache::::load_or_new(tmp_dir.path()) .await @@ -579,25 +565,7 @@ mod tests { .await .expect("Failed to create new segment_cache"); - let items: Vec<_> = (0..ITEMS_PER_SEGMENT * 2 + ITEMS_PER_SEGMENT / 2) - .map(FilterHeader::new_test) - .collect(); - - cache.store_items(&items).await.expect("Failed to store items"); - - assert_eq!( - items[0..ITEMS_PER_SEGMENT as usize], - cache.get_items(0..ITEMS_PER_SEGMENT).await.expect("Failed to get items") - ); - - assert_eq!( - items[0..(ITEMS_PER_SEGMENT - 1) as usize], - cache.get_items(0..ITEMS_PER_SEGMENT - 1).await.expect("Failed to get items") - ); - - let items: Vec<_> = (0..ITEMS_PER_SEGMENT * 2 + ITEMS_PER_SEGMENT / 2) - .map(FilterHeader::new_test) - .collect(); + let items = FilterHeader::dummy_batch(0..ITEMS_PER_SEGMENT * 2 + ITEMS_PER_SEGMENT / 2); cache.store_items(&items).await.expect("Failed to store items"); @@ -626,7 +594,7 @@ mod tests { const MAX_ITEMS: u32 = Segment::::ITEMS_PER_SEGMENT; // Testing with half full segment - let items: Vec<_> = (0..MAX_ITEMS / 2).map(FilterHeader::new_test).collect(); + let items = FilterHeader::dummy_batch(0..MAX_ITEMS / 2); let mut segment = Segment::new(segment_id, items.clone(), SegmentState::Dirty); assert_eq!(segment.first_valid_offset(), Some(0)); @@ -634,7 +602,7 @@ mod tests { assert_eq!(segment.get(0..MAX_ITEMS / 2), &items[0..MAX_ITEMS as usize / 2]); assert_eq!( segment.get(MAX_ITEMS / 2 - 1..MAX_ITEMS / 2), - [FilterHeader::new_test(MAX_ITEMS / 2 - 1)] + [FilterHeader::dummy(MAX_ITEMS / 2 - 1)] ); assert_eq!(segment.state, SegmentState::Dirty); @@ -649,7 +617,7 @@ mod tests { assert_eq!(loaded_segment.get(0..MAX_ITEMS / 2), &items[0..MAX_ITEMS as usize / 2]); assert_eq!( loaded_segment.get(MAX_ITEMS / 2 - 1..MAX_ITEMS / 2), - [FilterHeader::new_test(MAX_ITEMS / 2 - 1)] + [FilterHeader::dummy(MAX_ITEMS / 2 - 1)] ); } @@ -657,17 +625,17 @@ mod tests { fn test_segment_insert_get() { let segment_id = 10; - let items = (0..10).map(FilterHeader::new_test).collect(); + let items = FilterHeader::dummy_batch(0..10); let mut segment = Segment::new(segment_id, items, SegmentState::Dirty); assert_eq!(segment.first_valid_offset(), Some(0)); assert_eq!(segment.last_valid_offset(), Some(9)); - assert_eq!(segment.get(0..10), &(0..10).map(FilterHeader::new_test).collect::>()); + assert_eq!(segment.get(0..10), &(0..10).map(FilterHeader::dummy).collect::>()); - segment.insert(FilterHeader::new_test(10), 10); + segment.insert(FilterHeader::dummy(10), 10); assert_eq!(segment.last_valid_offset(), Some(10)); - assert_eq!(segment.get(0..11), &(0..11).map(FilterHeader::new_test).collect::>()); + assert_eq!(segment.get(0..11), &(0..11).map(FilterHeader::dummy).collect::>()); } } diff --git a/dash-spv/src/test_utils/chain_tip.rs b/dash-spv/src/test_utils/chain_tip.rs new file mode 100644 index 000000000..8890071df --- /dev/null +++ b/dash-spv/src/test_utils/chain_tip.rs @@ -0,0 +1,12 @@ +use dashcore::Header; + +use crate::chain::{ChainTip, ChainWork}; + +impl ChainTip { + pub fn dummy(height: u32, work_value: u8) -> ChainTip { + let header = Header::dummy(height); + let chain_work = ChainWork::dummy(work_value); + + ChainTip::new(header, height, chain_work) + } +} diff --git a/dash-spv/src/test_utils/chain_work.rs b/dash-spv/src/test_utils/chain_work.rs new file mode 100644 index 000000000..9603fb2f2 --- /dev/null +++ b/dash-spv/src/test_utils/chain_work.rs @@ -0,0 +1,9 @@ +use crate::chain::ChainWork; + +impl ChainWork { + pub fn dummy(work_value: u8) -> ChainWork { + let mut work_bytes = [0u8; 32]; + work_bytes[31] = work_value; + ChainWork::from_bytes(work_bytes) + } +} diff --git a/dash-spv/src/test_utils/checkpoint.rs b/dash-spv/src/test_utils/checkpoint.rs new file mode 100644 index 000000000..c9d11b433 --- /dev/null +++ b/dash-spv/src/test_utils/checkpoint.rs @@ -0,0 +1,26 @@ +use dashcore::{CompactTarget, Header, Target}; + +use crate::chain::Checkpoint; + +impl Checkpoint { + pub fn dummy(height: u32) -> Checkpoint { + let block_header = Header::dummy(height); + + Checkpoint { + height, + block_hash: block_header.block_hash(), + prev_blockhash: block_header.prev_blockhash, + timestamp: block_header.time, + target: Target::from_compact(CompactTarget::from_consensus(0x1d00ffff)), + merkle_root: Some(block_header.block_hash()), + chain_work: format!("0x{:064x}", height.wrapping_mul(1000)), + masternode_list_name: if height.is_multiple_of(100000) && height > 0 { + Some(format!("ML{}__70230", height)) + } else { + None + }, + protocol_version: None, + nonce: block_header.nonce, + } + } +} diff --git a/dash-spv/src/test_utils/filter.rs b/dash-spv/src/test_utils/filter.rs new file mode 100644 index 000000000..f4be5eb4f --- /dev/null +++ b/dash-spv/src/test_utils/filter.rs @@ -0,0 +1,13 @@ +use dashcore::Header; + +use crate::FilterMatch; + +impl FilterMatch { + pub fn dummy(height: u32) -> Self { + FilterMatch { + block_hash: Header::dummy(height).block_hash(), + height, + block_requested: false, + } + } +} diff --git a/dash-spv/src/test_utils/mod.rs b/dash-spv/src/test_utils/mod.rs new file mode 100644 index 000000000..a340983fa --- /dev/null +++ b/dash-spv/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +mod chain_tip; +mod chain_work; +mod checkpoint; +mod filter; +mod network; + +pub use network::MockNetworkManager; diff --git a/dash-spv/src/network/mock.rs b/dash-spv/src/test_utils/network.rs similarity index 98% rename from dash-spv/src/network/mock.rs rename to dash-spv/src/test_utils/network.rs index f3278ec60..66ce349fc 100644 --- a/dash-spv/src/network/mock.rs +++ b/dash-spv/src/test_utils/network.rs @@ -1,5 +1,3 @@ -//! Mock network manager for testing - use std::any::Any; use std::collections::VecDeque; @@ -11,10 +9,9 @@ use dashcore::{ use dashcore_hashes::Hash; use crate::error::{NetworkError, NetworkResult}; +use crate::network::NetworkManager; use crate::types::PeerInfo; -use super::NetworkManager; - /// Mock network manager for testing pub struct MockNetworkManager { connected: bool, @@ -26,7 +23,7 @@ impl MockNetworkManager { /// Create a new mock network manager pub fn new() -> Self { Self { - connected: false, + connected: true, messages: VecDeque::new(), headers_chain: Vec::new(), } diff --git a/dash-spv/src/validation/instantlock.rs b/dash-spv/src/validation/instantlock.rs index dd5fbfeb9..0af3b4f9d 100644 --- a/dash-spv/src/validation/instantlock.rs +++ b/dash-spv/src/validation/instantlock.rs @@ -119,79 +119,12 @@ impl InstantLockValidator { #[cfg(test)] mod tests { use super::*; - use dashcore::blockdata::constants::COIN_VALUE; - use dashcore::{OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; - use dashcore_hashes::{sha256d, Hash}; - - /// Helper to create a test transaction - fn create_test_transaction(inputs: Vec<(sha256d::Hash, u32)>, value: u64) -> Transaction { - let tx_ins = inputs - .into_iter() - .map(|(txid, vout)| TxIn { - previous_output: OutPoint { - txid: dashcore::Txid::from_raw_hash(txid), - vout, - }, - script_sig: ScriptBuf::new(), - sequence: 0xffffffff, - witness: dashcore::Witness::new(), - }) - .collect(); - - let tx_outs = vec![TxOut { - value, - script_pubkey: ScriptBuf::new(), - }]; - - Transaction { - version: 2, - lock_time: 0, - input: tx_ins, - output: tx_outs, - special_transaction_payload: None, - } - } - - /// Helper to create a test InstantLock - fn create_test_instant_lock(tx: &Transaction) -> InstantLock { - let inputs = tx.input.iter().map(|input| input.previous_output).collect(); - - InstantLock { - version: 1, - inputs, - txid: tx.txid(), - signature: dashcore::bls_sig_utils::BLSSignature::from([1; 96]), - cyclehash: dashcore::BlockHash::from_byte_array([0; 32]), - } - } - - /// Helper to create an InstantLock with specific inputs - fn create_instant_lock_with_inputs( - txid: sha256d::Hash, - inputs: Vec<(sha256d::Hash, u32)>, - ) -> InstantLock { - let inputs = inputs - .into_iter() - .map(|(txid, vout)| OutPoint { - txid: dashcore::Txid::from_raw_hash(txid), - vout, - }) - .collect(); - - InstantLock { - version: 1, - inputs, - txid: dashcore::Txid::from_raw_hash(txid), - signature: dashcore::bls_sig_utils::BLSSignature::from([1; 96]), - cyclehash: dashcore::BlockHash::from_byte_array([0; 32]), - } - } + use dashcore_hashes::Hash; #[test] fn test_valid_instantlock() { let validator = InstantLockValidator::new(); - let tx = create_test_transaction(vec![(sha256d::Hash::hash(&[1, 2, 3]), 0)], COIN_VALUE); - let is_lock = create_test_instant_lock(&tx); + let is_lock = InstantLock::dummy(0..3); // Structural validation only (for testing) assert!(validator.validate_structure(&is_lock).is_ok()); @@ -200,10 +133,7 @@ mod tests { #[test] fn test_empty_inputs() { let validator = InstantLockValidator::new(); - let mut is_lock = create_instant_lock_with_inputs( - sha256d::Hash::hash(&[1, 2, 3]), - vec![(sha256d::Hash::hash(&[4, 5, 6]), 0)], - ); + let mut is_lock = InstantLock::dummy(0..3); is_lock.inputs.clear(); let result = validator.validate_structure(&is_lock); @@ -214,10 +144,7 @@ mod tests { #[test] fn test_empty_signature() { let validator = InstantLockValidator::new(); - let mut is_lock = create_instant_lock_with_inputs( - sha256d::Hash::hash(&[1, 2, 3]), - vec![(sha256d::Hash::hash(&[4, 5, 6]), 0)], - ); + let mut is_lock = InstantLock::dummy(0..3); is_lock.signature = dashcore::bls_sig_utils::BLSSignature::from([0; 96]); // Zero signatures should be rejected as invalid structure @@ -229,10 +156,7 @@ mod tests { #[test] fn test_null_txid() { let validator = InstantLockValidator::new(); - let mut is_lock = create_instant_lock_with_inputs( - sha256d::Hash::hash(&[1, 2, 3]), - vec![(sha256d::Hash::hash(&[4, 5, 6]), 0)], - ); + let mut is_lock = InstantLock::dummy(0..3); is_lock.txid = dashcore::Txid::all_zeros(); // Null txid should be rejected as invalid structure @@ -244,10 +168,7 @@ mod tests { #[test] fn test_null_input_txid() { let validator = InstantLockValidator::new(); - let mut is_lock = create_instant_lock_with_inputs( - sha256d::Hash::hash(&[1, 2, 3]), - vec![(sha256d::Hash::hash(&[4, 5, 6]), 0), (sha256d::Hash::hash(&[7, 8, 9]), 1)], - ); + let mut is_lock = InstantLock::dummy(0..3); // Set the second input to have a null txid is_lock.inputs[1].txid = dashcore::Txid::all_zeros(); @@ -267,15 +188,14 @@ mod tests { #[test] fn test_request_id_computation() { - let tx = create_test_transaction(vec![(sha256d::Hash::hash(&[1, 2, 3]), 0)], COIN_VALUE); - let is_lock = create_test_instant_lock(&tx); + let is_lock = InstantLock::dummy(0..3); // Verify request ID can be computed let request_id = is_lock.request_id(); assert!(request_id.is_ok()); // Same inputs should produce same request ID - let is_lock2 = create_test_instant_lock(&tx); + let is_lock2 = InstantLock::dummy(0..3); let request_id2 = is_lock2.request_id(); assert!(request_id2.is_ok()); assert_eq!(request_id.unwrap(), request_id2.unwrap()); @@ -286,11 +206,7 @@ mod tests { let validator = InstantLockValidator::new(); // Create lock with many inputs - let many_inputs: Vec<(sha256d::Hash, u32)> = - (0..100u32).map(|i| (sha256d::Hash::hash(&i.to_le_bytes()), i % 10)).collect(); - - let lock = - create_instant_lock_with_inputs(sha256d::Hash::hash(&[100, 101, 102]), many_inputs); + let lock = InstantLock::dummy(0..100); assert!(validator.validate_structure(&lock).is_ok()); } diff --git a/dash-spv/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index e5014621b..f797520c3 100644 --- a/dash-spv/tests/block_download_test.rs +++ b/dash-spv/tests/block_download_test.rs @@ -1,118 +1,16 @@ //! Tests for block downloading on filter match functionality. +use dash_spv::test_utils::MockNetworkManager; use std::collections::HashSet; use std::sync::Arc; use tempfile::TempDir; use tokio::sync::Mutex; -use tokio::sync::RwLock; - -use dashcore::{ - block::{Block, Header as BlockHeader, Version}, - network::message::NetworkMessage, - network::message_blockdata::Inventory, - pow::CompactTarget, - BlockHash, -}; -use dashcore_hashes::Hash; + +use dashcore::block::Block; use dash_spv::{ - client::ClientConfig, network::NetworkManager, storage::DiskStorageManager, - sync::FilterSyncManager, types::FilterMatch, + client::ClientConfig, storage::DiskStorageManager, sync::FilterSyncManager, types::FilterMatch, }; -// use key_wallet::wallet::ManagedWalletInfo; - -/// Mock network manager for testing -struct MockNetworkManager { - sent_messages: Arc>>, - received_messages: Arc>>, - connected: bool, -} - -impl MockNetworkManager { - fn new() -> Self { - Self { - sent_messages: Arc::new(RwLock::new(Vec::new())), - received_messages: Arc::new(RwLock::new(Vec::new())), - connected: true, - } - } - - async fn get_sent_messages(&self) -> Vec { - self.sent_messages.read().await.clone() - } -} - -#[async_trait::async_trait] -impl NetworkManager for MockNetworkManager { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - async fn connect(&mut self) -> dash_spv::error::NetworkResult<()> { - self.connected = true; - Ok(()) - } - - async fn disconnect(&mut self) -> dash_spv::error::NetworkResult<()> { - self.connected = false; - Ok(()) - } - - async fn send_message( - &mut self, - message: NetworkMessage, - ) -> dash_spv::error::NetworkResult<()> { - self.sent_messages.write().await.push(message); - Ok(()) - } - - async fn receive_message(&mut self) -> dash_spv::error::NetworkResult> { - let mut messages = self.received_messages.write().await; - if messages.is_empty() { - Ok(None) - } else { - Ok(Some(messages.remove(0))) - } - } - - fn is_connected(&self) -> bool { - self.connected - } - - fn peer_count(&self) -> usize { - if self.connected { - 1 - } else { - 0 - } - } - - fn peer_info(&self) -> Vec { - vec![] - } - - async fn get_peer_best_height(&self) -> dash_spv::error::NetworkResult> { - Ok(Some(100)) - } - - async fn has_peer_with_service( - &self, - _service_flags: dashcore::network::constants::ServiceFlags, - ) -> bool { - true - } - - async fn get_last_message_peer_id(&self) -> dash_spv::types::PeerId { - dash_spv::types::PeerId(1) - } - - async fn update_peer_dsq_preference( - &mut self, - _wants_dsq: bool, - ) -> dash_spv::error::NetworkResult<()> { - Ok(()) - } -} fn create_test_config() -> ClientConfig { ClientConfig::testnet() @@ -120,38 +18,6 @@ fn create_test_config() -> ClientConfig { .with_validation_mode(dash_spv::types::ValidationMode::None) } -// fn create_test_address() -> Address { -// use dashcore::{Address, PubkeyHash, ScriptBuf}; -// use dashcore_hashes::Hash; -// let pubkey_hash = PubkeyHash::from_slice(&[1u8; 20]).unwrap(); -// let script = ScriptBuf::new_p2pkh(&pubkey_hash); -// Address::from_script(&script, Network::Testnet).unwrap() -// } - -fn create_test_block() -> Block { - let header = BlockHeader { - version: Version::from_consensus(1), - prev_blockhash: BlockHash::all_zeros(), - merkle_root: dashcore_hashes::sha256d::Hash::all_zeros().into(), - time: 1234567890, - bits: CompactTarget::from_consensus(0x1d00ffff), - nonce: 0, - }; - - Block { - header, - txdata: vec![], - } -} - -fn create_test_filter_match(block_hash: BlockHash, height: u32) -> FilterMatch { - FilterMatch { - block_hash, - height, - block_requested: false, - } -} - #[tokio::test] async fn test_filter_sync_manager_creation() { let config = create_test_config(); @@ -171,30 +37,12 @@ async fn test_request_block_download() { FilterSyncManager::new(&config, received_heights); let mut network = MockNetworkManager::new(); - let block_hash = BlockHash::from_slice(&[1u8; 32]).unwrap(); - let filter_match = create_test_filter_match(block_hash, 100); + let filter_match = FilterMatch::dummy(100); // Request block download let result = filter_sync.request_block_download(filter_match.clone(), &mut network).await; assert!(result.is_ok()); - // Check that a GetData message was sent - let sent_messages = network.get_sent_messages().await; - assert_eq!(sent_messages.len(), 1); - - match &sent_messages[0] { - NetworkMessage::GetData(getdata) => { - assert_eq!(getdata.len(), 1); - match &getdata[0] { - Inventory::Block(hash) => { - assert_eq!(hash, &block_hash); - } - _ => panic!("Expected Block inventory"), - } - } - _ => panic!("Expected GetData message"), - } - // Check sync manager state assert!(filter_sync.has_pending_downloads()); assert_eq!(filter_sync.pending_download_count(), 1); @@ -208,17 +56,12 @@ async fn test_duplicate_block_request_prevention() { FilterSyncManager::new(&config, received_heights); let mut network = MockNetworkManager::new(); - let block_hash = BlockHash::from_slice(&[1u8; 32]).unwrap(); - let filter_match = create_test_filter_match(block_hash, 100); + let filter_match = FilterMatch::dummy(100); // Request block download twice filter_sync.request_block_download(filter_match.clone(), &mut network).await.unwrap(); filter_sync.request_block_download(filter_match.clone(), &mut network).await.unwrap(); - // Should only send one GetData message - let sent_messages = network.get_sent_messages().await; - assert_eq!(sent_messages.len(), 1); - // Should only track one download assert_eq!(filter_sync.pending_download_count(), 1); } @@ -231,9 +74,9 @@ async fn test_handle_downloaded_block() { FilterSyncManager::new(&config, received_heights); let mut network = MockNetworkManager::new(); - let block = create_test_block(); + let block = Block::dummy(100, vec![]); let block_hash = block.block_hash(); - let filter_match = create_test_filter_match(block_hash, 100); + let filter_match = FilterMatch::dummy(100); // Request the block filter_sync.request_block_download(filter_match.clone(), &mut network).await.unwrap(); @@ -260,7 +103,7 @@ async fn test_handle_unexpected_block() { let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); - let block = create_test_block(); + let block = Block::dummy(0, vec![]); // Handle a block that wasn't requested let result = filter_sync.handle_downloaded_block(&block).await.unwrap(); @@ -277,44 +120,14 @@ async fn test_process_multiple_filter_matches() { FilterSyncManager::new(&config, received_heights); let mut network = MockNetworkManager::new(); - // Create multiple filter matches - let block_hash_1 = BlockHash::from_slice(&[1u8; 32]).unwrap(); - let block_hash_2 = BlockHash::from_slice(&[2u8; 32]).unwrap(); - let block_hash_3 = BlockHash::from_slice(&[3u8; 32]).unwrap(); - - let filter_matches = vec![ - create_test_filter_match(block_hash_1, 100), - create_test_filter_match(block_hash_2, 101), - create_test_filter_match(block_hash_3, 102), - ]; + let filter_matches = + vec![FilterMatch::dummy(100), FilterMatch::dummy(101), FilterMatch::dummy(102)]; // Process filter matches and request downloads let result = filter_sync.process_filter_matches_and_download(filter_matches, &mut network).await; assert!(result.is_ok()); - // Should have sent 1 bundled GetData message - let sent_messages = network.get_sent_messages().await; - assert_eq!(sent_messages.len(), 1); - - // Check that the GetData message contains all 3 blocks - match &sent_messages[0] { - NetworkMessage::GetData(getdata) => { - assert_eq!(getdata.len(), 3); - let requested_hashes: Vec<_> = getdata - .iter() - .filter_map(|inv| match inv { - Inventory::Block(hash) => Some(*hash), - _ => None, - }) - .collect(); - assert!(requested_hashes.contains(&block_hash_1)); - assert!(requested_hashes.contains(&block_hash_2)); - assert!(requested_hashes.contains(&block_hash_3)); - } - _ => panic!("Expected GetData message"), - } - // Should track 3 pending downloads assert_eq!(filter_sync.pending_download_count(), 3); } @@ -344,8 +157,7 @@ async fn test_filter_match_and_download_workflow() { // 5. Extract wallet transactions from blocks // For now, just test that we can create filter matches and request downloads - let block_hash = BlockHash::from_slice(&[1u8; 32]).unwrap(); - let filter_matches = vec![create_test_filter_match(block_hash, 100)]; + let filter_matches = vec![FilterMatch::dummy(100)]; let result = filter_sync.process_filter_matches_and_download(filter_matches, &mut network).await; @@ -362,8 +174,7 @@ async fn test_reset_clears_download_state() { FilterSyncManager::new(&config, received_heights); let mut network = MockNetworkManager::new(); - let block_hash = BlockHash::from_slice(&[1u8; 32]).unwrap(); - let filter_match = create_test_filter_match(block_hash, 100); + let filter_match = FilterMatch::dummy(100); // Request block download filter_sync.request_block_download(filter_match, &mut network).await.unwrap(); diff --git a/dash/Cargo.toml b/dash/Cargo.toml index 6dc4edc1b..9c2c412b6 100644 --- a/dash/Cargo.toml +++ b/dash/Cargo.toml @@ -33,6 +33,7 @@ eddsa = ["ed25519-dalek"] quorum_validation = ["bls"] message_verification = ["bls"] bincode = [ "dep:bincode", "dep:bincode_derive", "dashcore_hashes/bincode", "dash-network/bincode" ] +test-utils = [] # At least one of std, no-std must be enabled. # diff --git a/dash/src/lib.rs b/dash/src/lib.rs index 212e21431..d47da3d24 100644 --- a/dash/src/lib.rs +++ b/dash/src/lib.rs @@ -86,6 +86,9 @@ pub use ed25519_dalek; extern crate serde; extern crate core; +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + #[cfg(test)] #[macro_use] mod test_macros; diff --git a/dash/src/test_utils/address.rs b/dash/src/test_utils/address.rs new file mode 100644 index 000000000..fea6e7653 --- /dev/null +++ b/dash/src/test_utils/address.rs @@ -0,0 +1,21 @@ +use dash_network::Network; +use hashes::{Hash, sha256}; + +use crate::{Address, PrivateKey, PublicKey}; + +impl crate::Address { + pub fn dummy(network: Network, id: usize) -> Address { + let mut data = "dash-spv-test-seed".as_bytes().to_vec(); + data.extend_from_slice(&id.to_le_bytes()); + + let secret_bytes = sha256::Hash::hash(&data).to_byte_array(); + let secret_key = secp256k1::SecretKey::from_byte_array(&secret_bytes) + .unwrap_or_else(|e| panic!("Dummy address generation failed for id {id}: {e}")); + + let private_key = PrivateKey::new(secret_key, network); + let public_key = PublicKey::from_private_key(&secp256k1::Secp256k1::new(), &private_key); + + // Create P2PKH address from PublicKey + Address::p2pkh(&public_key, network) + } +} diff --git a/dash/src/test_utils/block.rs b/dash/src/test_utils/block.rs new file mode 100644 index 000000000..645c279e6 --- /dev/null +++ b/dash/src/test_utils/block.rs @@ -0,0 +1,57 @@ +use std::ops::Range; + +use hashes::Hash; + +use crate::{ + Block, BlockHash, CompactTarget, Header, Transaction, TxMerkleNode, + bip158::{BlockFilter, BlockFilterWriter}, + block::Version, +}; + +impl Block { + pub fn dummy(height: u32, transactions: Vec) -> Block { + Block { + header: Header::dummy(height), + txdata: transactions, + } + } +} + +impl BlockHash { + pub fn dummy(id: u32) -> Self { + let mut bytes = [0u8; 32]; + bytes[..4].copy_from_slice(&id.to_le_bytes()); + BlockHash::from_byte_array(bytes) + } +} + +impl Header { + pub fn dummy(height: u32) -> Self { + Header { + version: Version::ONE, + prev_blockhash: BlockHash::dummy(height.saturating_sub(1)), + merkle_root: TxMerkleNode::from_byte_array([0u8; 32]), + time: height, + bits: CompactTarget::from_consensus(0x1d00ffff), + nonce: height, + } + } + + pub fn dummy_batch(height_range: Range) -> Vec { + height_range.map(Self::dummy).collect() + } +} + +impl BlockFilter { + pub fn dummy(block: &Block) -> BlockFilter { + let mut content = Vec::new(); + let mut writer = BlockFilterWriter::new(&mut content, block); + + // Add output scripts from the block + writer.add_output_scripts(); + + // Finish writing and construct the filter + writer.finish().expect("Failed to finish filter"); + BlockFilter::new(&content) + } +} diff --git a/dash/src/test_utils/chainlock.rs b/dash/src/test_utils/chainlock.rs new file mode 100644 index 000000000..9f385d28d --- /dev/null +++ b/dash/src/test_utils/chainlock.rs @@ -0,0 +1,13 @@ +use crate::{ChainLock, Header}; + +use crate::bls_sig_utils::BLSSignature; + +impl ChainLock { + pub fn dummy(height: u32) -> ChainLock { + ChainLock { + block_height: height, + block_hash: Header::dummy(height).block_hash(), + signature: BLSSignature::from([0; 96]), + } + } +} diff --git a/dash/src/test_utils/filter.rs b/dash/src/test_utils/filter.rs new file mode 100644 index 000000000..0f7ea42e5 --- /dev/null +++ b/dash/src/test_utils/filter.rs @@ -0,0 +1,18 @@ +use std::ops::Range; + +use hashes::Hash; + +use crate::hash_types::FilterHeader; + +impl FilterHeader { + pub fn dummy(height: u32) -> Self { + let mut bytes = [0u8; 32]; + bytes[0] = 1; + bytes[1..5].copy_from_slice(&height.to_le_bytes()); + FilterHeader::from_raw_hash(dashcore_hashes::sha256d::Hash::from_byte_array(bytes)) + } + + pub fn dummy_batch(heights: Range) -> Vec { + heights.map(Self::dummy).collect() + } +} diff --git a/dash/src/test_utils/instantlock.rs b/dash/src/test_utils/instantlock.rs new file mode 100644 index 000000000..9d59ac28b --- /dev/null +++ b/dash/src/test_utils/instantlock.rs @@ -0,0 +1,22 @@ +use std::ops::Range; + +use crate::{ + Address, BlockHash, InstantLock, Transaction, bls_sig_utils::BLSSignature, + constants::COIN_VALUE, +}; + +impl InstantLock { + pub fn dummy(transaction_input_ids: Range) -> InstantLock { + let address = Address::dummy(crate::Network::Testnet, 0); + let tx = Transaction::dummy(&address, transaction_input_ids, &[COIN_VALUE]); + let inputs = tx.input.iter().map(|input| input.previous_output).collect(); + + InstantLock { + version: 1, + inputs, + txid: tx.txid(), + signature: BLSSignature::from([1; 96]), + cyclehash: BlockHash::dummy(0), + } + } +} diff --git a/dash/src/test_utils/mod.rs b/dash/src/test_utils/mod.rs new file mode 100644 index 000000000..ef5388c35 --- /dev/null +++ b/dash/src/test_utils/mod.rs @@ -0,0 +1,6 @@ +mod address; +mod block; +mod chainlock; +mod filter; +mod instantlock; +mod transaction; diff --git a/dash/src/test_utils/transaction.rs b/dash/src/test_utils/transaction.rs new file mode 100644 index 000000000..9bb292e1e --- /dev/null +++ b/dash/src/test_utils/transaction.rs @@ -0,0 +1,64 @@ +use std::ops::Range; + +use crate::{Address, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness}; + +impl Transaction { + pub fn dummy( + address: &Address, + inputs_ids_range: Range, + outputs_values: &[u64], + ) -> Transaction { + let inputs = inputs_ids_range + .enumerate() + .map(|(i, id)| { + let mut txid_bytes = [id; 32]; + txid_bytes[0] = 1; // This ensures that the txid is not all zeros + + TxIn { + previous_output: OutPoint::new(Txid::from(txid_bytes), i as u32), + script_sig: address.script_pubkey(), + sequence: 0xffffffff, + witness: Witness::new(), + } + }) + .collect(); + + let outputs = outputs_values + .iter() + .map(|&value| TxOut { + value, + script_pubkey: address.script_pubkey(), + }) + .collect(); + + Transaction { + version: 1, + lock_time: 0, + input: inputs, + output: outputs, + special_transaction_payload: None, + } + } + + pub fn dummy_coinbase(address: &Address, value: u64) -> Transaction { + let inputs = vec![TxIn { + previous_output: OutPoint::null(), + script_sig: ScriptBuf::new(), + sequence: 0xffffffff, + witness: Witness::new(), + }]; + + let outputs = vec![TxOut { + value, + script_pubkey: address.script_pubkey(), + }]; + + Transaction { + version: 1, + lock_time: 0, + input: inputs, + output: outputs, + special_transaction_payload: None, + } + } +} diff --git a/key-wallet-ffi/src/utxo_tests.rs b/key-wallet-ffi/src/utxo_tests.rs index 6875fb666..ee01038d1 100644 --- a/key-wallet-ffi/src/utxo_tests.rs +++ b/key-wallet-ffi/src/utxo_tests.rs @@ -313,7 +313,7 @@ mod utxo_tests { false, ); - let utxos = Utxo::new_test_batch(0..2, 10000, 100, false, false); + let utxos = Utxo::dummy_batch(0..2, 10000, 100, false, false); for utxo in utxos { bip44_account.utxos.insert(utxo.outpoint, utxo); } @@ -339,7 +339,7 @@ mod utxo_tests { false, ); - let utxos = Utxo::new_test_batch(10..11, 20000, 200, false, false); + let utxos = Utxo::dummy_batch(10..11, 20000, 200, false, false); for utxo in utxos { bip32_account.utxos.insert(utxo.outpoint, utxo); } @@ -358,7 +358,7 @@ mod utxo_tests { false, ); - let utxos = Utxo::new_test_batch(20..22, 30000, 300, false, false); + let utxos = Utxo::dummy_batch(20..22, 30000, 300, false, false); for utxo in utxos { coinjoin_account.utxos.insert(utxo.outpoint, utxo); } @@ -417,7 +417,7 @@ mod utxo_tests { false, ); - let utxos = Utxo::new_test_batch(1..2, 10000, 100, false, false); + let utxos = Utxo::dummy_batch(1..2, 10000, 100, false, false); for utxo in utxos { testnet_account.utxos.insert(utxo.outpoint, utxo); } diff --git a/key-wallet-manager/Cargo.toml b/key-wallet-manager/Cargo.toml index e709554ed..255280229 100644 --- a/key-wallet-manager/Cargo.toml +++ b/key-wallet-manager/Cargo.toml @@ -14,6 +14,7 @@ std = ["key-wallet/std", "dashcore/std", "dashcore_hashes/std", "secp256k1/std"] serde = ["dep:serde", "key-wallet/serde", "dashcore/serde"] getrandom = ["key-wallet/getrandom"] bincode = ["dep:bincode","key-wallet/bincode"] +test-utils = [] [dependencies] key-wallet = { path = "../key-wallet", default-features = false } @@ -24,12 +25,12 @@ serde = { version = "1.0", default-features = false, features = ["derive"], opti async-trait = "0.1" bincode = { version = "=2.0.0-rc.3", optional = true } zeroize = { version = "1.8", features = ["derive"] } +tokio = { version = "1.32", features = ["full"] } [dev-dependencies] -dashcore-test-utils = { path = "../test-utils" } +dashcore = { path = "../dash", features = ["test-utils"] } hex = "0.4" serde_json = "1.0" -tokio = { version = "1.32", features = ["full"] } [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(bench)', 'cfg(fuzzing)'] } diff --git a/key-wallet-manager/src/lib.rs b/key-wallet-manager/src/lib.rs index fb0e3561e..4a16b84f7 100644 --- a/key-wallet-manager/src/lib.rs +++ b/key-wallet-manager/src/lib.rs @@ -21,6 +21,9 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + pub mod wallet_interface; pub mod wallet_manager; diff --git a/key-wallet-manager/src/test_utils/mod.rs b/key-wallet-manager/src/test_utils/mod.rs new file mode 100644 index 000000000..8308f4c78 --- /dev/null +++ b/key-wallet-manager/src/test_utils/mod.rs @@ -0,0 +1,3 @@ +mod wallet; + +pub use wallet::{MockWallet, NonMatchingMockWallet}; diff --git a/key-wallet-manager/src/test_utils/wallet.rs b/key-wallet-manager/src/test_utils/wallet.rs new file mode 100644 index 000000000..d15a44074 --- /dev/null +++ b/key-wallet-manager/src/test_utils/wallet.rs @@ -0,0 +1,109 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use dashcore::{Block, Transaction, Txid}; +use tokio::sync::Mutex; + +use crate::{wallet_interface::WalletInterface, BlockProcessingResult}; + +// Type alias for transaction effects map +type TransactionEffectsMap = Arc)>>>; + +#[derive(Default)] +pub struct MockWallet { + processed_blocks: Arc>>, + processed_transactions: Arc>>, + // Map txid -> (net_amount, addresses) + effects: TransactionEffectsMap, +} + +impl MockWallet { + pub fn new() -> Self { + Self { + processed_blocks: Arc::new(Mutex::new(Vec::new())), + processed_transactions: Arc::new(Mutex::new(Vec::new())), + effects: Arc::new(Mutex::new(BTreeMap::new())), + } + } + + pub async fn set_effect(&self, txid: dashcore::Txid, net: i64, addresses: Vec) { + let mut map = self.effects.lock().await; + map.insert(txid, (net, addresses)); + } + + pub fn processed_blocks(&self) -> Arc>> { + self.processed_blocks.clone() + } + + pub fn processed_transactions(&self) -> Arc>> { + self.processed_transactions.clone() + } +} + +#[async_trait::async_trait] +impl WalletInterface for MockWallet { + async fn process_block(&mut self, block: &Block, height: u32) -> BlockProcessingResult { + let mut processed = self.processed_blocks.lock().await; + processed.push((block.block_hash(), height)); + + // Return txids of all transactions in block as "relevant" + BlockProcessingResult { + relevant_txids: block.txdata.iter().map(|tx| tx.txid()).collect(), + new_addresses: Vec::new(), + } + } + + async fn process_mempool_transaction(&mut self, tx: &Transaction) { + let mut processed = self.processed_transactions.lock().await; + processed.push(tx.txid()); + } + + async fn check_compact_filter( + &mut self, + _filter: &dashcore::bip158::BlockFilter, + _block_hash: &dashcore::BlockHash, + ) -> bool { + // Return true for all filters in test + true + } + + async fn describe(&self) -> String { + "MockWallet (test implementation)".to_string() + } + + async fn transaction_effect(&self, tx: &Transaction) -> Option<(i64, Vec)> { + let map = self.effects.lock().await; + map.get(&tx.txid()).cloned() + } +} + +/// Mock wallet that returns false for filter checks +#[derive(Default)] +pub struct NonMatchingMockWallet {} + +impl NonMatchingMockWallet { + pub fn new() -> Self { + Self {} + } +} + +#[async_trait::async_trait] +impl WalletInterface for NonMatchingMockWallet { + async fn process_block(&mut self, _block: &Block, _height: u32) -> BlockProcessingResult { + BlockProcessingResult::default() + } + + async fn process_mempool_transaction(&mut self, _tx: &Transaction) {} + + async fn check_compact_filter( + &mut self, + _filter: &dashcore::bip158::BlockFilter, + _block_hash: &dashcore::BlockHash, + ) -> bool { + // Always return false - filter doesn't match + false + } + + async fn describe(&self) -> String { + "NonMatchingWallet (test implementation)".to_string() + } +} diff --git a/key-wallet-manager/tests/spv_integration_tests.rs b/key-wallet-manager/tests/spv_integration_tests.rs index c285cf1c5..62f0f7051 100644 --- a/key-wallet-manager/tests/spv_integration_tests.rs +++ b/key-wallet-manager/tests/spv_integration_tests.rs @@ -1,14 +1,10 @@ //! Integration tests for SPV wallet functionality -use dashcore::bip158::{BlockFilter, BlockFilterWriter}; -use dashcore::blockdata::block::{Block, Header, Version}; -use dashcore::blockdata::script::ScriptBuf; +use dashcore::bip158::BlockFilter; +use dashcore::blockdata::block::Block; use dashcore::blockdata::transaction::Transaction; use dashcore::constants::COINBASE_MATURITY; -use dashcore::pow::CompactTarget; -use dashcore::{BlockHash, OutPoint, TxIn, TxOut, Txid}; -use dashcore_hashes::Hash; -use dashcore_test_utils::create_transaction_to_address; +use dashcore::Address; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -16,56 +12,6 @@ use key_wallet::Network; use key_wallet_manager::wallet_interface::WalletInterface; use key_wallet_manager::wallet_manager::WalletManager; -/// Create a test transaction -fn create_test_transaction(value: u64) -> Transaction { - Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint { - txid: Txid::from_byte_array([1u8; 32]), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: 0xffffffff, - witness: dashcore::Witness::default(), - }], - output: vec![TxOut { - value, - script_pubkey: ScriptBuf::new(), - }], - special_transaction_payload: None, - } -} - -/// Create a test block -fn create_test_block(height: u32, transactions: Vec) -> Block { - Block { - header: Header { - version: Version::ONE, - prev_blockhash: BlockHash::from_byte_array([0u8; 32]), - merkle_root: dashcore::TxMerkleNode::from_byte_array([0u8; 32]), - time: height, - bits: CompactTarget::from_consensus(0x1d00ffff), - nonce: 0, - }, - txdata: transactions, - } -} - -/// Create a mock filter that matches everything (for testing) -fn create_mock_filter(block: &Block) -> BlockFilter { - let mut content = Vec::new(); - let mut writer = BlockFilterWriter::new(&mut content, block); - - // Add output scripts from the block - writer.add_output_scripts(); - - // Finish writing and construct the filter - writer.finish().expect("Failed to finish filter"); - BlockFilter::new(&content) -} - #[tokio::test] async fn test_filter_checking() { let mut manager = WalletManager::::new(Network::Testnet); @@ -78,9 +24,9 @@ async fn test_filter_checking() { .expect("Failed to create wallet"); // Create a test block with a transaction - let tx = create_test_transaction(100000); - let block = create_test_block(100, vec![tx]); - let filter = create_mock_filter(&block); + let tx = Transaction::dummy(&Address::dummy(Network::Testnet, 0), 0..0, &[100000]); + let block = Block::dummy(100, vec![tx]); + let filter = BlockFilter::dummy(&block); let block_hash = block.block_hash(); // Check the filter @@ -105,19 +51,15 @@ async fn test_block_processing() { let addresses = manager.monitored_addresses(); assert!(!addresses.is_empty()); - let external = dashcore::Address::p2pkh( - &dashcore::PublicKey::from_slice(&[0x02; 33]).expect("valid pubkey"), - Network::Testnet, - ); + let external = Address::dummy(Network::Testnet, 0); let addresses_before = manager.monitored_addresses(); assert!(!addresses_before.is_empty()); + let tx1 = Transaction::dummy(&addresses[0], 0..0, &[100_000]); + let tx2 = Transaction::dummy(&addresses[1], 0..0, &[200_000]); + let tx3 = Transaction::dummy(&external, 0..0, &[300_000]); - let tx1 = create_transaction_to_address(&addresses[0], 100_000); - let tx2 = create_transaction_to_address(&addresses[1], 200_000); - let tx3 = create_transaction_to_address(&external, 300_000); - - let block = create_test_block(100, vec![tx1.clone(), tx2.clone(), tx3.clone()]); + let block = Block::dummy(100, vec![tx1.clone(), tx2.clone(), tx3.clone()]); let result = manager.process_block(&block, 100).await; assert_eq!(result.relevant_txids.len(), 2); @@ -142,14 +84,11 @@ async fn test_block_processing_result_empty() { .create_wallet_with_random_mnemonic(WalletAccountCreationOptions::Default) .expect("Failed to create wallet"); - let external = dashcore::Address::p2pkh( - &dashcore::PublicKey::from_slice(&[0x02; 33]).expect("valid pubkey"), - Network::Testnet, - ); - let tx1 = create_transaction_to_address(&external, 100_000); - let tx2 = create_transaction_to_address(&external, 200_000); + let external = Address::dummy(Network::Testnet, 0); + let tx1 = Transaction::dummy(&external, 0..0, &[100_000]); + let tx2 = Transaction::dummy(&external, 0..0, &[200_000]); - let block = create_test_block(100, vec![tx1, tx2]); + let block = Block::dummy(100, vec![tx1, tx2]); let result = manager.process_block(&block, 100).await; assert!(result.relevant_txids.is_empty()); @@ -166,11 +105,13 @@ async fn test_filter_caching() { .expect("Failed to create wallet"); // Create multiple blocks with different hashes - let block1 = create_test_block(100, vec![create_test_transaction(1000)]); - let block2 = create_test_block(101, vec![create_test_transaction(2000)]); + let tx1 = Transaction::dummy(&Address::dummy(Network::Testnet, 0), 0..0, &[1000]); + let tx2 = Transaction::dummy(&Address::dummy(Network::Testnet, 0), 0..0, &[2000]); + let block1 = Block::dummy(100, vec![tx1]); + let block2 = Block::dummy(101, vec![tx2]); - let filter1 = create_mock_filter(&block1); - let filter2 = create_mock_filter(&block2); + let filter1 = BlockFilter::dummy(&block1); + let filter2 = BlockFilter::dummy(&block2); let hash1 = block1.block_hash(); let hash2 = block2.block_hash(); @@ -200,29 +141,6 @@ fn assert_wallet_heights(manager: &WalletManager, expected_he } } -/// Create a coinbase transaction paying to the given script -/// TODO: Unify with other `create_coinbase_transaction` helpers into `dashcore` crate. -fn create_coinbase_transaction(script_pubkey: ScriptBuf, value: u64) -> Transaction { - Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint { - txid: Txid::all_zeros(), - vout: 0xffffffff, - }, - script_sig: ScriptBuf::new(), - sequence: 0xffffffff, - witness: dashcore::Witness::default(), - }], - output: vec![TxOut { - value, - script_pubkey, - }], - special_transaction_payload: None, - } -} - /// Test that the wallet heights are updated after block processing. #[tokio::test] async fn test_height_updated_after_block_processing() { @@ -237,7 +155,8 @@ async fn test_height_updated_after_block_processing() { assert_wallet_heights(&manager, 0); for height in [1000, 2000, 3000] { - let block = create_test_block(height, vec![create_test_transaction(1000)]); + let tx = Transaction::dummy(&Address::dummy(Network::Testnet, 0), 0..0, &[100000]); + let block = Block::dummy(height, vec![tx]); manager.process_block(&block, height).await; assert_wallet_heights(&manager, height); } @@ -270,11 +189,11 @@ async fn test_immature_balance_matures_during_block_processing() { // Create a coinbase transaction paying to our wallet let coinbase_value = 100; - let coinbase_tx = create_coinbase_transaction(receive_address.script_pubkey(), coinbase_value); + let coinbase_tx = Transaction::dummy_coinbase(&receive_address, coinbase_value); // Process the coinbase at height 1000 let coinbase_height = 1000; - let coinbase_block = create_test_block(coinbase_height, vec![coinbase_tx.clone()]); + let coinbase_block = Block::dummy(coinbase_height, vec![coinbase_tx.clone()]); manager.process_block(&coinbase_block, coinbase_height).await; // Verify the coinbase is detected and stored as immature @@ -291,8 +210,9 @@ async fn test_immature_balance_matures_during_block_processing() { // Process 99 more blocks up to just before maturity let maturity_height = coinbase_height + COINBASE_MATURITY; + let tx = Transaction::dummy(&Address::dummy(Network::Regtest, 0), 0..0, &[1000]); for height in (coinbase_height + 1)..maturity_height { - let block = create_test_block(height, vec![create_test_transaction(1000)]); + let block = Block::dummy(height, vec![tx.clone()]); manager.process_block(&block, height).await; } @@ -305,7 +225,7 @@ async fn test_immature_balance_matures_during_block_processing() { ); // Process the maturity block - let maturity_block = create_test_block(maturity_height, vec![create_test_transaction(1000)]); + let maturity_block = Block::dummy(maturity_height, vec![tx.clone()]); manager.process_block(&maturity_block, maturity_height).await; // Verify the coinbase has matured diff --git a/key-wallet/Cargo.toml b/key-wallet/Cargo.toml index af07f6e58..ef39be164 100644 --- a/key-wallet/Cargo.toml +++ b/key-wallet/Cargo.toml @@ -16,7 +16,7 @@ bincode = ["serde", "dep:bincode", "dep:bincode_derive", "dash-network/bincode", bip38 = ["scrypt", "aes", "bs58", "rand"] eddsa = ["dashcore/eddsa"] bls = ["dashcore/bls"] -test-utils = [] +test-utils = ["dashcore/test-utils"] [dependencies] internals = { path = "../internals", package = "dashcore-private" } @@ -47,6 +47,7 @@ tracing = "0.1" async-trait = "0.1" [dev-dependencies] +dashcore = { path="../dash", features = ["test-utils"] } hex = "0.4" key-wallet = { path = ".", features = ["test-utils", "bip38", "serde", "bincode", "eddsa", "bls"] } tokio = { version = "1", features = ["macros", "rt"] } diff --git a/key-wallet/src/test_utils/account.rs b/key-wallet/src/test_utils/account.rs index bf3f427cc..06d62fadb 100644 --- a/key-wallet/src/test_utils/account.rs +++ b/key-wallet/src/test_utils/account.rs @@ -1,19 +1,21 @@ +use dashcore::Network; + use crate::account::StandardAccountType; use crate::managed_account::address_pool::{AddressPool, AddressPoolType, KeySource}; use crate::managed_account::managed_account_type::ManagedAccountType; use crate::managed_account::ManagedAccount; -use crate::{DerivationPath, Network}; +use crate::DerivationPath; impl ManagedAccount { /// Create a test managed account with a standard BIP44 type and empty address pools - pub fn new_test_bip44(network: Network) -> Self { + pub fn dummy_bip44() -> Self { let base_path = DerivationPath::master(); let external_pool = AddressPool::new( base_path.clone(), AddressPoolType::External, 20, - network, + Network::Regtest, &KeySource::NoKeySource, ) .expect("Failed to create external address pool"); @@ -22,7 +24,7 @@ impl ManagedAccount { base_path, AddressPoolType::Internal, 20, - network, + Network::Regtest, &KeySource::NoKeySource, ) .expect("Failed to create internal address pool"); @@ -34,6 +36,6 @@ impl ManagedAccount { internal_addresses: internal_pool, }; - ManagedAccount::new(account_type, network, false) + ManagedAccount::new(account_type, Network::Regtest, false) } } diff --git a/key-wallet/src/test_utils/address.rs b/key-wallet/src/test_utils/address.rs deleted file mode 100644 index aa14eff0e..000000000 --- a/key-wallet/src/test_utils/address.rs +++ /dev/null @@ -1,11 +0,0 @@ -use dashcore::{Address, Network}; - -const TEST_PUBKEY_BYTES: [u8; 33] = [ - 0x02, 0x50, 0x86, 0x3a, 0xd6, 0x4a, 0x87, 0xae, 0x8a, 0x2f, 0xe8, 0x3c, 0x1a, 0xf1, 0xa8, 0x40, - 0x3c, 0xb5, 0x3f, 0x53, 0xe4, 0x86, 0xd8, 0x51, 0x1d, 0xad, 0x8a, 0x04, 0x88, 0x7e, 0x5b, 0x23, - 0x52, -]; - -pub fn test_address() -> Address { - Address::p2pkh(&dashcore::PublicKey::from_slice(&TEST_PUBKEY_BYTES).unwrap(), Network::Testnet) -} diff --git a/key-wallet/src/test_utils/mod.rs b/key-wallet/src/test_utils/mod.rs index baaa97e9d..5f69fe1d6 100644 --- a/key-wallet/src/test_utils/mod.rs +++ b/key-wallet/src/test_utils/mod.rs @@ -1,5 +1,3 @@ mod account; -mod address; mod utxo; - -pub use address::*; +mod wallet; diff --git a/key-wallet/src/test_utils/utxo.rs b/key-wallet/src/test_utils/utxo.rs index 1a6faa2f7..5233fe08f 100644 --- a/key-wallet/src/test_utils/utxo.rs +++ b/key-wallet/src/test_utils/utxo.rs @@ -1,15 +1,15 @@ use std::ops::Range; -use dashcore::{OutPoint, ScriptBuf, TxOut, Txid}; +use dashcore::{Address, Network, OutPoint, ScriptBuf, TxOut, Txid}; -use crate::{test_utils::test_address, Utxo}; +use crate::Utxo; impl Utxo { - pub fn new_test(id: u8, value: u64, height: u32, coinbase: bool, confirmed: bool) -> Self { - Self::new_test_batch(id..id + 1, value, height, coinbase, confirmed).remove(0) + pub fn dummy(id: u8, value: u64, height: u32, coinbase: bool, confirmed: bool) -> Self { + Self::dummy_batch(id..id + 1, value, height, coinbase, confirmed).remove(0) } - pub fn new_test_batch( + pub fn dummy_batch( ids_range: Range, value: u64, height: u32, @@ -26,7 +26,13 @@ impl Utxo { script_pubkey: ScriptBuf::new(), }; - let mut utxo = Utxo::new(outpoint, txout, test_address(), height, coinbase); + let mut utxo = Utxo::new( + outpoint, + txout, + Address::dummy(Network::Testnet, id as usize), + height, + coinbase, + ); utxo.is_confirmed = confirmed; utxo }) diff --git a/key-wallet/src/test_utils/wallet.rs b/key-wallet/src/test_utils/wallet.rs new file mode 100644 index 000000000..653e17bb6 --- /dev/null +++ b/key-wallet/src/test_utils/wallet.rs @@ -0,0 +1,9 @@ +use dashcore::Network; + +use crate::wallet::ManagedWalletInfo; + +impl ManagedWalletInfo { + pub fn dummy(id: u8) -> Self { + ManagedWalletInfo::new(Network::Regtest, [id; 32]) + } +} diff --git a/key-wallet/src/tests/balance_tests.rs b/key-wallet/src/tests/balance_tests.rs index 38fc78d88..59dcc653f 100644 --- a/key-wallet/src/tests/balance_tests.rs +++ b/key-wallet/src/tests/balance_tests.rs @@ -3,21 +3,21 @@ use crate::managed_account::ManagedAccount; use crate::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use crate::wallet::managed_wallet_info::ManagedWalletInfo; -use crate::{Network, Utxo, WalletBalance}; +use crate::{Utxo, WalletBalance}; #[test] fn test_balance_with_mixed_utxo_types() { - let mut wallet_info = ManagedWalletInfo::new(Network::Regtest, [1u8; 32]); - let mut account = ManagedAccount::new_test_bip44(Network::Regtest); + let mut wallet_info = ManagedWalletInfo::dummy(1); + let mut account = ManagedAccount::dummy_bip44(); // Regular confirmed UTXO - let utxo1 = Utxo::new_test(1, 100_000, 1000, false, true); + let utxo1 = Utxo::dummy(1, 100_000, 1000, false, true); account.utxos.insert(utxo1.outpoint, utxo1); // Mature coinbase (100+ confirmations at height 1100) - let utxo2 = Utxo::new_test(2, 10_000_000, 1000, true, true); + let utxo2 = Utxo::dummy(2, 10_000_000, 1000, true, true); account.utxos.insert(utxo2.outpoint, utxo2); // Immature coinbase (<100 confirmations at height 1100) - let utxo3 = Utxo::new_test(3, 20_000_000, 1050, true, true); + let utxo3 = Utxo::dummy(3, 20_000_000, 1050, true, true); account.utxos.insert(utxo3.outpoint, utxo3); wallet_info.accounts.insert(account); @@ -29,11 +29,11 @@ fn test_balance_with_mixed_utxo_types() { #[test] fn test_coinbase_maturity_boundary() { - let mut wallet_info = ManagedWalletInfo::new(Network::Regtest, [2u8; 32]); - let mut account = ManagedAccount::new_test_bip44(Network::Regtest); + let mut wallet_info = ManagedWalletInfo::dummy(2); + let mut account = ManagedAccount::dummy_bip44(); // Coinbase at height 1000 - let utxo = Utxo::new_test(1, 50_000_000, 1000, true, true); + let utxo = Utxo::dummy(1, 50_000_000, 1000, true, true); account.utxos.insert(utxo.outpoint, utxo); wallet_info.accounts.insert(account); @@ -51,10 +51,10 @@ fn test_coinbase_maturity_boundary() { #[test] fn test_locked_utxos_in_locked_balance() { - let mut wallet_info = ManagedWalletInfo::new(Network::Regtest, [3u8; 32]); - let mut account = ManagedAccount::new_test_bip44(Network::Regtest); + let mut wallet_info = ManagedWalletInfo::dummy(3); + let mut account = ManagedAccount::dummy_bip44(); - let mut utxo = Utxo::new_test(1, 100_000, 1000, false, true); + let mut utxo = Utxo::dummy(1, 100_000, 1000, false, true); utxo.is_locked = true; account.utxos.insert(utxo.outpoint, utxo); wallet_info.accounts.insert(account); @@ -67,10 +67,10 @@ fn test_locked_utxos_in_locked_balance() { #[test] fn test_unconfirmed_utxos_in_unconfirmed_balance() { - let mut wallet_info = ManagedWalletInfo::new(Network::Regtest, [4u8; 32]); - let mut account = ManagedAccount::new_test_bip44(Network::Regtest); + let mut wallet_info = ManagedWalletInfo::dummy(4); + let mut account = ManagedAccount::dummy_bip44(); - let utxo = Utxo::new_test(1, 100_000, 0, false, false); + let utxo = Utxo::dummy(1, 100_000, 0, false, false); account.utxos.insert(utxo.outpoint, utxo); wallet_info.accounts.insert(account); diff --git a/key-wallet/src/utxo.rs b/key-wallet/src/utxo.rs index 9df1fb67c..dbf4eed0b 100644 --- a/key-wallet/src/utxo.rs +++ b/key-wallet/src/utxo.rs @@ -320,7 +320,7 @@ mod tests { #[test] fn test_utxo_spendability() { - let mut utxo = Utxo::new_test(0, 100000, 100, false, false); + let mut utxo = Utxo::dummy(0, 100000, 100, false, false); // Unconfirmed UTXO should not be spendable assert!(!utxo.is_spendable(200)); @@ -338,8 +338,8 @@ mod tests { fn test_utxo_set_operations() { let mut set = UtxoSet::new(); - let utxo1 = Utxo::new_test(0, 100000, 100, false, false); - let utxo2 = Utxo::new_test(1, 200000, 150, false, false); + let utxo1 = Utxo::dummy(0, 100000, 100, false, false); + let utxo2 = Utxo::dummy(1, 200000, 150, false, false); set.add(utxo1.clone()); set.add(utxo2.clone()); @@ -372,7 +372,7 @@ mod tests { current_height: u32, expected: u32, ) { - let utxo = Utxo::new_test(0, 100000, utxo_height, false, is_confirmed); + let utxo = Utxo::dummy(0, 100000, utxo_height, false, is_confirmed); assert_eq!(utxo.confirmations(current_height), expected); } } diff --git a/key-wallet/src/wallet/managed_wallet_info/coin_selection.rs b/key-wallet/src/wallet/managed_wallet_info/coin_selection.rs index 4432cf8e6..52fb37aee 100644 --- a/key-wallet/src/wallet/managed_wallet_info/coin_selection.rs +++ b/key-wallet/src/wallet/managed_wallet_info/coin_selection.rs @@ -694,10 +694,10 @@ mod tests { #[test] fn test_smallest_first_selection() { let utxos = vec![ - Utxo::new_test(0, 10000, 100, false, true), - Utxo::new_test(0, 20000, 100, false, true), - Utxo::new_test(0, 30000, 100, false, true), - Utxo::new_test(0, 40000, 100, false, true), + Utxo::dummy(0, 10000, 100, false, true), + Utxo::dummy(0, 20000, 100, false, true), + Utxo::dummy(0, 30000, 100, false, true), + Utxo::dummy(0, 40000, 100, false, true), ]; let selector = CoinSelector::new(SelectionStrategy::SmallestFirst); @@ -712,10 +712,10 @@ mod tests { #[test] fn test_largest_first_selection() { let utxos = vec![ - Utxo::new_test(0, 10000, 100, false, true), - Utxo::new_test(0, 20000, 100, false, true), - Utxo::new_test(0, 30000, 100, false, true), - Utxo::new_test(0, 40000, 100, false, true), + Utxo::dummy(0, 10000, 100, false, true), + Utxo::dummy(0, 20000, 100, false, true), + Utxo::dummy(0, 30000, 100, false, true), + Utxo::dummy(0, 40000, 100, false, true), ]; let selector = CoinSelector::new(SelectionStrategy::LargestFirst); @@ -728,10 +728,8 @@ mod tests { #[test] fn test_insufficient_funds() { - let utxos = vec![ - Utxo::new_test(0, 10000, 100, false, true), - Utxo::new_test(0, 20000, 100, false, true), - ]; + let utxos = + vec![Utxo::dummy(0, 10000, 100, false, true), Utxo::dummy(0, 20000, 100, false, true)]; let selector = CoinSelector::new(SelectionStrategy::LargestFirst); let result = selector.select_coins(&utxos, 50000, FeeRate::new(1000), 200); @@ -743,12 +741,12 @@ mod tests { fn test_optimal_consolidation_strategy() { // Test that OptimalConsolidation strategy works correctly let utxos = vec![ - Utxo::new_test(0, 100, 100, false, true), - Utxo::new_test(0, 200, 100, false, true), - Utxo::new_test(0, 300, 100, false, true), - Utxo::new_test(0, 500, 100, false, true), - Utxo::new_test(0, 1000, 100, false, true), - Utxo::new_test(0, 2000, 100, false, true), + Utxo::dummy(0, 100, 100, false, true), + Utxo::dummy(0, 200, 100, false, true), + Utxo::dummy(0, 300, 100, false, true), + Utxo::dummy(0, 500, 100, false, true), + Utxo::dummy(0, 1000, 100, false, true), + Utxo::dummy(0, 2000, 100, false, true), ]; let selector = CoinSelector::new(SelectionStrategy::OptimalConsolidation); diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs index c2c497732..4d823cc77 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs @@ -835,7 +835,6 @@ impl std::error::Error for BuilderError {} #[cfg(test)] mod tests { use super::*; - use crate::test_utils::test_address; use crate::Network; use dashcore::blockdata::transaction::special_transaction::asset_lock::AssetLockPayload; use dashcore_hashes::{sha256d, Hash}; @@ -843,9 +842,9 @@ mod tests { #[test] fn test_transaction_builder_basic() { - let utxo = Utxo::new_test(0, 100000, 100, false, true); - let destination = test_address(); - let change = test_address(); + let utxo = Utxo::dummy(0, 100000, 100, false, true); + let destination = Address::dummy(Network::Testnet, 0); + let change = Address::dummy(Network::Testnet, 0); let tx = TransactionBuilder::new() .add_input(utxo, None) @@ -862,8 +861,8 @@ mod tests { #[test] fn test_insufficient_funds() { - let utxo = Utxo::new_test(0, 10000, 100, false, true); - let destination = test_address(); + let utxo = Utxo::dummy(0, 10000, 100, false, true); + let destination = Address::dummy(Network::Testnet, 0); let result = TransactionBuilder::new() .add_input(utxo, None) @@ -929,12 +928,12 @@ mod tests { fn test_transaction_size_estimation() { // Test that transaction size estimation is accurate let utxos = vec![ - Utxo::new_test(0, 100000, 100, false, true), - Utxo::new_test(0, 200000, 100, false, true), + Utxo::dummy(0, 100000, 100, false, true), + Utxo::dummy(0, 200000, 100, false, true), ]; - let recipient_address = test_address(); - let change_address = test_address(); + let recipient_address = Address::dummy(Network::Testnet, 0); + let change_address = Address::dummy(Network::Testnet, 0); let builder = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) @@ -965,10 +964,10 @@ mod tests { #[test] fn test_fee_calculation() { // Test that fees are calculated correctly - let utxos = vec![Utxo::new_test(0, 1000000, 100, false, true)]; + let utxos = vec![Utxo::dummy(0, 1000000, 100, false, true)]; - let recipient_address = test_address(); - let change_address = test_address(); + let recipient_address = Address::dummy(Network::Testnet, 0); + let change_address = Address::dummy(Network::Testnet, 0); let tx = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) // 1 duff per byte @@ -992,10 +991,10 @@ mod tests { #[test] fn test_exact_change_no_change_output() { // Test when the exact amount is used (no change output needed) - let utxos = vec![Utxo::new_test(0, 150226, 100, false, true)]; // Exact amount for output + fee + let utxos = vec![Utxo::dummy(0, 150226, 100, false, true)]; // Exact amount for output + fee - let recipient_address = test_address(); - let change_address = test_address(); + let recipient_address = Address::dummy(Network::Testnet, 0); + let change_address = Address::dummy(Network::Testnet, 0); let tx = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) @@ -1014,9 +1013,9 @@ mod tests { #[test] fn test_special_payload_size_calculations() { // Test that special payload sizes are calculated correctly - let utxo = Utxo::new_test(0, 100000, 100, false, true); - let destination = test_address(); - let change = test_address(); + let utxo = Utxo::dummy(0, 100000, 100, false, true); + let destination = Address::dummy(Network::Testnet, 0); + let change = Address::dummy(Network::Testnet, 0); // Test with AssetLock payload let credit_outputs = vec![ @@ -1079,9 +1078,9 @@ mod tests { #[test] fn test_build_with_payload_override() { // Test that build_with_payload overrides set_special_payload - let utxo = Utxo::new_test(0, 100000, 100, false, true); - let destination = test_address(); - let change = test_address(); + let utxo = Utxo::dummy(0, 100000, 100, false, true); + let destination = Address::dummy(Network::Testnet, 0); + let change = Address::dummy(Network::Testnet, 0); let credit_outputs = vec![TxOut { value: 50000, @@ -1125,8 +1124,8 @@ mod tests { #[test] fn test_bip69_output_ordering() { // Test that outputs are sorted according to BIP-69 - let utxo = Utxo::new_test(0, 1000000, 100, false, true); - let address1 = test_address(); + let utxo = Utxo::dummy(0, 1000000, 100, false, true); + let address1 = Address::dummy(Network::Testnet, 0); let address2 = Address::p2pkh( &dashcore::PublicKey::from_slice(&[ 0x02, 0x60, 0x86, 0x3a, 0xd6, 0x4a, 0x87, 0xae, 0x8a, 0x2f, 0xe8, 0x3c, 0x1a, 0xf1, @@ -1136,7 +1135,7 @@ mod tests { .unwrap(), Network::Testnet, ); - let change_address = test_address(); + let change_address = Address::dummy(Network::Testnet, 0); let tx = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) @@ -1172,7 +1171,7 @@ mod tests { value: 100000, script_pubkey: ScriptBuf::new(), }, - test_address(), + Address::dummy(Network::Testnet, 0), 100, false, ); @@ -1186,7 +1185,7 @@ mod tests { value: 200000, script_pubkey: ScriptBuf::new(), }, - test_address(), + Address::dummy(Network::Testnet, 0), 100, false, ); @@ -1200,13 +1199,13 @@ mod tests { value: 300000, script_pubkey: ScriptBuf::new(), }, - test_address(), + Address::dummy(Network::Testnet, 0), 100, false, ); - let destination = test_address(); - let change = test_address(); + let destination = Address::dummy(Network::Testnet, 0); + let change = Address::dummy(Network::Testnet, 0); let tx = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) @@ -1245,13 +1244,13 @@ mod tests { fn test_coin_selection_with_special_payload() { // Test that coin selection considers special payload size let utxos = vec![ - Utxo::new_test(0, 50000, 100, false, true), - Utxo::new_test(0, 60000, 100, false, true), - Utxo::new_test(0, 70000, 100, false, true), + Utxo::dummy(0, 50000, 100, false, true), + Utxo::dummy(0, 60000, 100, false, true), + Utxo::dummy(0, 70000, 100, false, true), ]; - let recipient_address = test_address(); - let change_address = test_address(); + let recipient_address = Address::dummy(Network::Testnet, 0); + let change_address = Address::dummy(Network::Testnet, 0); // Create a large special payload that affects fee calculation let credit_outputs = vec![ diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs index b87b5d41a..1344c4afd 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs @@ -188,9 +188,9 @@ mod tests { fn test_basic_transaction_creation() { // Test creating a basic transaction with inputs and outputs let utxos = vec![ - Utxo::new_test(0, 100000, 100, false, true), - Utxo::new_test(0, 200000, 100, false, true), - Utxo::new_test(0, 300000, 100, false, true), + Utxo::dummy(0, 100000, 100, false, true), + Utxo::dummy(0, 200000, 100, false, true), + Utxo::dummy(0, 300000, 100, false, true), ]; let recipient_address = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs") @@ -293,8 +293,8 @@ mod tests { fn test_transaction_size_estimation() { // Test that transaction size estimation is accurate let utxos = vec![ - Utxo::new_test(0, 100000, 100, false, true), - Utxo::new_test(0, 200000, 100, false, true), + Utxo::dummy(0, 100000, 100, false, true), + Utxo::dummy(0, 200000, 100, false, true), ]; let recipient_address = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs") @@ -330,7 +330,7 @@ mod tests { #[test] fn test_fee_calculation() { // Test that fees are calculated correctly - let utxos = vec![Utxo::new_test(0, 1000000, 100, false, true)]; + let utxos = vec![Utxo::dummy(0, 1000000, 100, false, true)]; let recipient_address = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs") .unwrap() @@ -364,7 +364,7 @@ mod tests { #[test] fn test_insufficient_funds() { // Test that insufficient funds returns an error - let utxos = vec![Utxo::new_test(0, 100000, 100, false, true)]; + let utxos = vec![Utxo::dummy(0, 100000, 100, false, true)]; let recipient_address = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs") .unwrap() @@ -388,7 +388,7 @@ mod tests { #[test] fn test_exact_change_no_change_output() { // Test when the exact amount is used (no change output needed) - let utxos = vec![Utxo::new_test(0, 150226, 100, false, true)]; // Exact amount for output + fee + let utxos = vec![Utxo::dummy(0, 150226, 100, false, true)]; // Exact amount for output + fee let recipient_address = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs") .unwrap() diff --git a/key-wallet/tests/test_optimal_consolidation.rs b/key-wallet/tests/test_optimal_consolidation.rs index aa5a1945f..fab6a467f 100644 --- a/key-wallet/tests/test_optimal_consolidation.rs +++ b/key-wallet/tests/test_optimal_consolidation.rs @@ -8,12 +8,12 @@ fn test_optimal_consolidation_strategy() { // Test that OptimalConsolidation strategy works correctly let utxos = vec![ - Utxo::new_test(0, 100, 100, false, true), - Utxo::new_test(0, 200, 100, false, true), - Utxo::new_test(0, 300, 100, false, true), - Utxo::new_test(0, 500, 100, false, true), - Utxo::new_test(0, 1000, 100, false, true), - Utxo::new_test(0, 2000, 100, false, true), + Utxo::dummy(0, 100, 100, false, true), + Utxo::dummy(0, 200, 100, false, true), + Utxo::dummy(0, 300, 100, false, true), + Utxo::dummy(0, 500, 100, false, true), + Utxo::dummy(0, 1000, 100, false, true), + Utxo::dummy(0, 2000, 100, false, true), ]; let selector = CoinSelector::new(SelectionStrategy::OptimalConsolidation); diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml deleted file mode 100644 index fb31a604e..000000000 --- a/test-utils/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "dashcore-test-utils" -version = "0.1.0" -edition = "2021" -authors = ["The Dash Core developers"] -license = "MIT" -repository = "https://github.com/dashpay/rust-dashcore/" -documentation = "https://docs.rs/dashcore-test-utils/" -description = "Test utilities for rust-dashcore workspace" - -[dependencies] -dashcore = { path = "../dash" } -dashcore_hashes = { path = "../hashes" } -hex = "0.4" -rand = "0.8" -chrono = "0.4" -uuid = { version = "1.0", features = ["v4"] } -tokio = { version = "1.0", features = ["time"], optional = true } - -[features] -async = ["tokio"] - -[dev-dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" diff --git a/test-utils/src/builders.rs b/test-utils/src/builders.rs deleted file mode 100644 index 9fbcc6411..000000000 --- a/test-utils/src/builders.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Test data builders for creating test objects - -use chrono::Utc; -use dashcore::blockdata::block; -use dashcore::blockdata::transaction::special_transaction::TransactionPayload; -use dashcore::hash_types::{BlockHash, TxMerkleNode, Txid}; -use dashcore::ScriptBuf; -use dashcore::{Header, OutPoint, Transaction, TxIn, TxOut}; -use dashcore_hashes::Hash; -use rand::Rng; - -/// Builder for creating test block headers -pub struct TestHeaderBuilder { - version: block::Version, - prev_blockhash: BlockHash, - merkle_root: TxMerkleNode, - time: u32, - bits: dashcore::CompactTarget, - nonce: u32, -} - -impl Default for TestHeaderBuilder { - fn default() -> Self { - Self { - version: block::Version::from_consensus(536870912), // Version 0x20000000 - prev_blockhash: BlockHash::all_zeros(), - merkle_root: TxMerkleNode::all_zeros(), - time: Utc::now().timestamp() as u32, - bits: dashcore::CompactTarget::from_consensus(0x207fffff), // Easy difficulty - nonce: 0, - } - } -} - -impl TestHeaderBuilder { - pub fn new() -> Self { - Self::default() - } - - pub fn with_version(mut self, version: i32) -> Self { - self.version = block::Version::from_consensus(version); - self - } - - pub fn with_prev_blockhash(mut self, hash: BlockHash) -> Self { - self.prev_blockhash = hash; - self - } - - pub fn with_merkle_root(mut self, root: TxMerkleNode) -> Self { - self.merkle_root = root; - self - } - - pub fn with_time(mut self, time: u32) -> Self { - self.time = time; - self - } - - pub fn with_bits(mut self, bits: u32) -> Self { - self.bits = dashcore::CompactTarget::from_consensus(bits); - self - } - - pub fn with_nonce(mut self, nonce: u32) -> Self { - self.nonce = nonce; - self - } - - pub fn build(self) -> Header { - Header { - version: self.version, - prev_blockhash: self.prev_blockhash, - merkle_root: self.merkle_root, - time: self.time, - bits: self.bits, - nonce: self.nonce, - } - } - - /// Build a header with valid proof of work - pub fn build_with_valid_pow(self) -> Header { - // For testing, we'll just return a header with the current nonce - // Real PoW validation would be too slow for tests - self.build() - } -} - -/// Builder for creating test transactions -pub struct TestTransactionBuilder { - version: u16, - lock_time: u32, - inputs: Vec, - outputs: Vec, - special_transaction_payload: Option, -} - -impl Default for TestTransactionBuilder { - fn default() -> Self { - Self { - version: 1, - lock_time: 0, - inputs: vec![], - outputs: vec![], - special_transaction_payload: None, - } - } -} - -impl TestTransactionBuilder { - pub fn new() -> Self { - Self::default() - } - - pub fn with_version(mut self, version: u16) -> Self { - self.version = version; - self - } - - pub fn with_lock_time(mut self, lock_time: u32) -> Self { - self.lock_time = lock_time; - self - } - - pub fn add_input(mut self, txid: Txid, vout: u32) -> Self { - let input = TxIn { - previous_output: OutPoint { - txid, - vout, - }, - script_sig: ScriptBuf::new(), - sequence: 0xffffffff, - witness: dashcore::Witness::new(), - }; - self.inputs.push(input); - self - } - - pub fn add_output(mut self, value: u64, script_pubkey: ScriptBuf) -> Self { - let output = TxOut { - value, - script_pubkey, - }; - self.outputs.push(output); - self - } - - pub fn with_special_payload(mut self, payload: TransactionPayload) -> Self { - self.special_transaction_payload = Some(payload); - self - } - - pub fn build(self) -> Transaction { - Transaction { - version: self.version, - lock_time: self.lock_time, - input: self.inputs, - output: self.outputs, - special_transaction_payload: self.special_transaction_payload, - } - } -} - -pub fn create_transaction_to_address(address: &dashcore::Address, value: u64) -> Transaction { - TestTransactionBuilder::new() - .add_input(random_txid(), 0) - .add_output(value, address.script_pubkey()) - .build() -} - -/// Create a chain of test headers -pub fn create_header_chain(count: usize, start_height: u32) -> Vec
{ - let mut headers = Vec::with_capacity(count); - let mut prev_hash = BlockHash::all_zeros(); - - for i in 0..count { - let header = TestHeaderBuilder::new() - .with_prev_blockhash(prev_hash) - .with_time(1_600_000_000 + (start_height + i as u32) * 600) - .build(); - - prev_hash = header.block_hash(); - headers.push(header); - } - - headers -} - -/// Create a random transaction ID -pub fn random_txid() -> Txid { - let mut rng = rand::thread_rng(); - let mut bytes = [0u8; 32]; - rng.fill(&mut bytes); - Txid::from_slice(&bytes).unwrap() -} - -/// Create a random block hash -pub fn random_block_hash() -> BlockHash { - let mut rng = rand::thread_rng(); - let mut bytes = [0u8; 32]; - rng.fill(&mut bytes); - BlockHash::from_slice(&bytes).unwrap() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_header_builder() { - let header = TestHeaderBuilder::new().with_version(2).with_nonce(12345).build(); - - assert_eq!(header.version, block::Version::from_consensus(2)); - assert_eq!(header.nonce, 12345); - } - - #[test] - fn test_transaction_builder() { - let tx = TestTransactionBuilder::new() - .with_version(2) - .add_input(random_txid(), 0) - .add_output(50000, ScriptBuf::new()) - .build(); - - assert_eq!(tx.version, 2); - assert_eq!(tx.input.len(), 1); - assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value, 50000); - } - - #[test] - fn test_header_chain_creation() { - let chain = create_header_chain(10, 0); - - assert_eq!(chain.len(), 10); - - // Verify chain linkage - for i in 1..chain.len() { - assert_eq!(chain[i].prev_blockhash, chain[i - 1].block_hash()); - } - } -} diff --git a/test-utils/src/fixtures.rs b/test-utils/src/fixtures.rs deleted file mode 100644 index 4f4324abc..000000000 --- a/test-utils/src/fixtures.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Common test fixtures and constants - -use dashcore::hash_types::{BlockHash, Txid}; -use dashcore_hashes::Hash; -use hex::decode; - -/// Genesis block hash for mainnet -pub const MAINNET_GENESIS_HASH: &str = - "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6"; - -/// Genesis block hash for testnet -pub const TESTNET_GENESIS_HASH: &str = - "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c"; - -/// Common test addresses -pub mod addresses { - pub const MAINNET_P2PKH: &str = "XcQjD5Gs5i6kLmfFGJC3aS14PdLp1bEDk8"; - pub const MAINNET_P2SH: &str = "7gnwGHt17heGpG9CrJQjqXDLpTGeLpJV8s"; - pub const TESTNET_P2PKH: &str = "yNDp7n5JHJnG4yLJbD8pSr8YKuhrFERCTG"; - pub const TESTNET_P2SH: &str = "8j7NfpSwYJrnQKJvvbFckbE9NCUjYCpPN2"; -} - -/// Get mainnet genesis block hash -pub fn mainnet_genesis_hash() -> BlockHash { - let bytes = decode(MAINNET_GENESIS_HASH).unwrap(); - let mut reversed = [0u8; 32]; - reversed.copy_from_slice(&bytes); - reversed.reverse(); - BlockHash::from_slice(&reversed).unwrap() -} - -/// Get testnet genesis block hash -pub fn testnet_genesis_hash() -> BlockHash { - let bytes = decode(TESTNET_GENESIS_HASH).unwrap(); - let mut reversed = [0u8; 32]; - reversed.copy_from_slice(&bytes); - reversed.reverse(); - BlockHash::from_slice(&reversed).unwrap() -} - -/// Create a deterministic test block hash from a u32 identifier -pub fn test_block_hash(id: u32) -> BlockHash { - let mut bytes = [0u8; 32]; - bytes[..4].copy_from_slice(&id.to_le_bytes()); - BlockHash::from_byte_array(bytes) -} - -/// Common test transaction IDs -pub mod txids { - use super::*; - - /// Example coinbase transaction - pub fn example_coinbase_txid() -> Txid { - Txid::from_slice( - &decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap() - } - - /// Example regular transaction - pub fn example_regular_txid() -> Txid { - Txid::from_slice( - &decode("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468").unwrap(), - ) - .unwrap() - } -} - -/// Test network parameters -pub mod network_params { - pub const MAINNET_PORT: u16 = 9999; - pub const TESTNET_PORT: u16 = 19999; - pub const REGTEST_PORT: u16 = 19899; - - pub const PROTOCOL_VERSION: u32 = 70228; - pub const MIN_PEER_PROTO_VERSION: u32 = 70215; -} - -/// Common block heights -pub mod heights { - pub const GENESIS: u32 = 0; - pub const DIP0001_HEIGHT_MAINNET: u32 = 782208; - pub const DIP0001_HEIGHT_TESTNET: u32 = 4001; - pub const DIP0003_HEIGHT_MAINNET: u32 = 1028160; - pub const DIP0003_HEIGHT_TESTNET: u32 = 7000; -} - -/// Test quorum data -pub mod quorums { - /// Example quorum hash - pub const EXAMPLE_QUORUM_HASH: &str = - "0000000000000000000000000000000000000000000000000000000000000001"; - - /// Example quorum public key (48 bytes) - pub const EXAMPLE_QUORUM_PUBKEY: &[u8; 48] = - b"000000000000000000000000000000000000000000000000"; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_genesis_hashes() { - let mainnet = mainnet_genesis_hash(); - let testnet = testnet_genesis_hash(); - - assert_ne!(mainnet, testnet); - - // Create expected BlockHash instances from the constants for proper comparison - let expected_mainnet = { - let bytes = decode(MAINNET_GENESIS_HASH).unwrap(); - let mut reversed = [0u8; 32]; - reversed.copy_from_slice(&bytes); - reversed.reverse(); - BlockHash::from_slice(&reversed).unwrap() - }; - - let expected_testnet = { - let bytes = decode(TESTNET_GENESIS_HASH).unwrap(); - let mut reversed = [0u8; 32]; - reversed.copy_from_slice(&bytes); - reversed.reverse(); - BlockHash::from_slice(&reversed).unwrap() - }; - - assert_eq!(mainnet, expected_mainnet); - assert_eq!(testnet, expected_testnet); - } - - #[test] - fn test_txid_fixtures() { - let coinbase = txids::example_coinbase_txid(); - let regular = txids::example_regular_txid(); - - assert_ne!(coinbase, regular); - let coinbase_bytes: &[u8] = coinbase.as_ref(); - assert_eq!(coinbase_bytes, &[0u8; 32]); - } -} diff --git a/test-utils/src/helpers.rs b/test-utils/src/helpers.rs deleted file mode 100644 index 5f26a3ab5..000000000 --- a/test-utils/src/helpers.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! Test helper functions and utilities - -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::Mutex; - -/// Mock storage for testing -pub struct MockStorage { - data: Arc>>, -} - -impl MockStorage { - pub fn new() -> Self { - Self { - data: Arc::new(Mutex::new(HashMap::new())), - } - } - - pub fn insert(&self, key: K, value: V) { - self.data.lock().unwrap().insert(key, value); - } - - pub fn get(&self, key: &K) -> Option { - self.data.lock().unwrap().get(key).cloned() - } - - pub fn remove(&self, key: &K) -> Option { - self.data.lock().unwrap().remove(key) - } - - pub fn clear(&self) { - self.data.lock().unwrap().clear(); - } - - pub fn len(&self) -> usize { - self.data.lock().unwrap().len() - } - - pub fn is_empty(&self) -> bool { - self.data.lock().unwrap().is_empty() - } -} - -impl Default for MockStorage { - fn default() -> Self { - Self { - data: Arc::new(Mutex::new(HashMap::new())), - } - } -} - -/// Test error injection helper -pub struct ErrorInjector { - should_fail: Arc>, - fail_count: Arc>, -} - -impl ErrorInjector { - pub fn new() -> Self { - Self { - should_fail: Arc::new(Mutex::new(false)), - fail_count: Arc::new(Mutex::new(0)), - } - } - - /// Enable error injection - pub fn enable(&self) { - *self.should_fail.lock().unwrap() = true; - } - - /// Disable error injection - pub fn disable(&self) { - *self.should_fail.lock().unwrap() = false; - } - - /// Set to fail after n successful calls - pub fn fail_after(&self, n: usize) { - *self.fail_count.lock().unwrap() = n; - } - - /// Check if should inject error - pub fn should_fail(&self) -> bool { - let mut count = self.fail_count.lock().unwrap(); - if *count > 0 { - *count -= 1; - false - } else { - *self.should_fail.lock().unwrap() - } - } -} - -impl Default for ErrorInjector { - fn default() -> Self { - Self::new() - } -} - -/// Assert that two byte slices are equal, with helpful error message -pub fn assert_bytes_eq(actual: &[u8], expected: &[u8]) { - if actual != expected { - panic!( - "Byte arrays not equal\nActual: {:?}\nExpected: {:?}\nActual hex: {}\nExpected hex: {}", - actual, - expected, - hex::encode(actual), - hex::encode(expected) - ); - } -} - -/// Create a temporary directory that's cleaned up on drop -pub struct TempDir { - path: std::path::PathBuf, -} - -impl TempDir { - pub fn new() -> std::io::Result { - let path = std::env::temp_dir().join(format!("dashcore-test-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&path)?; - Ok(Self { - path, - }) - } - - pub fn path(&self) -> &std::path::Path { - &self.path - } -} - -impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.path); - } -} - -/// Helper to run async tests with timeout -#[cfg(feature = "async")] -pub async fn with_timeout(duration: std::time::Duration, future: F) -> Result -where - F: std::future::Future, -{ - tokio::time::timeout(duration, future).await.map_err(|_| "Test timed out") -} - -/// Helper to assert that a closure panics with a specific message -pub fn assert_panic_contains(f: F, expected_msg: &str) { - let result = std::panic::catch_unwind(f); - match result { - Ok(_) => panic!( - "Expected panic with message containing '{}', but no panic occurred", - expected_msg - ), - Err(panic_info) => { - let msg = if let Some(s) = panic_info.downcast_ref::() { - s.clone() - } else if let Some(s) = panic_info.downcast_ref::<&str>() { - s.to_string() - } else { - format!("{:?}", panic_info) - }; - - if !msg.contains(expected_msg) { - panic!("Expected panic message to contain '{}', but got '{}'", expected_msg, msg); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_mock_storage() { - let storage: MockStorage = MockStorage::new(); - - storage.insert("key1".to_string(), 42); - assert_eq!(storage.get(&"key1".to_string()), Some(42)); - assert_eq!(storage.len(), 1); - - storage.remove(&"key1".to_string()); - assert_eq!(storage.get(&"key1".to_string()), None); - assert_eq!(storage.len(), 0); - } - - #[test] - fn test_error_injector() { - let injector = ErrorInjector::new(); - - assert!(!injector.should_fail()); - - injector.enable(); - assert!(injector.should_fail()); - - injector.disable(); - injector.fail_after(2); - assert!(!injector.should_fail()); // First call - assert!(!injector.should_fail()); // Second call - injector.enable(); // Need to enable for the third call to fail - assert!(injector.should_fail()); // Third call (fails) - } - - #[test] - fn test_assert_panic_contains() { - assert_panic_contains(|| panic!("This is a test panic"), "test panic"); - } - - #[test] - #[should_panic(expected = "Expected panic")] - fn test_assert_panic_contains_no_panic() { - assert_panic_contains(|| { /* no panic */ }, "anything"); - } -} diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs deleted file mode 100644 index 81c27f9da..000000000 --- a/test-utils/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Test utilities for rust-dashcore workspace -//! -//! This crate provides common test utilities, builders, and helpers -//! used across the rust-dashcore workspace for testing. - -pub mod builders; -pub mod fixtures; -pub mod helpers; -pub mod macros; - -pub use builders::*; -pub use fixtures::*; -pub use helpers::*; diff --git a/test-utils/src/macros.rs b/test-utils/src/macros.rs deleted file mode 100644 index 178e47b16..000000000 --- a/test-utils/src/macros.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Test macros for common testing patterns - -/// Macro to test serde round-trip serialization -#[macro_export] -macro_rules! test_serde_round_trip { - ($value:expr) => {{ - let serialized = serde_json::to_string(&$value).expect("Failed to serialize"); - let deserialized = serde_json::from_str(&serialized).expect("Failed to deserialize"); - assert_eq!($value, deserialized, "Serde round-trip failed"); - }}; -} - -/// Macro to test binary serialization round-trip -#[macro_export] -macro_rules! test_serialize_round_trip { - ($value:expr) => {{ - use dashcore::consensus::encode::{deserialize, serialize}; - let serialized = serialize(&$value); - let deserialized: Result<_, _> = deserialize(&serialized); - assert_eq!( - $value, - deserialized.expect("Failed to deserialize"), - "Binary round-trip failed" - ); - }}; -} - -/// Macro to assert an error contains a specific substring -#[macro_export] -macro_rules! assert_error_contains { - ($result:expr, $expected:expr) => {{ - match $result { - Ok(_) => panic!("Expected error containing '{}', but got Ok", $expected), - Err(e) => { - let error_str = format!("{}", e); - if !error_str.contains($expected) { - panic!("Expected error to contain '{}', but got '{}'", $expected, error_str); - } - } - } - }}; -} - -/// Macro to create a test with multiple test cases -#[macro_export] -macro_rules! parameterized_test { - ($test_name:ident, $test_fn:expr, $( ($name:expr, $($arg:expr),+) ),+ $(,)?) => { - #[test] - fn $test_name() { - $( - println!("Running test case: {}", $name); - $test_fn($($arg),+); - )+ - } - }; -} - -/// Macro to assert two results are equal, handling both Ok and Err cases -#[macro_export] -macro_rules! assert_results_eq { - ($left:expr, $right:expr) => {{ - match (&$left, &$right) { - (Ok(l), Ok(r)) => assert_eq!(l, r, "Ok values not equal"), - (Err(l), Err(r)) => { - assert_eq!(format!("{}", l), format!("{}", r), "Error messages not equal") - } - (Ok(_), Err(e)) => panic!("Expected Ok, got Err({})", e), - (Err(e), Ok(_)) => panic!("Expected Err({}), got Ok", e), - } - }}; -} - -/// Macro to measure execution time of a block -#[macro_export] -macro_rules! measure_time { - ($label:expr, $block:block) => {{ - let start = std::time::Instant::now(); - let result = $block; - let duration = start.elapsed(); - println!("{}: {:?}", $label, duration); - result - }}; -} - -#[cfg(test)] -mod tests { - use serde::{Deserialize, Serialize}; - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct TestStruct { - field: String, - } - - #[test] - fn test_serde_macro() { - let value = TestStruct { - field: "test".to_string(), - }; - test_serde_round_trip!(value); - } - - #[test] - fn test_error_contains_macro() { - let result: Result<(), String> = Err("This is an error message".to_string()); - assert_error_contains!(result, "error message"); - } - - #[test] - #[should_panic(expected = "Expected error")] - fn test_error_contains_macro_with_ok() { - let result: Result = Ok(42); - assert_error_contains!(result, "anything"); - } - - parameterized_test!( - test_addition, - |a: i32, b: i32, expected: i32| { - assert_eq!(a + b, expected); - }, - ("1+1", 1, 1, 2), - ("2+3", 2, 3, 5), - ("0+0", 0, 0, 0) - ); - - #[test] - fn test_measure_time_macro() { - let result = measure_time!("Test operation", { - std::thread::sleep(std::time::Duration::from_millis(10)); - 42 - }); - assert_eq!(result, 42); - } -}