From fb7ef8fd331209f937586f323d1b75ddef2da9df Mon Sep 17 00:00:00 2001 From: xdustinface Date: Wed, 3 Dec 2025 20:06:02 +1100 Subject: [PATCH] refactor: Cleanup SPV validation - Some simplifications and name changes - Some unused code - Add header validation timing logs - Dropping `ValidationManager` as its not really used and not needed - Dropping the `HeaderValidator` making `validate_headers` a standalone function now located in `sync::headers::validation` - Making the `ValidationMode::Full` mode checking PoW by default, not based on yet another parameter --- dash-spv/src/client/core.rs | 2 - dash-spv/src/client/lifecycle.rs | 5 - dash-spv/src/client/sync_coordinator.rs | 6 +- dash-spv/src/validation/headers.rs | 202 ++---------- dash-spv/src/validation/headers_edge_test.rs | 106 ++----- dash-spv/src/validation/headers_test.rs | 229 ++------------ dash-spv/src/validation/manager_test.rs | 306 ------------------- dash-spv/src/validation/mod.rs | 85 +----- 8 files changed, 81 insertions(+), 860 deletions(-) delete mode 100644 dash-spv/src/validation/manager_test.rs diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index 33c44c8c2..341b25d03 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -22,7 +22,6 @@ use crate::storage::StorageManager; use crate::sync::filters::FilterNotificationSender; use crate::sync::SyncManager; use crate::types::{ChainState, DetailedSyncProgress, MempoolState, SpvEvent, SpvStats}; -use crate::validation::ValidationManager; use key_wallet_manager::wallet_interface::WalletInterface; use super::{BlockProcessingTask, ClientConfig, StatusDisplay}; @@ -133,7 +132,6 @@ pub struct DashSpvClient, - pub(super) validation: ValidationManager, pub(super) chainlock_manager: Arc, pub(super) running: Arc>, #[cfg(feature = "terminal-ui")] diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index aa84f2c67..dfcdaa1bf 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -19,7 +19,6 @@ use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::sync::SyncManager; use crate::types::{ChainState, MempoolState, SpvStats}; -use crate::validation::ValidationManager; use dashcore::network::constants::NetworkExt; use dashcore_hashes::Hash; use key_wallet_manager::wallet_interface::WalletInterface; @@ -61,9 +60,6 @@ impl< ) .map_err(SpvError::Sync)?; - // Create validation manager - let validation = ValidationManager::new(config.validation_mode); - // Create ChainLock manager let chainlock_manager = Arc::new(ChainLockManager::new(true)); @@ -87,7 +83,6 @@ impl< storage, wallet, sync_manager, - validation, chainlock_manager, running: Arc::new(RwLock::new(false)), #[cfg(feature = "terminal-ui")] diff --git a/dash-spv/src/client/sync_coordinator.rs b/dash-spv/src/client/sync_coordinator.rs index c84287438..863d4c49d 100644 --- a/dash-spv/src/client/sync_coordinator.rs +++ b/dash-spv/src/client/sync_coordinator.rs @@ -18,6 +18,8 @@ use crate::network::constants::MESSAGE_RECEIVE_TIMEOUT; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::{DetailedSyncProgress, SyncProgress}; +use crate::validation::validate_headers; +use crate::ValidationMode; use key_wallet_manager::wallet_interface::WalletInterface; use std::time::{Duration, Instant, SystemTime}; use tokio::sync::mpsc::UnboundedReceiver; @@ -946,7 +948,9 @@ impl< // Validate headers before adding to chain state { // Validate the batch of headers - if let Err(e) = self.validation.validate_header_chain(&headers, false) { + // Use basic validation only, the headers anyway are already validated since they + // come from storage. + if let Err(e) = validate_headers(&headers, ValidationMode::Basic) { tracing::error!( "Header validation failed for range {}..{}: {:?}", current_height, diff --git a/dash-spv/src/validation/headers.rs b/dash-spv/src/validation/headers.rs index 8baa281a9..a4e7deffa 100644 --- a/dash-spv/src/validation/headers.rs +++ b/dash-spv/src/validation/headers.rs @@ -1,196 +1,58 @@ //! Header validation functionality. -use dashcore::{ - block::Header as BlockHeader, error::Error as DashError, network::constants::NetworkExt, - Network, -}; +use dashcore::{block::Header as BlockHeader, error::Error as DashError}; +use std::time::Instant; use crate::error::{ValidationError, ValidationResult}; use crate::types::ValidationMode; -/// Validates block headers. -pub struct HeaderValidator { - mode: ValidationMode, - network: Network, -} - -impl HeaderValidator { - /// Create a new header validator. - pub fn new(mode: ValidationMode) -> Self { - Self { - mode, - network: Network::Dash, // Default to mainnet - } +/// Validate a chain of headers considering the validation mode. +pub fn validate_headers(headers: &[BlockHeader], mode: ValidationMode) -> ValidationResult<()> { + if mode == ValidationMode::None { + tracing::debug!("Skipping header validation: disabled"); + return Ok(()); } - /// Set validation mode. - pub fn set_mode(&mut self, mode: ValidationMode) { - self.mode = mode; - } - - /// Set network. - pub fn set_network(&mut self, network: Network) { - self.network = network; - } + let start = Instant::now(); - /// Validate a single header. - pub fn validate( - &self, - header: &BlockHeader, - prev_header: Option<&BlockHeader>, - ) -> ValidationResult<()> { - match self.mode { - ValidationMode::None => Ok(()), - ValidationMode::Basic => self.validate_basic(header, prev_header), - ValidationMode::Full => self.validate_full(header, prev_header), - } - } - - /// Basic header validation (structure and chain continuity). - fn validate_basic( - &self, - header: &BlockHeader, - prev_header: Option<&BlockHeader>, - ) -> ValidationResult<()> { + let mut prev_block_hash = None; + for header in headers { // Check chain continuity if we have previous header - if let Some(prev) = prev_header { - if header.prev_blockhash != prev.block_hash() { + if let Some(prev) = prev_block_hash { + if header.prev_blockhash != prev { return Err(ValidationError::InvalidHeaderChain( "Header does not connect to previous header".to_string(), )); } } - Ok(()) - } - - /// Full header validation (includes PoW verification). - fn validate_full( - &self, - header: &BlockHeader, - prev_header: Option<&BlockHeader>, - ) -> ValidationResult<()> { - // First do basic validation - self.validate_basic(header, prev_header)?; - - // Validate proof of work with X11 hashing (now enabled with core-block-hash-use-x11 feature) - let target = header.target(); - if let Err(e) = header.validate_pow(target) { - match e { - DashError::BlockBadProofOfWork => { - return Err(ValidationError::InvalidProofOfWork); - } - DashError::BlockBadTarget => { - return Err(ValidationError::InvalidHeaderChain("Invalid target".to_string())); - } - _ => { - return Err(ValidationError::InvalidHeaderChain(format!( + if mode == ValidationMode::Full { + // Validate proof of work with X11 hashing + let target = header.target(); + if let Err(e) = header.validate_pow(target) { + return match e { + DashError::BlockBadProofOfWork => Err(ValidationError::InvalidProofOfWork), + DashError::BlockBadTarget => { + Err(ValidationError::InvalidHeaderChain("Invalid target".to_string())) + } + _ => Err(ValidationError::InvalidHeaderChain(format!( "PoW validation error: {:?}", e - ))); - } - } - } - - Ok(()) - } - - /// Validate a chain of headers with basic validation. - pub fn validate_chain_basic(&self, headers: &[BlockHeader]) -> ValidationResult<()> { - // Respect ValidationMode::None - if self.mode == ValidationMode::None { - return Ok(()); - } - - if headers.is_empty() { - return Ok(()); - } - - // Validate chain continuity - for i in 1..headers.len() { - let header = &headers[i]; - let prev_header = &headers[i - 1]; - - self.validate_basic(header, Some(prev_header))?; - } - - tracing::debug!("Basic header chain validation passed for {} headers", headers.len()); - Ok(()) - } - - /// Validate a chain of headers with full validation. - pub fn validate_chain_full( - &self, - headers: &[BlockHeader], - validate_pow: bool, - ) -> ValidationResult<()> { - // Respect ValidationMode::None - if self.mode == ValidationMode::None { - return Ok(()); - } - - if headers.is_empty() { - return Ok(()); - } - - // For the first header, we might need to check it connects to genesis or our existing chain - // For now, we'll just validate internal chain continuity - - // Validate each header in the chain - for i in 0..headers.len() { - let header = &headers[i]; - let prev_header = if i > 0 { - Some(&headers[i - 1]) - } else { - None - }; - - if validate_pow { - self.validate_full(header, prev_header)?; - } else { - self.validate_basic(header, prev_header)?; + ))), + }; } } - tracing::debug!("Full header chain validation passed for {} headers", headers.len()); - Ok(()) + prev_block_hash = Some(header.block_hash()); } - /// Validate headers connect to genesis block. - pub fn validate_connects_to_genesis(&self, headers: &[BlockHeader]) -> ValidationResult<()> { - if headers.is_empty() { - return Ok(()); - } - - let genesis_hash = self.network.known_genesis_block_hash().ok_or_else(|| { - ValidationError::Consensus("No known genesis hash for network".to_string()) - })?; - - if headers[0].prev_blockhash != genesis_hash { - return Err(ValidationError::InvalidHeaderChain( - "First header doesn't connect to genesis".to_string(), - )); - } - - Ok(()) - } - - /// Validate difficulty adjustment (simplified for SPV). - pub fn validate_difficulty_adjustment( - &self, - header: &BlockHeader, - prev_header: &BlockHeader, - ) -> ValidationResult<()> { - // For SPV client, we trust that the network has validated difficulty properly - // We only check basic constraints - - // For SPV we trust the network for difficulty validation - // TODO: Implement proper difficulty validation if needed - let _prev_target = prev_header.target(); - let _current_target = header.target(); - - Ok(()) - } + tracing::debug!( + "Header chain validation passed for {} headers in mode: {:?}, duration: {:?}", + headers.len(), + mode, + start.elapsed(), + ); + Ok(()) } #[cfg(test)] diff --git a/dash-spv/src/validation/headers_edge_test.rs b/dash-spv/src/validation/headers_edge_test.rs index 7ebd12393..c0b87b22e 100644 --- a/dash-spv/src/validation/headers_edge_test.rs +++ b/dash-spv/src/validation/headers_edge_test.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use super::super::*; + use super::super::validate_headers; use crate::error::ValidationError; use crate::types::ValidationMode; use dashcore::{ @@ -33,25 +33,20 @@ mod tests { #[test] fn test_genesis_block_validation() { - let mut validator = HeaderValidator::new(ValidationMode::Full); - for network in [Network::Dash, Network::Testnet, Network::Regtest] { - validator.set_network(network); let genesis = genesis_block(network).header; // Genesis block should validate with no previous header - assert!(validator.validate(&genesis, None).is_ok()); + assert!(validate_headers(&[genesis], ValidationMode::Full).is_ok()); // Genesis block with itself as previous should fail - let result = validator.validate(&genesis, Some(&genesis)); + let result = validate_headers(&[genesis, genesis], ValidationMode::Full); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } } #[test] fn test_maximum_target_validation() { - let validator = HeaderValidator::new(ValidationMode::Full); - // Create header with maximum allowed target (easiest difficulty) let max_target_bits = 0x1e0fffff; // Maximum target for testing let header = create_test_header_with_params( @@ -66,13 +61,11 @@ mod tests { ); // Should validate (though PoW might fail - that's expected) - let _ = validator.validate(&header, None); + let _ = validate_headers(&[header], ValidationMode::Full); } #[test] fn test_minimum_target_validation() { - let validator = HeaderValidator::new(ValidationMode::Full); - // Create header with very low target (hardest difficulty) let min_target_bits = 0x17000000; // Very difficult target let header = create_test_header_with_params( @@ -87,14 +80,12 @@ mod tests { ); // Should fail PoW validation - let result = validator.validate(&header, None); + let result = validate_headers(&[header], ValidationMode::Full); assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); } #[test] fn test_zero_prev_blockhash() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // First header with zero prev_blockhash (like genesis) let header1 = create_test_header_with_params( 0x20000000, @@ -117,17 +108,15 @@ mod tests { 2, ); - // Should validate when no previous header provided - assert!(validator.validate(&header1, None).is_ok()); + // Should validate single header + assert!(validate_headers(&[header1], ValidationMode::Basic).is_ok()); // Should validate chain continuity - assert!(validator.validate(&header2, Some(&header1)).is_ok()); + assert!(validate_headers(&[header1, header2], ValidationMode::Basic).is_ok()); } #[test] fn test_all_ff_prev_blockhash() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Header with all 0xFF prev_blockhash let header = create_test_header_with_params( 0x20000000, @@ -140,10 +129,10 @@ mod tests { 1, ); - // Should validate when no previous header - assert!(validator.validate(&header, None).is_ok()); + // Should validate when single header + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); - // Create a previous header that would match + // Create a previous header that would NOT match let prev_header = create_test_header_with_params( 0x20000000, dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -156,14 +145,12 @@ mod tests { ); // Should fail chain continuity - let result = validator.validate(&header, Some(&prev_header)); + let result = validate_headers(&[prev_header, header], ValidationMode::Basic); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] fn test_timestamp_boundaries() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Test with minimum timestamp (0) let header_min_time = create_test_header_with_params( 0x20000000, @@ -175,7 +162,7 @@ mod tests { 0x1e0fffff, 1, ); - assert!(validator.validate(&header_min_time, None).is_ok()); + assert!(validate_headers(&[header_min_time], ValidationMode::Basic).is_ok()); // Test with maximum timestamp (u32::MAX) let header_max_time = create_test_header_with_params( @@ -188,13 +175,11 @@ mod tests { 0x1e0fffff, 2, ); - assert!(validator.validate(&header_max_time, None).is_ok()); + assert!(validate_headers(&[header_max_time], ValidationMode::Basic).is_ok()); } #[test] fn test_version_edge_cases() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Test various version values let versions = [0, 1, 0x20000000, 0x20000001, u32::MAX]; @@ -211,14 +196,12 @@ mod tests { ); // All versions should pass basic validation - assert!(validator.validate(&header, None).is_ok()); + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); } } #[test] fn test_large_chain_validation() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Create a large chain let chain_size = 1000; let mut headers = Vec::with_capacity(chain_size); @@ -240,7 +223,7 @@ mod tests { } // Should validate entire chain - assert!(validator.validate_chain_basic(&headers).is_ok()); + assert!(validate_headers(&headers, ValidationMode::Basic).is_ok()); // Break the chain in the middle let broken_index = chain_size / 2; @@ -256,14 +239,12 @@ mod tests { ); // Should fail validation - let result = validator.validate_chain_basic(&headers); + let result = validate_headers(&headers, ValidationMode::Basic); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] fn test_single_header_chain_validation() { - let validator = HeaderValidator::new(ValidationMode::Full); - let header = create_test_header_with_params( 0x20000000, dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -275,17 +256,12 @@ mod tests { 1, ); - let headers = vec![header]; - - // Single header chain should validate in both basic and full modes - assert!(validator.validate_chain_basic(&headers).is_ok()); - assert!(validator.validate_chain_full(&headers, false).is_ok()); + // Single header chain should validate + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); } #[test] fn test_duplicate_headers_in_chain() { - let validator = HeaderValidator::new(ValidationMode::Basic); - let header = create_test_header_with_params( 0x20000000, dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -301,14 +277,12 @@ mod tests { let headers = vec![header, header]; // Should fail because second header's prev_blockhash won't match first header's hash - let result = validator.validate_chain_basic(&headers); + let result = validate_headers(&headers, ValidationMode::Basic); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] fn test_merkle_root_variations() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Test various merkle root patterns let merkle_patterns = [ [0u8; 32], // All zeros @@ -332,47 +306,9 @@ mod tests { ); // All merkle roots should be valid for basic validation - assert!(validator.validate(&header, None).is_ok()); + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); prev_hash = header.block_hash(); } } - - #[test] - fn test_mode_switching_during_chain_validation() { - let mut validator = HeaderValidator::new(ValidationMode::None); - - // Create headers with invalid PoW - let mut headers = vec![]; - let mut prev_hash = dashcore::BlockHash::from_raw_hash( - dashcore_hashes::hash_x11::Hash::from_byte_array([0; 32]), - ); - - for i in 0..3 { - let header = create_test_header_with_params( - 0x20000000, - prev_hash, - [i as u8; 32], - 1234567890 + i * 600, - 0x1d00ffff, // Difficult target - 0, // Invalid nonce - ); - prev_hash = header.block_hash(); - headers.push(header); - } - - // Should pass with None mode (ValidationMode::None always passes) - let result = validator.validate_chain_full(&headers, true); - assert!(result.is_ok(), "ValidationMode::None should always pass, but got: {:?}", result); - - // Switch to Full mode - validator.set_mode(ValidationMode::Full); - - // Should now fail due to invalid PoW - let result = validator.validate_chain_full(&headers, true); - assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); - - // But should pass without PoW check - assert!(validator.validate_chain_full(&headers, false).is_ok()); - } } diff --git a/dash-spv/src/validation/headers_test.rs b/dash-spv/src/validation/headers_test.rs index 7c53bf636..6853467ad 100644 --- a/dash-spv/src/validation/headers_test.rs +++ b/dash-spv/src/validation/headers_test.rs @@ -2,14 +2,10 @@ #[cfg(test)] mod tests { - use super::super::*; + use super::super::validate_headers; use crate::error::ValidationError; use crate::types::ValidationMode; - use dashcore::{ - block::{Header as BlockHeader, Version}, - blockdata::constants::genesis_block, - Network, - }; + use dashcore::block::{Header as BlockHeader, Version}; use dashcore_hashes::Hash; /// Create a test header with given parameters @@ -31,7 +27,6 @@ mod tests { #[test] fn test_validation_mode_none_always_passes() { - let validator = HeaderValidator::new(ValidationMode::None); let header = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( [0; 32], @@ -42,7 +37,7 @@ mod tests { ); // Should pass with no previous header - assert!(validator.validate(&header, None).is_ok()); + assert!(validate_headers(&[header], ValidationMode::None).is_ok()); // Should pass even with invalid chain continuity let prev_header = create_test_header( @@ -53,13 +48,11 @@ mod tests { 0x1e0fffff, 1234567890, ); - assert!(validator.validate(&header, Some(&prev_header)).is_ok()); + assert!(validate_headers(&[prev_header, header], ValidationMode::None).is_ok()); } #[test] fn test_basic_validation_chain_continuity() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Create two headers that connect properly let header1 = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -72,7 +65,7 @@ mod tests { let header2 = create_test_header(header1.block_hash(), 2, 0x1e0fffff, 1234567900); // Should pass when headers connect - assert!(validator.validate(&header2, Some(&header1)).is_ok()); + assert!(validate_headers(&[header1, header2], ValidationMode::Basic).is_ok()); // Should fail when headers don't connect let disconnected_header = create_test_header( @@ -83,14 +76,12 @@ mod tests { 0x1e0fffff, 1234567910, ); - let result = validator.validate(&disconnected_header, Some(&header1)); + let result = validate_headers(&[header1, disconnected_header], ValidationMode::Basic); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] fn test_basic_validation_no_pow_check() { - let validator = HeaderValidator::new(ValidationMode::Basic); - // Create header with invalid PoW (would fail full validation) let header = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -102,13 +93,11 @@ mod tests { ); // Should pass basic validation (no PoW check) - assert!(validator.validate(&header, None).is_ok()); + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); } #[test] fn test_full_validation_includes_pow() { - let validator = HeaderValidator::new(ValidationMode::Full); - // Create header with invalid PoW let header = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -120,49 +109,21 @@ mod tests { ); // Should fail full validation due to invalid PoW - let result = validator.validate(&header, None); + let result = validate_headers(&[header], ValidationMode::Full); assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); } #[test] - fn test_full_validation_chain_continuity_and_pow() { - let validator = HeaderValidator::new(ValidationMode::Full); - - // Create headers that don't connect - let header1 = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 1, - 0x1e0fffff, - 1234567890, - ); - let disconnected_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 2, - 0x1e0fffff, - 1234567900, - ); - - // Should fail due to chain continuity before PoW check - let result = validator.validate(&disconnected_header, Some(&header1)); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - } - - #[test] - fn test_validate_chain_basic_empty() { - let validator = HeaderValidator::new(ValidationMode::Basic); - let headers: Vec = vec![]; - - // Empty chain should pass - assert!(validator.validate_chain_basic(&headers).is_ok()); + fn test_validate_headers_empty() { + for mode in [ValidationMode::None, ValidationMode::Basic, ValidationMode::Full] { + let headers: Vec = vec![]; + // Empty chain should pass + assert!(validate_headers(&headers, mode).is_ok()); + } } #[test] - fn test_validate_chain_basic_single_header() { - let validator = HeaderValidator::new(ValidationMode::Basic); + fn test_validate_headers_basic_single_header() { let header = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( [0; 32], @@ -171,16 +132,13 @@ mod tests { 0x1e0fffff, 1234567890, ); - let headers = vec![header]; // Single header should pass (no chain validation needed) - assert!(validator.validate_chain_basic(&headers).is_ok()); + assert!(validate_headers(&[header], ValidationMode::Basic).is_ok()); } #[test] - fn test_validate_chain_basic_valid_chain() { - let validator = HeaderValidator::new(ValidationMode::Basic); - + fn test_validate_headers_basic_valid_chain() { // Create a valid chain of headers let mut headers = vec![]; let mut prev_hash = dashcore::BlockHash::from_raw_hash( @@ -194,13 +152,11 @@ mod tests { } // Valid chain should pass - assert!(validator.validate_chain_basic(&headers).is_ok()); + assert!(validate_headers(&headers, ValidationMode::Basic).is_ok()); } #[test] - fn test_validate_chain_basic_broken_chain() { - let validator = HeaderValidator::new(ValidationMode::Basic); - + fn test_validate_headers_basic_broken_chain() { // Create a chain with a break in the middle let header1 = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -223,14 +179,12 @@ mod tests { let headers = vec![header1, header2, header3]; // Should fail due to broken chain - let result = validator.validate_chain_basic(&headers); + let result = validate_headers(&headers, ValidationMode::Basic); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] - fn test_validate_chain_full_with_pow() { - let validator = HeaderValidator::new(ValidationMode::Full); - + fn test_validate_headers_full_with_pow() { // Create headers with invalid PoW let header1 = create_test_header( dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( @@ -240,148 +194,9 @@ mod tests { 0x1d00ffff, // Difficulty that requires real PoW 1234567890, ); - let headers = vec![header1]; // Should fail when PoW validation is enabled - let result = validator.validate_chain_full(&headers, true); + let result = validate_headers(&[header1], ValidationMode::Full); assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); - - // Should pass when PoW validation is disabled - assert!(validator.validate_chain_full(&headers, false).is_ok()); - } - - #[test] - fn test_validate_connects_to_genesis_mainnet() { - let mut validator = HeaderValidator::new(ValidationMode::Basic); - validator.set_network(Network::Dash); - - let genesis = genesis_block(Network::Dash).header; - let valid_header = - create_test_header(genesis.block_hash(), 1, 0x1e0fffff, genesis.time + 600); - - let headers = vec![valid_header]; - - // Should pass when connecting to genesis - assert!(validator.validate_connects_to_genesis(&headers).is_ok()); - - // Should fail when not connecting to genesis - let invalid_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 2, - 0x1e0fffff, - genesis.time + 1200, - ); - let headers = vec![invalid_header]; - - let result = validator.validate_connects_to_genesis(&headers); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - } - - #[test] - fn test_validate_connects_to_genesis_testnet() { - let mut validator = HeaderValidator::new(ValidationMode::Basic); - validator.set_network(Network::Testnet); - - let genesis = genesis_block(Network::Testnet).header; - let valid_header = - create_test_header(genesis.block_hash(), 1, 0x1e0fffff, genesis.time + 600); - - let headers = vec![valid_header]; - - // Should pass when connecting to testnet genesis - assert!(validator.validate_connects_to_genesis(&headers).is_ok()); - } - - #[test] - fn test_validate_connects_to_genesis_empty() { - let validator = HeaderValidator::new(ValidationMode::Basic); - let headers: Vec = vec![]; - - // Empty chain should pass - assert!(validator.validate_connects_to_genesis(&headers).is_ok()); - } - - #[test] - fn test_set_validation_mode() { - let mut validator = HeaderValidator::new(ValidationMode::None); - - // Create header with broken chain continuity - let header1 = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 1, - 0x1e0fffff, - 1234567890, - ); - let disconnected_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 2, - 0x1e0fffff, - 1234567900, - ); - - // Should pass with ValidationMode::None - assert!(validator.validate(&disconnected_header, Some(&header1)).is_ok()); - - // Change to Basic mode - validator.set_mode(ValidationMode::Basic); - - // Should now fail - let result = validator.validate(&disconnected_header, Some(&header1)); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - - // Change back to None - validator.set_mode(ValidationMode::None); - - // Should pass again - assert!(validator.validate(&disconnected_header, Some(&header1)).is_ok()); - } - - #[test] - fn test_network_setting() { - let mut validator = HeaderValidator::new(ValidationMode::Basic); - - // Test with different networks (skip Regtest as it may not have a known genesis hash) - for network in [Network::Dash, Network::Testnet] { - validator.set_network(network); - - let genesis = genesis_block(network).header; - let valid_header = - create_test_header(genesis.block_hash(), 1, 0x1e0fffff, genesis.time + 600); - - let headers = vec![valid_header]; - assert!(validator.validate_connects_to_genesis(&headers).is_ok()); - } - - // For Regtest, just verify we can set the network - validator.set_network(Network::Regtest); - } - - #[test] - fn test_validate_difficulty_adjustment() { - let validator = HeaderValidator::new(ValidationMode::Full); - - let header1 = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 1, - 0x1e0fffff, - 1234567890, - ); - let header2 = create_test_header( - header1.block_hash(), - 2, - 0x1e0ffff0, // Slightly different difficulty - 1234567900, - ); - - // Currently just passes - SPV trusts network for difficulty - assert!(validator.validate_difficulty_adjustment(&header2, &header1).is_ok()); } } diff --git a/dash-spv/src/validation/manager_test.rs b/dash-spv/src/validation/manager_test.rs deleted file mode 100644 index bb8c98a8e..000000000 --- a/dash-spv/src/validation/manager_test.rs +++ /dev/null @@ -1,306 +0,0 @@ -//! Unit tests for ValidationManager. - -#[cfg(test)] -mod tests { - use super::super::*; - use crate::error::ValidationError; - use crate::types::ValidationMode; - use dashcore::{ - block::{Header as BlockHeader, Version}, - InstantLock, OutPoint, Transaction, TxIn, TxOut, - }; - use dashcore_hashes::Hash; - - /// Create a test header - fn create_test_header(prev_hash: dashcore::BlockHash, nonce: u32, bits: u32) -> BlockHeader { - BlockHeader { - version: Version::from_consensus(0x20000000), - prev_blockhash: prev_hash, - merkle_root: dashcore::TxMerkleNode::from_byte_array([0; 32]), - time: 1234567890, - bits: dashcore::CompactTarget::from_consensus(bits), - nonce, - } - } - - /// Create a simple test transaction - fn create_test_transaction() -> Transaction { - Transaction { - version: 1, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint::default(), - script_sig: dashcore::ScriptBuf::new(), - sequence: u32::MAX, - witness: dashcore::Witness::new(), - }], - output: vec![TxOut { - value: 50000, - script_pubkey: dashcore::ScriptBuf::new(), - }], - special_transaction_payload: None, - } - } - - /// Create a test InstantLock - fn create_test_instantlock() -> InstantLock { - let tx = create_test_transaction(); - let txid = tx.txid(); - InstantLock { - version: 1, - inputs: tx.input.into_iter().map(|inp| inp.previous_output).collect(), - txid, - signature: dashcore::bls_sig_utils::BLSSignature::from([0u8; 96]), - cyclehash: dashcore::BlockHash::from_raw_hash( - dashcore_hashes::hash_x11::Hash::from_byte_array([0; 32]), - ), - } - } - - #[test] - fn test_validation_manager_creation() { - let manager = ValidationManager::new(ValidationMode::Basic); - assert_eq!(manager.mode(), ValidationMode::Basic); - - let manager = ValidationManager::new(ValidationMode::Full); - assert_eq!(manager.mode(), ValidationMode::Full); - - let manager = ValidationManager::new(ValidationMode::None); - assert_eq!(manager.mode(), ValidationMode::None); - } - - #[test] - fn test_validation_manager_mode_change() { - let mut manager = ValidationManager::new(ValidationMode::None); - assert_eq!(manager.mode(), ValidationMode::None); - - manager.set_mode(ValidationMode::Basic); - assert_eq!(manager.mode(), ValidationMode::Basic); - - manager.set_mode(ValidationMode::Full); - assert_eq!(manager.mode(), ValidationMode::Full); - } - - #[test] - fn test_header_validation_with_mode_none() { - let manager = ValidationManager::new(ValidationMode::None); - - let header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 0, - 0x1e0fffff, - ); - - // Should always pass with ValidationMode::None - assert!(manager.validate_header(&header, None).is_ok()); - - // Even with invalid chain continuity - let prev_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 1, - 0x1e0fffff, - ); - assert!(manager.validate_header(&header, Some(&prev_header)).is_ok()); - } - - #[test] - fn test_header_validation_with_mode_basic() { - let manager = ValidationManager::new(ValidationMode::Basic); - - // Valid chain continuity - let header1 = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 1, - 0x1e0fffff, - ); - let header2 = create_test_header(header1.block_hash(), 2, 0x1e0fffff); - - assert!(manager.validate_header(&header2, Some(&header1)).is_ok()); - - // Invalid chain continuity - let disconnected_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 3, - 0x1e0fffff, - ); - - let result = manager.validate_header(&disconnected_header, Some(&header1)); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - } - - #[test] - fn test_header_validation_with_mode_full() { - let manager = ValidationManager::new(ValidationMode::Full); - - // Header with invalid PoW - let header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 0, // Invalid nonce - 0x1d00ffff, // Difficulty that requires real PoW - ); - - let result = manager.validate_header(&header, None); - assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); - } - - #[test] - fn test_header_chain_validation_none() { - let manager = ValidationManager::new(ValidationMode::None); - - // Even an empty chain should pass - assert!(manager.validate_header_chain(&[], false).is_ok()); - assert!(manager.validate_header_chain(&[], true).is_ok()); - - // Even broken chains should pass - let headers = vec![ - create_test_header(dashcore::BlockHash::from_byte_array([0; 32]), 1, 0x1e0fffff), - create_test_header(dashcore::BlockHash::from_byte_array([99; 32]), 2, 0x1e0fffff), - ]; - - assert!(manager.validate_header_chain(&headers, false).is_ok()); - assert!(manager.validate_header_chain(&headers, true).is_ok()); - } - - #[test] - fn test_header_chain_validation_basic() { - let manager = ValidationManager::new(ValidationMode::Basic); - - // Valid chain - let mut headers = vec![]; - let mut prev_hash = dashcore::BlockHash::from_raw_hash( - dashcore_hashes::hash_x11::Hash::from_byte_array([0; 32]), - ); - - for i in 0..3 { - let header = create_test_header(prev_hash, i, 0x1e0fffff); - prev_hash = header.block_hash(); - headers.push(header); - } - - assert!(manager.validate_header_chain(&headers, false).is_ok()); - - // Broken chain - headers[2] = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 99, - 0x1e0fffff, - ); - - let result = manager.validate_header_chain(&headers, false); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - } - - #[test] - fn test_header_chain_validation_full() { - let manager = ValidationManager::new(ValidationMode::Full); - - // Headers with invalid PoW - let headers = vec![create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 0, - 0x1d00ffff, - )]; - - // Should pass when validate_pow is false - assert!(manager.validate_header_chain(&headers, false).is_ok()); - - // Should fail when validate_pow is true - let result = manager.validate_header_chain(&headers, true); - assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); - } - - #[test] - fn test_instantlock_validation_none() { - let manager = ValidationManager::new(ValidationMode::None); - let instantlock = create_test_instantlock(); - - // Should always pass - assert!(manager.validate_instantlock(&instantlock).is_ok()); - } - - #[test] - fn test_instantlock_validation_basic() { - let manager = ValidationManager::new(ValidationMode::Basic); - let instantlock = create_test_instantlock(); - - // Basic validation should check structure - let result = manager.validate_instantlock(&instantlock); - // The actual validation depends on InstantLockValidator implementation - // For now, we just ensure it runs - let _ = result; - } - - #[test] - fn test_instantlock_validation_full() { - let manager = ValidationManager::new(ValidationMode::Full); - let instantlock = create_test_instantlock(); - - // Full validation should check structure and signatures - let result = manager.validate_instantlock(&instantlock); - // The actual validation depends on InstantLockValidator implementation - let _ = result; - } - - #[test] - fn test_mode_switching_affects_validation() { - let mut manager = ValidationManager::new(ValidationMode::None); - - // Create headers with broken chain - let header1 = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [0; 32], - )), - 1, - 0x1e0fffff, - ); - let disconnected_header = create_test_header( - dashcore::BlockHash::from_raw_hash(dashcore_hashes::hash_x11::Hash::from_byte_array( - [99; 32], - )), - 2, - 0x1e0fffff, - ); - - // Should pass with None - assert!(manager.validate_header(&disconnected_header, Some(&header1)).is_ok()); - - // Switch to Basic - manager.set_mode(ValidationMode::Basic); - - // Should now fail - let result = manager.validate_header(&disconnected_header, Some(&header1)); - assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); - - // Switch back to None - manager.set_mode(ValidationMode::None); - - // Should pass again - assert!(manager.validate_header(&disconnected_header, Some(&header1)).is_ok()); - } - - #[test] - fn test_empty_header_chain_validation() { - for mode in [ValidationMode::None, ValidationMode::Basic, ValidationMode::Full] { - let manager = ValidationManager::new(mode); - let empty_chain: Vec = vec![]; - - // Empty chains should always pass - assert!(manager.validate_header_chain(&empty_chain, false).is_ok()); - assert!(manager.validate_header_chain(&empty_chain, true).is_ok()); - } - } -} diff --git a/dash-spv/src/validation/mod.rs b/dash-spv/src/validation/mod.rs index 57feab2a6..5879b23ee 100644 --- a/dash-spv/src/validation/mod.rs +++ b/dash-spv/src/validation/mod.rs @@ -4,89 +4,6 @@ pub mod headers; pub mod instantlock; pub mod quorum; -use dashcore::{block::Header as BlockHeader, InstantLock}; - -use crate::error::ValidationResult; -use crate::types::ValidationMode; - -pub use headers::HeaderValidator; +pub use headers::validate_headers; pub use instantlock::InstantLockValidator; pub use quorum::{QuorumInfo, QuorumManager, QuorumType}; - -/// Manages all validation operations. -pub struct ValidationManager { - mode: ValidationMode, - header_validator: HeaderValidator, - instantlock_validator: InstantLockValidator, -} - -impl ValidationManager { - /// Create a new validation manager. - pub fn new(mode: ValidationMode) -> Self { - Self { - mode, - header_validator: HeaderValidator::new(mode), - instantlock_validator: InstantLockValidator::new(), - } - } - - /// Validate a block header. - pub fn validate_header( - &self, - header: &BlockHeader, - prev_header: Option<&BlockHeader>, - ) -> ValidationResult<()> { - match self.mode { - ValidationMode::None => Ok(()), - ValidationMode::Basic | ValidationMode::Full => { - self.header_validator.validate(header, prev_header) - } - } - } - - /// Validate a chain of headers. - pub fn validate_header_chain( - &self, - headers: &[BlockHeader], - validate_pow: bool, - ) -> ValidationResult<()> { - match self.mode { - ValidationMode::None => Ok(()), - ValidationMode::Basic => self.header_validator.validate_chain_basic(headers), - ValidationMode::Full => { - self.header_validator.validate_chain_full(headers, validate_pow) - } - } - } - - /// Validate an InstantLock (structural validation only). - /// - /// **WARNING**: This only performs structural validation without BLS signature - /// verification. For network messages, the caller must use the InstantLockValidator - /// directly with a masternode engine to ensure full security. - pub fn validate_instantlock(&self, instantlock: &InstantLock) -> ValidationResult<()> { - match self.mode { - ValidationMode::None => Ok(()), - ValidationMode::Basic | ValidationMode::Full => { - // Only structural validation - signature verification requires masternode engine - // which should be passed by the caller when processing network messages - self.instantlock_validator.validate_structure(instantlock) - } - } - } - - /// Get current validation mode. - pub fn mode(&self) -> ValidationMode { - self.mode - } - - /// Set validation mode. - pub fn set_mode(&mut self, mode: ValidationMode) { - self.mode = mode; - self.header_validator.set_mode(mode); - } -} - -#[cfg(test)] -#[path = "manager_test.rs"] -mod manager_test;