diff --git a/dash-spv-ffi/src/broadcast.rs b/dash-spv-ffi/src/broadcast.rs index f61206dac..6f656c89b 100644 --- a/dash-spv-ffi/src/broadcast.rs +++ b/dash-spv-ffi/src/broadcast.rs @@ -44,17 +44,13 @@ pub unsafe extern "C" fn dash_spv_ffi_client_broadcast_transaction( let client = &(*client); let inner = client.inner.clone(); - let result: Result<(), dash_spv::SpvError> = client.runtime.block_on(async { + let result: dash_spv::Result<()> = client.runtime.block_on(async { // Take the client out to avoid holding the lock across await let spv_client = { let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 8848d188b..70238de35 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -186,7 +186,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new( DashSpvClient::new(client_config, network, storage, wallet).await } (Err(e), _) => Err(e), - (_, Err(e)) => Err(dash_spv::SpvError::Storage(e)), + (_, Err(e)) => Err(e.into()), } }); @@ -379,7 +379,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_drain_events(client: *mut FFIDashSp FFIErrorCode::Success as i32 } -fn stop_client_internal(client: &mut FFIDashSpvClient) -> Result<(), dash_spv::SpvError> { +fn stop_client_internal(client: &mut FFIDashSpvClient) -> dash_spv::Result<()> { client.shutdown_token.cancel(); // Ensure callbacks are cleared so no further progress/completion notifications fire. @@ -399,11 +399,7 @@ fn stop_client_internal(client: &mut FFIDashSpvClient) -> Result<(), dash_spv::S let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.stop().await; @@ -440,9 +436,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_update_config( let mut guard = client.inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Config("Client not initialized".to_string())) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; @@ -479,11 +473,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_start(client: *mut FFIDashSpvClient let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.start().await; @@ -558,9 +548,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_test_sync(client: *mut FFIDashSpvCl let mut guard = client.inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Config("Client not initialized".to_string())) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; tracing::info!("Starting test sync..."); @@ -596,7 +584,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_test_sync(client: *mut FFIDashSpvCl } else { let msg = "No headers downloaded".to_string(); tracing::error!("❌ {}", msg); - Err(dash_spv::SpvError::Sync(dash_spv::SyncError::Network(msg))) + Err(dash_spv::Error::Sync(dash_spv::SyncError::Network(msg))) }; // put client back @@ -764,11 +752,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_sync_to_tip_with_progress( let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Config( - "Client not initialized".to_string(), - )) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let (_command_sender, command_receiver) = tokio::sync::mpsc::unbounded_channel(); @@ -892,11 +876,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_sync_progress( let mut guard = inner.lock().unwrap(); match guard.take() { Some(c) => c, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.sync_progress().await; @@ -932,11 +912,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_stats( let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.stats().await; @@ -978,9 +954,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_tip_hash( let mut guard = inner.lock().unwrap(); match guard.take() { Some(c) => c, - None => { - return Err(dash_spv::SpvError::Config("Client not initialized".to_string())) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let tip = spv_client.tip_hash().await; @@ -1031,9 +1005,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_tip_height( let mut guard = inner.lock().unwrap(); match guard.take() { Some(c) => c, - None => { - return Err(dash_spv::SpvError::Config("Client not initialized".to_string())) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let height = spv_client.tip_height().await; @@ -1070,9 +1042,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_clear_storage(client: *mut FFIDashS let mut guard = inner.lock().unwrap(); match guard.take() { Some(c) => c, - None => { - return Err(dash_spv::SpvError::Config("Client not initialized".to_string())) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; @@ -1232,15 +1202,13 @@ pub unsafe extern "C" fn dash_spv_ffi_client_rescan_blockchain( let client = &(*client); let inner = client.inner.clone(); - let result: Result<(), dash_spv::SpvError> = client.runtime.block_on(async { + let result: dash_spv::Result<()> = client.runtime.block_on(async { let mut guard = inner.lock().unwrap(); if let Some(ref mut _spv_client) = *guard { // TODO: rescan_from_height not yet implemented in dash-spv - Err(dash_spv::SpvError::Config("Not implemented".to_string())) + Err(dash_spv::Error::Config("Not implemented".to_string())) } else { - Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) + Err(dash_spv::Error::UninitializedClient) } }); @@ -1274,11 +1242,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_enable_mempool_tracking( let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.enable_mempool_tracking(mempool_strategy).await; @@ -1332,11 +1296,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_record_send( let mut guard = inner.lock().unwrap(); match guard.take() { Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } + None => return Err(dash_spv::Error::UninitializedClient), } }; let res = spv_client.record_send(txid).await; diff --git a/dash-spv-ffi/src/error.rs b/dash-spv-ffi/src/error.rs index 18f861671..0fb10e629 100644 --- a/dash-spv-ffi/src/error.rs +++ b/dash-spv-ffi/src/error.rs @@ -1,4 +1,3 @@ -use dash_spv::error::SpvError; use std::ffi::CString; use std::os::raw::c_char; use std::sync::Mutex; @@ -49,21 +48,19 @@ pub extern "C" fn dash_spv_ffi_clear_error() { clear_last_error(); } -impl From for FFIErrorCode { - fn from(err: SpvError) -> Self { +impl From for FFIErrorCode { + fn from(err: dash_spv::Error) -> Self { match err { - SpvError::ChannelFailure(_, _) => FFIErrorCode::RuntimeError, - SpvError::Network(_) => FFIErrorCode::NetworkError, - SpvError::Storage(_) => FFIErrorCode::StorageError, - SpvError::Validation(_) => FFIErrorCode::ValidationError, - SpvError::Sync(_) => FFIErrorCode::SyncError, - SpvError::Io(_) => FFIErrorCode::RuntimeError, - SpvError::Config(_) => FFIErrorCode::ConfigError, - SpvError::Parse(_) => FFIErrorCode::ValidationError, - SpvError::Logging(_) => FFIErrorCode::RuntimeError, - SpvError::Wallet(_) => FFIErrorCode::WalletError, - SpvError::QuorumLookupError(_) => FFIErrorCode::ValidationError, - SpvError::General(_) => FFIErrorCode::Unknown, + dash_spv::Error::ChannelFailure(_, _) => FFIErrorCode::RuntimeError, + dash_spv::Error::Network(_) => FFIErrorCode::NetworkError, + dash_spv::Error::Storage(_) => FFIErrorCode::StorageError, + dash_spv::Error::Validation(_) => FFIErrorCode::ValidationError, + dash_spv::Error::Sync(_) => FFIErrorCode::SyncError, + dash_spv::Error::Config(_) => FFIErrorCode::ConfigError, + dash_spv::Error::Logging(_) => FFIErrorCode::RuntimeError, + dash_spv::Error::QuorumLookupError(_) => FFIErrorCode::ValidationError, + dash_spv::Error::UninitializedClient => FFIErrorCode::RuntimeError, + dash_spv::Error::TaskFailed(_) => FFIErrorCode::RuntimeError, } } } diff --git a/dash-spv-ffi/tests/unit/test_error_handling.rs b/dash-spv-ffi/tests/unit/test_error_handling.rs index f47f48c09..b3b3202e1 100644 --- a/dash-spv-ffi/tests/unit/test_error_handling.rs +++ b/dash-spv-ffi/tests/unit/test_error_handling.rs @@ -109,24 +109,21 @@ mod tests { assert_eq!(FFIErrorCode::Unknown as i32, 99); // Test conversions from SpvError - use dash_spv::{NetworkError, SpvError, StorageError, SyncError, ValidationError}; + use dash_spv::{NetworkError, StorageError, SyncError, ValidationError}; - let net_err = SpvError::Network(NetworkError::ConnectionFailed("test".to_string())); + let net_err = dash_spv::Error::Network(NetworkError::ConnectionFailed("test".to_string())); assert_eq!(FFIErrorCode::from(net_err) as i32, FFIErrorCode::NetworkError as i32); - let storage_err = SpvError::Storage(StorageError::NotFound("test".to_string())); + let storage_err = dash_spv::Error::Storage(StorageError::NotFound("test".to_string())); assert_eq!(FFIErrorCode::from(storage_err) as i32, FFIErrorCode::StorageError as i32); - let val_err = SpvError::Validation(ValidationError::InvalidProofOfWork); + let val_err = dash_spv::Error::Validation(ValidationError::InvalidProofOfWork); assert_eq!(FFIErrorCode::from(val_err) as i32, FFIErrorCode::ValidationError as i32); - let sync_err = SpvError::Sync(SyncError::Timeout("Test timeout".to_string())); + let sync_err = dash_spv::Error::Sync(SyncError::Timeout("Test timeout".to_string())); assert_eq!(FFIErrorCode::from(sync_err) as i32, FFIErrorCode::SyncError as i32); - let io_err = SpvError::Io(std::io::Error::other("test")); - assert_eq!(FFIErrorCode::from(io_err) as i32, FFIErrorCode::RuntimeError as i32); - - let config_err = SpvError::Config("test".to_string()); + let config_err = dash_spv::Error::Config("test".to_string()); assert_eq!(FFIErrorCode::from(config_err) as i32, FFIErrorCode::ConfigError as i32); } diff --git a/dash-spv/clippy.toml b/dash-spv/clippy.toml new file mode 100644 index 000000000..7dc72ab7b --- /dev/null +++ b/dash-spv/clippy.toml @@ -0,0 +1,4 @@ +disallowed-types = [ + { path = "std::sync::RwLock", reason = "This struct is blocking and can be poisoned", replacement = "tokio::sync::RwLock" }, + { path = "std::sync::Mutex", reason = "This struct is blocking and can be poisoned", replacement = "tokio::sync::Mutex" }, +] diff --git a/dash-spv/src/chain/chainlock_manager.rs b/dash-spv/src/chain/chainlock_manager.rs index 84b1db5aa..6da83bd7e 100644 --- a/dash-spv/src/chain/chainlock_manager.rs +++ b/dash-spv/src/chain/chainlock_manager.rs @@ -6,12 +6,13 @@ use dashcore::sml::masternode_list_engine::MasternodeListEngine; use dashcore::{BlockHash, ChainLock}; use indexmap::IndexMap; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; +use tokio::sync::RwLock; use tracing::{debug, error, info, warn}; -use crate::error::{StorageError, StorageResult, ValidationError, ValidationResult}; use crate::storage::StorageManager; use crate::types::ChainState; +use crate::{StorageError, StorageResult, ValidationError, ValidationResult}; /// Maximum number of pending ChainLocks to queue const MAX_PENDING_CHAINLOCKS: usize = 100; @@ -62,24 +63,15 @@ impl ChainLockManager { } /// Set the masternode engine for validation - pub fn set_masternode_engine(&self, engine: Arc) { - match self.masternode_engine.write() { - Ok(mut guard) => { - *guard = Some(engine); - info!("Masternode engine set for ChainLock validation"); - } - Err(e) => { - error!("Failed to set masternode engine: {}", e); - } - } + pub async fn set_masternode_engine(&self, engine: Arc) { + let mut guard = self.masternode_engine.write().await; + *guard = Some(engine); + info!("Masternode engine set for ChainLock validation"); } /// Queue a ChainLock for validation when masternode data is available - pub fn queue_pending_chainlock(&self, chain_lock: ChainLock) -> StorageResult<()> { - let mut pending = self - .pending_chainlocks - .write() - .map_err(|_| StorageError::LockPoisoned("pending_chainlocks".to_string()))?; + pub async fn queue_pending_chainlock(&self, chain_lock: ChainLock) { + let mut pending = self.pending_chainlocks.write().await; // If at capacity, drop the oldest ChainLock if pending.len() >= MAX_PENDING_CHAINLOCKS { @@ -92,7 +84,6 @@ impl ChainLockManager { pending.push(chain_lock); debug!("Queued ChainLock for pending validation, total pending: {}", pending.len()); - Ok(()) } /// Validate all pending ChainLocks after masternode sync @@ -101,20 +92,14 @@ impl ChainLockManager { chain_state: &ChainState, storage: &mut S, ) -> ValidationResult<()> { - let pending = { - let mut pending_guard = self - .pending_chainlocks - .write() - .map_err(|_| ValidationError::InvalidChainLock("Lock poisoned".to_string()))?; - std::mem::take(&mut *pending_guard) - }; + let pending = self.pending_chainlocks.write().await; info!("Validating {} pending ChainLocks", pending.len()); let mut validated_count = 0; let mut failed_count = 0; - for chain_lock in pending { + for chain_lock in pending.iter() { match self.process_chain_lock(chain_lock.clone(), chain_state, storage).await { Ok(_) => { validated_count += 1; @@ -154,8 +139,8 @@ impl ChainLockManager { ); // Check if we already have this chain lock - if self.has_chain_lock_at_height(chain_lock.block_height) { - let existing = self.get_chain_lock_by_height(chain_lock.block_height); + if self.has_chain_lock_at_height(chain_lock.block_height).await { + let existing = self.get_chain_lock_by_height(chain_lock.block_height).await; if let Some(existing_entry) = existing { if existing_entry.chain_lock.block_hash != chain_lock.block_hash { error!( @@ -175,11 +160,7 @@ impl ChainLockManager { } // Verify the block exists in our chain - if let Some(header) = storage - .get_header(chain_lock.block_height) - .await - .map_err(ValidationError::StorageError)? - { + if let Some(header) = storage.get_header(chain_lock.block_height).await? { let header_hash = header.block_hash(); if header_hash != chain_lock.block_hash { return Err(ValidationError::InvalidChainLock(format!( @@ -195,10 +176,7 @@ impl ChainLockManager { // Full validation with masternode engine if available let mut validated = false; { - let engine_guard = self - .masternode_engine - .read() - .map_err(|_| ValidationError::InvalidChainLock("Lock poisoned".to_string()))?; + let engine_guard = self.masternode_engine.read().await; if let Some(engine) = engine_guard.as_ref() { // Use the masternode engine's verify_chain_lock method @@ -220,12 +198,7 @@ impl ChainLockManager { .saturating_sub(CHAINLOCK_VALIDATION_MASTERNODE_OFFSET); warn!("⚠️ Masternode engine exists but lacks required masternode lists for height {} (needs list at height {} for ChainLock validation), queueing ChainLock for later validation", chain_lock.block_height, required_height); - self.queue_pending_chainlock(chain_lock.clone()).map_err(|e| { - ValidationError::InvalidChainLock(format!( - "Failed to queue pending ChainLock: {}", - e - )) - })?; + self.queue_pending_chainlock(chain_lock.clone()).await; } else { return Err(ValidationError::InvalidChainLock(format!( "MasternodeListEngine validation failed: {:?}", @@ -239,12 +212,7 @@ impl ChainLockManager { warn!( "⚠️ Masternode engine not available, queueing ChainLock for later validation" ); - self.queue_pending_chainlock(chain_lock.clone()).map_err(|e| { - ValidationError::InvalidChainLock(format!( - "Failed to queue pending ChainLock: {}", - e - )) - })?; + self.queue_pending_chainlock(chain_lock.clone()).await; } } // engine_guard dropped before any await @@ -304,14 +272,8 @@ impl ChainLockManager { ) -> StorageResult<()> { // Store in memory caches { - let mut by_height = self - .chain_locks_by_height - .write() - .map_err(|_| StorageError::LockPoisoned("chain_locks_by_height".to_string()))?; - let mut by_hash = self - .chain_locks_by_hash - .write() - .map_err(|_| StorageError::LockPoisoned("chain_locks_by_hash".to_string()))?; + let mut by_height = self.chain_locks_by_height.write().await; + let mut by_hash = self.chain_locks_by_hash.write().await; by_height.insert(chain_lock.block_height, entry.clone()); by_hash.insert(chain_lock.block_hash, entry.clone()); @@ -346,29 +308,29 @@ impl ChainLockManager { } /// Check if we have a chain lock at the given height - pub fn has_chain_lock_at_height(&self, height: u32) -> bool { - self.chain_locks_by_height.read().map(|locks| locks.contains_key(&height)).unwrap_or(false) + pub async fn has_chain_lock_at_height(&self, height: u32) -> bool { + self.chain_locks_by_height.read().await.contains_key(&height) } /// Get chain lock by height - pub fn get_chain_lock_by_height(&self, height: u32) -> Option { - self.chain_locks_by_height.read().ok().and_then(|locks| locks.get(&height).cloned()) + pub async fn get_chain_lock_by_height(&self, height: u32) -> Option { + self.chain_locks_by_height.read().await.get(&height).cloned() } /// Get chain lock by block hash - pub fn get_chain_lock_by_hash(&self, hash: &BlockHash) -> Option { - self.chain_locks_by_hash.read().ok().and_then(|locks| locks.get(hash).cloned()) + pub async fn get_chain_lock_by_hash(&self, hash: &BlockHash) -> Option { + self.chain_locks_by_hash.read().await.get(hash).cloned() } /// Check if a block is chain-locked - pub fn is_block_chain_locked(&self, block_hash: &BlockHash, height: u32) -> bool { + pub async fn is_block_chain_locked(&self, block_hash: &BlockHash, height: u32) -> bool { // First check by hash (most specific) - if let Some(entry) = self.get_chain_lock_by_hash(block_hash) { + if let Some(entry) = self.get_chain_lock_by_hash(block_hash).await { return entry.validated && entry.chain_lock.block_hash == *block_hash; } // Then check by height - if let Some(entry) = self.get_chain_lock_by_height(height) { + if let Some(entry) = self.get_chain_lock_by_height(height).await { return entry.validated && entry.chain_lock.block_hash == *block_hash; } @@ -376,20 +338,21 @@ impl ChainLockManager { } /// Get the highest chain-locked block height - pub fn get_highest_chain_locked_height(&self) -> Option { - self.chain_locks_by_height.read().ok().and_then(|locks| locks.keys().max().cloned()) + pub async fn get_highest_chain_locked_height(&self) -> Option { + self.chain_locks_by_height.read().await.keys().max().cloned() } /// Check if a reorganization would violate chain locks - pub fn would_violate_chain_lock(&self, reorg_from_height: u32, reorg_to_height: u32) -> bool { + pub async fn would_violate_chain_lock( + &self, + reorg_from_height: u32, + reorg_to_height: u32, + ) -> bool { if !self.enforce_chain_locks { return false; } - let locks = match self.chain_locks_by_height.read() { - Ok(locks) => locks, - Err(_) => return false, // If we can't read locks, assume no violation - }; + let locks = self.chain_locks_by_height.read().await; // Check if any chain-locked block would be reorganized for height in reorg_from_height..=reorg_to_height { @@ -429,12 +392,8 @@ impl ChainLockManager { validated: true, }; - let mut by_height = self.chain_locks_by_height.write().map_err(|_| { - StorageError::LockPoisoned("chain_locks_by_height".to_string()) - })?; - let mut by_hash = self.chain_locks_by_hash.write().map_err(|_| { - StorageError::LockPoisoned("chain_locks_by_hash".to_string()) - })?; + let mut by_height = self.chain_locks_by_height.write().await; + let mut by_hash = self.chain_locks_by_hash.write().await; by_height.insert(chain_lock.block_height, entry.clone()); by_hash.insert(chain_lock.block_hash, entry); diff --git a/dash-spv/src/chain/chainlock_test.rs b/dash-spv/src/chain/chainlock_test.rs index 96f3b2cae..9c906e3d4 100644 --- a/dash-spv/src/chain/chainlock_test.rs +++ b/dash-spv/src/chain/chainlock_test.rs @@ -26,11 +26,12 @@ mod tests { assert!(result.is_ok(), "ChainLock processing should succeed"); // Verify it was stored - assert!(chainlock_manager.has_chain_lock_at_height(1000)); + assert!(chainlock_manager.has_chain_lock_at_height(1000).await); // Verify we can retrieve it let entry = chainlock_manager .get_chain_lock_by_height(1000) + .await .expect("ChainLock should be retrievable after storing"); assert_eq!(entry.chain_lock.block_height, 1000); assert_eq!(entry.chain_lock.block_hash, chainlock.block_hash); @@ -58,11 +59,11 @@ mod tests { .expect("Second ChainLock should process successfully"); // Verify both are stored - assert!(chainlock_manager.has_chain_lock_at_height(1000)); - assert!(chainlock_manager.has_chain_lock_at_height(2000)); + assert!(chainlock_manager.has_chain_lock_at_height(1000).await); + assert!(chainlock_manager.has_chain_lock_at_height(2000).await); // Get highest ChainLock - let highest = chainlock_manager.get_highest_chain_locked_height(); + let highest = chainlock_manager.get_highest_chain_locked_height().await; assert_eq!(highest, Some(2000)); } @@ -85,9 +86,9 @@ mod tests { } // Test reorganization protection - assert!(!chainlock_manager.would_violate_chain_lock(500, 999)); // Before ChainLocks - OK - assert!(chainlock_manager.would_violate_chain_lock(1500, 2500)); // Would reorg ChainLock at 2000 - assert!(!chainlock_manager.would_violate_chain_lock(3001, 4000)); // After ChainLocks - OK + assert!(!chainlock_manager.would_violate_chain_lock(500, 999).await); // Before ChainLocks - OK + assert!(chainlock_manager.would_violate_chain_lock(1500, 2500).await); // Would reorg ChainLock at 2000 + assert!(!chainlock_manager.would_violate_chain_lock(3001, 4000).await); // After ChainLocks - OK } #[tokio::test] @@ -99,14 +100,14 @@ mod tests { 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(); - chainlock_manager.queue_pending_chainlock(chain_lock3).unwrap(); + chainlock_manager.queue_pending_chainlock(chain_lock1).await; + chainlock_manager.queue_pending_chainlock(chain_lock2).await; + chainlock_manager.queue_pending_chainlock(chain_lock3).await; // Verify all are queued { // Note: pending_chainlocks is private, can't access directly - let pending = chainlock_manager.pending_chainlocks.read().unwrap(); + let pending = chainlock_manager.pending_chainlocks.read().await; assert_eq!(pending.len(), 3); assert_eq!(pending[0].block_height, 100); assert_eq!(pending[1].block_height, 200); @@ -132,13 +133,13 @@ mod tests { .await; // Test cache operations - assert!(chainlock_manager.has_chain_lock_at_height(0)); + assert!(chainlock_manager.has_chain_lock_at_height(0).await); - let entry = chainlock_manager.get_chain_lock_by_height(0); + let entry = chainlock_manager.get_chain_lock_by_height(0).await; assert!(entry.is_some()); assert_eq!(entry.unwrap().chain_lock.block_height, 0); - let entry_by_hash = chainlock_manager.get_chain_lock_by_hash(&header.block_hash()); + let entry_by_hash = chainlock_manager.get_chain_lock_by_hash(&header.block_hash()).await; assert!(entry_by_hash.is_some()); assert_eq!(entry_by_hash.unwrap().chain_lock.block_height, 0); } diff --git a/dash-spv/src/client/chainlock.rs b/dash-spv/src/client/chainlock.rs index 2c374ca4c..fee3c7c80 100644 --- a/dash-spv/src/client/chainlock.rs +++ b/dash-spv/src/client/chainlock.rs @@ -8,7 +8,6 @@ use std::sync::Arc; -use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::SpvEvent; @@ -22,7 +21,7 @@ impl DashSpvClient Result<()> { + ) -> crate::Result<()> { tracing::info!( "Processing ChainLock for block {} at height {}", chainlock.block_hash, @@ -41,7 +40,7 @@ impl DashSpvClient DashSpvClient Result<()> { + ) -> crate::Result<()> { tracing::info!("Processing InstantSendLock for tx {}", islock.txid); // Get the masternode engine from sync manager for proper quorum verification let masternode_engine = self.sync_manager.get_masternode_engine().ok_or_else(|| { - SpvError::Validation(crate::error::ValidationError::MasternodeVerification( + crate::Error::Validation(crate::ValidationError::MasternodeVerification( "Masternode engine not available for InstantLock verification".to_string(), )) })?; @@ -109,7 +108,7 @@ impl DashSpvClient DashSpvClient Result { + pub async fn update_chainlock_validation(&self) -> crate::Result { // Check if masternode sync has an engine available if let Some(engine) = self.sync_manager.get_masternode_engine() { // Clone the engine for the ChainLockManager let engine_arc = Arc::new(engine.clone()); - self.chainlock_manager.set_masternode_engine(engine_arc); + self.chainlock_manager.set_masternode_engine(engine_arc).await; tracing::info!("Updated ChainLockManager with masternode engine for full validation"); @@ -152,7 +151,7 @@ impl DashSpvClient Result<()> { + pub async fn validate_pending_chainlocks(&mut self) -> crate::Result<()> { let chain_state = self.state.read().await; let mut storage = self.storage.lock().await; @@ -164,7 +163,7 @@ impl DashSpvClient { tracing::error!("Failed to validate pending ChainLocks: {}", e); - Err(SpvError::Validation(e)) + Err(crate::Error::Validation(e)) } } } diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index 0505b93e1..3e6de35ef 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -192,18 +192,18 @@ impl ClientConfig { } /// Validate the configuration. - pub fn validate(&self) -> Result<(), String> { + pub fn validate(&self) -> Result<(), crate::Error> { // Note: Empty peers list is now valid - DNS discovery will be used automatically if self.max_peers == 0 { - return Err("max_peers must be > 0".to_string()); + return Err(crate::Error::Config(String::from("max_peers must be > 0"))); } // Mempool validation if self.enable_mempool_tracking && self.max_mempool_transactions == 0 { - return Err( - "max_mempool_transactions must be > 0 when mempool tracking is enabled".to_string() - ); + return Err(crate::Error::Config(String::from( + "max_mempool_transactions must be > 0 when mempool tracking is enabled", + ))); } Ok(()) diff --git a/dash-spv/src/client/config_test.rs b/dash-spv/src/client/config_test.rs index 6c564799e..8b853d729 100644 --- a/dash-spv/src/client/config_test.rs +++ b/dash-spv/src/client/config_test.rs @@ -103,7 +103,7 @@ mod tests { let result = config.validate(); assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "max_peers must be > 0"); + assert!(result.unwrap_err().to_string().contains("max_peers must be > 0")); } #[test] @@ -116,7 +116,7 @@ mod tests { let result = config.validate(); assert!(result.is_err()); - assert!(result.unwrap_err().contains("max_mempool_transactions must be > 0")); + assert!(result.unwrap_err().to_string().contains("max_mempool_transactions must be > 0")); } // Removed selective strategy validation test; Selective variant no longer exists diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index b41a7aeae..b077e2d2e 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -15,7 +15,6 @@ use tokio::sync::{mpsc, Mutex, RwLock}; use crate::terminal::TerminalUI; use crate::chain::ChainLockManager; -use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -201,11 +200,11 @@ impl DashSpvClient Result<()> { + pub async fn clear_storage(&mut self) -> crate::Result<()> { // Wipe on-disk persistence fully { let mut storage = self.storage.lock().await; - storage.clear().await.map_err(SpvError::Storage)?; + storage.clear().await?; } // Reset in-memory chain state to a clean baseline for the current network @@ -267,13 +266,15 @@ impl DashSpvClient Result<()> { + pub async fn update_config(&mut self, new_config: ClientConfig) -> crate::Result<()> { // Validate new configuration - new_config.validate().map_err(SpvError::Config)?; + new_config.validate()?; // Ensure network hasn't changed if new_config.network != self.config.network { - return Err(SpvError::Config("Cannot change network on running client".to_string())); + return Err(crate::Error::Config( + "Cannot change network on running client".to_string(), + )); } // Update configuration diff --git a/dash-spv/src/client/interface.rs b/dash-spv/src/client/interface.rs index 1704c88c8..122fa41c6 100644 --- a/dash-spv/src/client/interface.rs +++ b/dash-spv/src/client/interface.rs @@ -1,16 +1,13 @@ -use crate::error::SpvError; use dashcore::sml::llmq_type::LLMQType; use dashcore::sml::quorum_entry::qualified_quorum_entry::QualifiedQuorumEntry; use dashcore::QuorumHash; use std::fmt::Display; use tokio::sync::{mpsc, oneshot}; -pub type Result = std::result::Result; +pub type GetQuorumByHeightResult = crate::Result; -pub type GetQuorumByHeightResult = Result; - -async fn receive(context: String, receiver: oneshot::Receiver) -> Result { - receiver.await.map_err(|error| SpvError::ChannelFailure(context, error.to_string())) +async fn receive(context: String, receiver: oneshot::Receiver) -> crate::Result { + receiver.await.map_err(|error| crate::Error::ChannelFailure(context, error.to_string())) } pub enum DashSpvClientCommand { @@ -27,8 +24,10 @@ impl DashSpvClientCommand { self, context: String, sender: mpsc::UnboundedSender, - ) -> Result<()> { - sender.send(self).map_err(|error| SpvError::ChannelFailure(context, error.to_string()))?; + ) -> crate::Result<()> { + sender + .send(self) + .map_err(|error| crate::Error::ChannelFailure(context, error.to_string()))?; Ok(()) } } diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index ce061bc8d..1ed693323 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -13,7 +13,6 @@ use std::sync::Arc; use tokio::sync::{mpsc, Mutex, RwLock}; use crate::chain::ChainLockManager; -use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -32,9 +31,9 @@ impl DashSpvClient>, - ) -> Result { + ) -> crate::Result { // Validate configuration - config.validate().map_err(SpvError::Config)?; + config.validate()?; // Initialize state for the network let state = Arc::new(RwLock::new(ChainState::new_for_network(config.network))); @@ -53,7 +52,7 @@ impl DashSpvClient DashSpvClient Result<()> { + pub async fn start(&mut self) -> crate::Result<()> { { let running = self.running.read().await; if *running { - return Err(SpvError::Config("Client already running".to_string())); + return Err(crate::Error::Config("Client already running".to_string())); } } @@ -114,14 +113,7 @@ impl DashSpvClient DashSpvClient DashSpvClient Result<()> { + pub async fn stop(&mut self) -> crate::Result<()> { // Check if already stopped { let running = self.running.read().await; @@ -207,12 +198,12 @@ impl DashSpvClient Result<()> { + pub async fn shutdown(&mut self) -> crate::Result<()> { self.stop().await } /// Initialize genesis block or checkpoint. - pub(super) async fn initialize_genesis_block(&mut self) -> Result<()> { + pub(super) async fn initialize_genesis_block(&mut self) -> crate::Result<()> { // Check if we already have any headers in storage let current_tip = { let storage = self.storage.lock().await; @@ -297,10 +288,7 @@ impl DashSpvClient DashSpvClient DashSpvClient DashSpvClient DashSpvClient Result<()> { + pub(super) async fn load_wallet_data(&self) -> crate::Result<()> { tracing::info!("Loading wallet data from storage..."); let _wallet = self.wallet.read().await; diff --git a/dash-spv/src/client/mempool.rs b/dash-spv/src/client/mempool.rs index fac807360..325bb2c33 100644 --- a/dash-spv/src/client/mempool.rs +++ b/dash-spv/src/client/mempool.rs @@ -9,7 +9,6 @@ use std::collections::HashSet; use std::sync::Arc; -use crate::error::Result; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -22,7 +21,7 @@ impl DashSpvClient Result<()> { + ) -> crate::Result<()> { // Update config self.config.enable_mempool_tracking = true; self.config.mempool_strategy = strategy; @@ -46,7 +45,7 @@ impl DashSpvClient Result { + ) -> crate::Result { let _wallet = self.wallet.read().await; let mempool_state = self.mempool_state.read().await; @@ -134,7 +133,7 @@ impl DashSpvClient Result<()> { + pub async fn record_send(&self, txid: dashcore::Txid) -> crate::Result<()> { let mut mempool_state = self.mempool_state.write().await; mempool_state.record_send(txid); Ok(()) diff --git a/dash-spv/src/client/message_handler.rs b/dash-spv/src/client/message_handler.rs index 75ee4dfb0..3c7937f2b 100644 --- a/dash-spv/src/client/message_handler.rs +++ b/dash-spv/src/client/message_handler.rs @@ -1,7 +1,6 @@ //! Network message handling for the Dash SPV client. use crate::client::ClientConfig; -use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -50,7 +49,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle pub async fn handle_network_message( &mut self, message: &dashcore::network::message::NetworkMessage, - ) -> Result<()> { + ) -> crate::Result<()> { use dashcore::network::message::NetworkMessage; tracing::debug!("Client handling network message: {:?}", std::mem::discriminant(message)); @@ -70,27 +69,19 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle } // Move to sync manager without cloning - return self - .sync_manager + self.sync_manager .handle_message(message, &mut *self.network, &mut *self.storage) - .await - .map_err(|e| { - tracing::error!("Sequential sync manager error handling message: {}", e); - SpvError::Sync(e) - }); + .await?; + return Ok(()); } NetworkMessage::MnListDiff(ref diff) => { tracing::info!("📨 Received MnListDiff message: {} new masternodes, {} deleted masternodes, {} quorums", diff.new_masternodes.len(), diff.deleted_masternodes.len(), diff.new_quorums.len()); // Move to sync manager without cloning - return self - .sync_manager + self.sync_manager .handle_message(message, &mut *self.network, &mut *self.storage) - .await - .map_err(|e| { - tracing::error!("Sequential sync manager error handling message: {}", e); - SpvError::Sync(e) - }); + .await?; + return Ok(()); } NetworkMessage::CFHeaders(ref cf_headers) => { // Try to include the peer address for better diagnostics @@ -111,14 +102,10 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle } } // Move to sync manager without cloning - return self - .sync_manager + self.sync_manager .handle_message(message, &mut *self.network, &mut *self.storage) - .await - .map_err(|e| { - tracing::error!("Sequential sync manager error handling message: {}", e); - SpvError::Sync(e) - }); + .await?; + return Ok(()); } NetworkMessage::QRInfo(ref qr_info) => { tracing::info!( @@ -127,14 +114,10 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle qr_info.quorum_snapshot_list.len() ); // Move to sync manager without cloning - return self - .sync_manager + self.sync_manager .handle_message(message, &mut *self.network, &mut *self.storage) - .await - .map_err(|e| { - tracing::error!("Sequential sync manager error handling QRInfo: {}", e); - SpvError::Sync(e) - }); + .await?; + return Ok(()); } NetworkMessage::Headers(_) | NetworkMessage::CFilter(_) => { // Headers and CFilters are relatively small, cloning is acceptable @@ -200,18 +183,9 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle // 1) Ensure header processing and chain tip update for this block // Route the header through the sequential sync manager as a Headers message let headers_msg = NetworkMessage::Headers(vec![block.header]); - if let Err(e) = self - .sync_manager + self.sync_manager .handle_message(&headers_msg, &mut *self.network, &mut *self.storage) - .await - { - tracing::error!( - "❌ Failed to process header for block {} via sync manager: {}", - block_hash, - e - ); - return Err(SpvError::Sync(e)); - } + .await? } NetworkMessage::Inv(inv) => { tracing::debug!("Received inventory message with {} items", inv.len()); @@ -317,7 +291,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle async fn handle_inventory( &mut self, inv: Vec, - ) -> Result<()> { + ) -> crate::Result<()> { use dashcore::network::message::NetworkMessage; use dashcore::network::message_blockdata::Inventory; @@ -384,14 +358,14 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle if !chainlocks_to_request.is_empty() { tracing::info!("Requesting {} ChainLocks", chainlocks_to_request.len()); let getdata = NetworkMessage::GetData(chainlocks_to_request); - self.network.send_message(getdata).await.map_err(SpvError::Network)?; + self.network.send_message(getdata).await?; } // Auto-request InstantLocks (only when synced and masternodes available; gated above) if !islocks_to_request.is_empty() { tracing::info!("Requesting {} InstantLocks", islocks_to_request.len()); let getdata = NetworkMessage::GetData(islocks_to_request); - self.network.send_message(getdata).await.map_err(SpvError::Network)?; + self.network.send_message(getdata).await?; } // For blocks announced via inventory during tip sync, request full blocks for privacy @@ -415,7 +389,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle pub async fn handle_post_sync_headers( &mut self, headers: &[dashcore::block::Header], - ) -> Result<()> { + ) -> crate::Result<()> { if !self.config.enable_filters { tracing::debug!( "Filters not enabled, skipping post-sync filter requests for {} headers", @@ -433,8 +407,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle // request filter headers and filters as needed self.sync_manager .handle_new_headers(headers, &mut *self.network, &mut *self.storage) - .await - .map_err(SpvError::Sync)?; + .await?; Ok(()) } diff --git a/dash-spv/src/client/progress.rs b/dash-spv/src/client/progress.rs index 315fe700d..f15ece148 100644 --- a/dash-spv/src/client/progress.rs +++ b/dash-spv/src/client/progress.rs @@ -5,7 +5,6 @@ //! - Phase-to-stage mapping //! - Statistics gathering -use crate::error::Result; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::sync::SyncPhase; @@ -16,13 +15,13 @@ use super::DashSpvClient; impl DashSpvClient { /// Get current sync progress. - pub async fn sync_progress(&self) -> Result { + pub async fn sync_progress(&self) -> crate::Result { let display = self.create_status_display().await; display.sync_progress().await } /// Get current statistics. - pub async fn stats(&self) -> Result { + pub async fn stats(&self) -> crate::Result { let display = self.create_status_display().await; let mut stats = display.stats().await?; diff --git a/dash-spv/src/client/queries.rs b/dash-spv/src/client/queries.rs index f6f0b0590..af73979ee 100644 --- a/dash-spv/src/client/queries.rs +++ b/dash-spv/src/client/queries.rs @@ -6,7 +6,6 @@ //! - Balance queries //! - Filter availability checks -use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::AddressBalance; @@ -38,14 +37,20 @@ impl DashSpvClient Result<()> { + pub async fn disconnect_peer( + &self, + addr: &std::net::SocketAddr, + reason: &str, + ) -> crate::Result<()> { // Cast network manager to PeerNetworkManager to access disconnect_peer let network = self .network .as_any() .downcast_ref::() .ok_or_else(|| { - SpvError::Config("Network manager does not support peer disconnection".to_string()) + crate::Error::Config( + "Network manager does not support peer disconnection".to_string(), + ) })?; network.disconnect_peer(addr, reason).await @@ -72,7 +77,7 @@ impl DashSpvClient Result { + ) -> crate::Result { // First check if we have the masternode list at this height match self.get_masternode_list_at_height(height) { Some(ml) => { @@ -95,7 +100,7 @@ impl DashSpvClient { @@ -104,7 +109,7 @@ impl DashSpvClient DashSpvClient DashSpvClient Result { + ) -> crate::Result { // This method requires wallet-specific functionality not in WalletInterface // The wallet should expose balance info through its own interface - Err(SpvError::Config( + Err(crate::Error::Config( "Address balance queries should be made directly to the wallet implementation" .to_string(), )) @@ -146,7 +151,7 @@ impl DashSpvClient Result> { + ) -> crate::Result> { // TODO: Get balances from wallet instead of tracking separately // Will be implemented when wallet integration is complete Ok(std::collections::HashMap::new()) diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index cc3e9aee5..3777db313 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use tokio::sync::{Mutex, RwLock}; use crate::client::ClientConfig; -use crate::error::Result; use crate::storage::StorageManager; #[cfg(feature = "terminal-ui")] use crate::terminal::TerminalUI; @@ -98,7 +97,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { } /// Get current sync progress. - pub async fn sync_progress(&self) -> Result { + pub async fn sync_progress(&self) -> crate::Result { let state = self.state.read().await; // Clone the inner heights handle and copy needed counters without awaiting while holding the RwLock let (filters_received, received_heights) = { @@ -135,7 +134,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { } /// Get current statistics. - pub async fn stats(&self) -> Result { + pub async fn stats(&self) -> crate::Result { let stats = self.stats.read().await; Ok(stats.clone()) } diff --git a/dash-spv/src/client/sync_coordinator.rs b/dash-spv/src/client/sync_coordinator.rs index 356e3c20c..b51a1366e 100644 --- a/dash-spv/src/client/sync_coordinator.rs +++ b/dash-spv/src/client/sync_coordinator.rs @@ -12,7 +12,6 @@ use super::{DashSpvClient, MessageHandler}; use crate::client::interface::DashSpvClientCommand; -use crate::error::{Result, SpvError}; use crate::network::constants::MESSAGE_RECEIVE_TIMEOUT; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -31,10 +30,10 @@ impl DashSpvClient, token: CancellationToken, - ) -> Result<()> { + ) -> crate::Result<()> { let running = self.running.read().await; if !*running { - return Err(SpvError::Config("Client not running".to_string())); + return Err(crate::Error::Config("Client not running".to_string())); } drop(running); @@ -377,7 +376,7 @@ impl DashSpvClient DashSpvClient { + crate::Error::Network(_) => { tracing::warn!("Network error during message handling - may recover automatically"); } - SpvError::Storage(_) => { + crate::Error::Storage(_) => { tracing::error!("Storage error during message handling - this may affect data consistency"); } - SpvError::Validation(_) => { + crate::Error::Validation(_) => { tracing::warn!("Validation error during message handling - message rejected"); } _ => { @@ -454,7 +453,7 @@ impl DashSpvClient { // Handle specific network error types - if let crate::error::NetworkError::ConnectionFailed(msg) = &err { + if let crate::NetworkError::ConnectionFailed(msg) = &err { if msg.contains("No connected peers") || self.network.peer_count() == 0 { tracing::warn!("All peers disconnected during monitoring, checking connection health"); @@ -499,7 +498,7 @@ impl DashSpvClient, shutdown_token: CancellationToken, - ) -> Result<()> { + ) -> crate::Result<()> { let client_token = shutdown_token.clone(); let client_task = tokio::spawn(async move { @@ -522,10 +521,10 @@ impl DashSpvClient Result<()> { + async fn handle_command(&mut self, command: DashSpvClientCommand) -> crate::Result<()> { match command { DashSpvClientCommand::GetQuorumByHeight { height, @@ -535,7 +534,7 @@ impl DashSpvClient { let result = self.get_quorum_at_height(height, quorum_type, quorum_hash); if sender.send(result).is_err() { - return Err(SpvError::ChannelFailure( + return Err(crate::Error::ChannelFailure( format!("GetQuorumByHeight({height}, {quorum_type}, {quorum_hash})"), "Failed to send quorum result".to_string(), )); @@ -549,7 +548,7 @@ impl DashSpvClient Result<()> { + ) -> crate::Result<()> { // Check if this is a special message that needs client-level processing let needs_special_processing = matches!( &message, @@ -614,7 +613,7 @@ impl DashSpvClient, block_height: u32, - ) -> Result<()> { + ) -> crate::Result<()> { tracing::info!("💰 Balance changes detected in block at height {}:", block_height); for (address, change_sat) in balance_changes { diff --git a/dash-spv/src/client/transactions.rs b/dash-spv/src/client/transactions.rs index e45285a2d..ed273a3dd 100644 --- a/dash-spv/src/client/transactions.rs +++ b/dash-spv/src/client/transactions.rs @@ -1,6 +1,5 @@ //! Transaction-related client APIs (e.g., broadcasting) -use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use dashcore::network::message::NetworkMessage; @@ -10,17 +9,17 @@ use super::DashSpvClient; impl DashSpvClient { /// Broadcast a transaction to all connected peers. - pub async fn broadcast_transaction(&self, tx: &dashcore::Transaction) -> Result<()> { + pub async fn broadcast_transaction(&self, tx: &dashcore::Transaction) -> crate::Result<()> { let network = self .network .as_any() .downcast_ref::() .ok_or_else(|| { - SpvError::Config("Network manager does not support broadcasting".to_string()) + crate::Error::Config("Network manager does not support broadcasting".to_string()) })?; if network.peer_count() == 0 { - return Err(SpvError::Network(crate::error::NetworkError::NotConnected)); + return Err(crate::Error::Network(crate::NetworkError::NotConnected)); } let message = NetworkMessage::Tx(tx.clone()); @@ -38,7 +37,7 @@ impl DashSpvClient Self { - match self { - StorageError::Corruption(s) => StorageError::Corruption(s.clone()), - StorageError::NotFound(s) => StorageError::NotFound(s.clone()), - StorageError::WriteFailed(s) => StorageError::WriteFailed(s.clone()), - StorageError::ReadFailed(s) => StorageError::ReadFailed(s.clone()), - StorageError::Io(err) => StorageError::Io(io::Error::new(err.kind(), err.to_string())), - StorageError::Serialization(s) => StorageError::Serialization(s.clone()), - StorageError::InconsistentState(s) => StorageError::InconsistentState(s.clone()), - StorageError::LockPoisoned(s) => StorageError::LockPoisoned(s.clone()), - StorageError::DirectoryLocked(s) => StorageError::DirectoryLocked(s.clone()), - } - } -} - /// Validation-related errors. #[derive(Debug, Error)] pub enum ValidationError { @@ -171,12 +119,6 @@ pub enum ValidationError { #[error("Invalid signature: {0}")] InvalidSignature(String), - #[error("Invalid filter header chain: {0}")] - InvalidFilterHeaderChain(String), - - #[error("Consensus error: {0}")] - Consensus(String), - #[error("Masternode verification failed: {0}")] MasternodeVerification(String), @@ -191,11 +133,6 @@ pub enum SyncError { #[error("Sync already in progress")] SyncInProgress, - /// Deprecated: Use specific error variants instead - #[deprecated(note = "Use Network, Storage, Validation, or Timeout variants instead")] - #[error("Sync failed: {0}")] - SyncFailed(String), - /// Indicates an invalid state in the sync process (e.g., unexpected phase transitions) /// Use this for sync state machine errors, not validation errors #[error("Invalid sync state: {0}")] @@ -228,26 +165,8 @@ pub enum SyncError { Headers2DecompressionFailed(String), } -impl SyncError { - /// Returns a static string representing the error category based on the variant - pub fn category(&self) -> &'static str { - match self { - SyncError::SyncInProgress | SyncError::InvalidState(_) => "state", - SyncError::Timeout(_) => "timeout", - SyncError::Validation(_) => "validation", - SyncError::MissingDependency(_) => "dependency", - SyncError::Network(_) => "network", - SyncError::Storage(_) => "storage", - SyncError::Headers2DecompressionFailed(_) => "headers2", - // Deprecated variant - should not be used - #[allow(deprecated)] - SyncError::SyncFailed(_) => "unknown", - } - } -} - -/// Type alias for Result with SpvError. -pub type Result = std::result::Result; +/// Type alias for Result with dash_spv::Error. +pub type Result = std::result::Result; /// Type alias for network operation results. pub type NetworkResult = std::result::Result; @@ -263,72 +182,3 @@ pub type SyncResult = std::result::Result; /// Type alias for logging operation results. pub type LoggingResult = std::result::Result; - -/// Wallet-related errors. -#[derive(Debug, Error)] -pub enum WalletError { - #[error("Balance calculation overflow")] - BalanceOverflow, - - #[error("Unsupported address type: {0}")] - UnsupportedAddressType(String), - - #[error("UTXO not found: {0}")] - UtxoNotFound(dashcore::OutPoint), - - #[error("Invalid script pubkey")] - InvalidScriptPubkey, - - #[error("Wallet not initialized")] - NotInitialized, - - #[error("Transaction validation failed: {0}")] - TransactionValidation(String), - - #[error("Invalid transaction output at index {0}")] - InvalidOutput(usize), - - #[error("Address error: {0}")] - AddressError(String), - - #[error("Script error: {0}")] - ScriptError(String), -} - -/// Type alias for wallet operation results. -pub type WalletResult = std::result::Result; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_sync_error_category() { - // Test explicit variant categories - assert_eq!(SyncError::Timeout("test".to_string()).category(), "timeout"); - assert_eq!(SyncError::Network("test".to_string()).category(), "network"); - assert_eq!(SyncError::Validation("test".to_string()).category(), "validation"); - assert_eq!(SyncError::Storage("test".to_string()).category(), "storage"); - - // Test existing variant categories - assert_eq!(SyncError::SyncInProgress.category(), "state"); - assert_eq!(SyncError::InvalidState("test".to_string()).category(), "state"); - assert_eq!(SyncError::MissingDependency("test".to_string()).category(), "dependency"); - - // Test deprecated SyncFailed always returns "unknown" - #[allow(deprecated)] - { - assert_eq!( - SyncError::SyncFailed("connection timeout".to_string()).category(), - "unknown" - ); - assert_eq!(SyncError::SyncFailed("network error".to_string()).category(), "unknown"); - assert_eq!( - SyncError::SyncFailed("validation failed".to_string()).category(), - "unknown" - ); - assert_eq!(SyncError::SyncFailed("disk full".to_string()).category(), "unknown"); - assert_eq!(SyncError::SyncFailed("something else".to_string()).category(), "unknown"); - } - } -} diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index 472784574..215f10d76 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -56,12 +56,15 @@ //! - **Persistent storage**: Save and restore state between runs //! - **Extensive logging**: Built-in tracing support for debugging +#![deny(clippy::disallowed_types)] + #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; +mod error; + pub mod chain; pub mod client; -pub mod error; pub mod logging; pub mod mempool_filter; pub mod network; @@ -75,7 +78,8 @@ pub mod validation; // Re-export main types for convenience pub use client::{ClientConfig, DashSpvClient}; pub use error::{ - LoggingError, LoggingResult, NetworkError, SpvError, StorageError, SyncError, ValidationError, + Error, LoggingError, LoggingResult, NetworkError, NetworkResult, Result, StorageError, + StorageResult, SyncError, SyncResult, ValidationError, ValidationResult, }; pub use logging::{init_console_logging, init_logging, LogFileConfig, LoggingConfig, LoggingGuard}; pub use tracing::level_filters::LevelFilter; diff --git a/dash-spv/src/logging.rs b/dash-spv/src/logging.rs index 8e7d54cf9..597401e22 100644 --- a/dash-spv/src/logging.rs +++ b/dash-spv/src/logging.rs @@ -10,7 +10,7 @@ use tracing::level_filters::LevelFilter; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use crate::error::{LoggingError, LoggingResult}; +use crate::{LoggingError, LoggingResult}; /// Prefix for archived log files. const LOG_FILE_PREFIX: &str = "dash-spv."; diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 5566c5116..6e3e154f2 100644 --- a/dash-spv/src/main.rs +++ b/dash-spv/src/main.rs @@ -18,13 +18,12 @@ async fn main() { eprintln!("Error: {}", e); // Provide specific exit codes for different error types - let exit_code = if let Some(spv_error) = e.downcast_ref::() { + let exit_code = if let Some(spv_error) = e.downcast_ref::() { match spv_error { - dash_spv::SpvError::Network(_) => 1, - dash_spv::SpvError::Storage(_) => 2, - dash_spv::SpvError::Validation(_) => 3, - dash_spv::SpvError::Config(_) => 4, - dash_spv::SpvError::Parse(_) => 5, + dash_spv::Error::Network(_) => 1, + dash_spv::Error::Storage(_) => 2, + dash_spv::Error::Validation(_) => 3, + dash_spv::Error::Config(_) => 4, _ => 255, } } else { diff --git a/dash-spv/src/network/discovery.rs b/dash-spv/src/network/discovery.rs index b0f4408a0..9738dbc02 100644 --- a/dash-spv/src/network/discovery.rs +++ b/dash-spv/src/network/discovery.rs @@ -6,7 +6,6 @@ use hickory_resolver::name_server::TokioConnectionProvider; use hickory_resolver::TokioResolver; use std::net::{IpAddr, SocketAddr}; -use crate::error::SpvError as Error; use crate::network::constants::{MAINNET_DNS_SEEDS, TESTNET_DNS_SEEDS}; /// DNS discovery for finding initial peers @@ -16,7 +15,7 @@ pub struct DnsDiscovery { impl DnsDiscovery { /// Create a new DNS discovery instance - pub async fn new() -> Result { + pub async fn new() -> Result { let resolver = hickory_resolver::Resolver::builder_with_config( ResolverConfig::default(), TokioConnectionProvider::default(), diff --git a/dash-spv/src/network/handshake.rs b/dash-spv/src/network/handshake.rs index f738ce164..7dea7ec58 100644 --- a/dash-spv/src/network/handshake.rs +++ b/dash-spv/src/network/handshake.rs @@ -11,8 +11,8 @@ use dashcore::Network; // Hash trait not needed in current implementation use crate::client::config::MempoolStrategy; -use crate::error::{NetworkError, NetworkResult}; use crate::network::peer::Peer; +use crate::{NetworkError, NetworkResult}; /// Handshake state. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/dash-spv/src/network/manager.rs b/dash-spv/src/network/manager.rs index 9f41d6566..cc4f1476d 100644 --- a/dash-spv/src/network/manager.rs +++ b/dash-spv/src/network/manager.rs @@ -12,7 +12,6 @@ use tokio::time; use crate::client::config::MempoolStrategy; use crate::client::ClientConfig; -use crate::error::{NetworkError, NetworkResult, SpvError as Error}; use crate::network::addrv2::AddrV2Handler; use crate::network::constants::*; use crate::network::discovery::DnsDiscovery; @@ -77,7 +76,7 @@ pub struct PeerNetworkManager { impl PeerNetworkManager { /// Create a new peer network manager - pub async fn new(config: &ClientConfig) -> Result { + pub async fn new(config: &ClientConfig) -> Result { let (message_tx, message_rx) = mpsc::channel(1000); let discovery = DnsDiscovery::new().await?; @@ -131,7 +130,7 @@ impl PeerNetworkManager { } /// Start the network manager - pub async fn start(&self) -> Result<(), Error> { + pub async fn start(&self) -> Result<(), crate::Error> { log::info!("Starting peer network manager for {:?}", self.network); let mut peer_addresses = self.initial_peers.clone(); @@ -394,7 +393,7 @@ impl PeerNetworkManager { if let Err(e) = peer_guard.handle_ping(*nonce).await { log::error!("Failed to handle ping from {}: {}", addr, e); // If we can't send pong, connection is likely broken - if matches!(e, NetworkError::ConnectionFailed(_)) { + if matches!(e, crate::NetworkError::ConnectionFailed(_)) { log::warn!("Breaking peer reader loop for {} - failed to send pong response (iteration {})", addr, loop_iteration); break; } @@ -536,11 +535,11 @@ impl PeerNetworkManager { } Err(e) => { match e { - NetworkError::PeerDisconnected => { + crate::NetworkError::PeerDisconnected => { log::info!("Peer {} disconnected", addr); break; } - NetworkError::Timeout => { + crate::NetworkError::Timeout => { log::debug!("Timeout reading from {}, continuing...", addr); // Minor reputation penalty for timeout reputation_manager @@ -556,7 +555,7 @@ impl PeerNetworkManager { log::error!("Fatal error reading from {}: {}", addr, e); // Check if this is a serialization error that might have context - if let NetworkError::Serialization(ref decode_error) = e { + if let crate::NetworkError::Serialization(ref decode_error) = e { let error_msg = decode_error.to_string(); if error_msg.contains("unknown special transaction type") { log::warn!("Peer {} sent block with unsupported transaction type: {}", addr, decode_error); @@ -820,11 +819,11 @@ impl PeerNetworkManager { } /// Send a message to a single peer (using sticky peer selection for sync consistency) - async fn send_to_single_peer(&self, message: NetworkMessage) -> NetworkResult<()> { + async fn send_to_single_peer(&self, message: NetworkMessage) -> crate::NetworkResult<()> { let peers = self.pool.get_all_peers().await; if peers.is_empty() { - return Err(NetworkError::ConnectionFailed("No connected peers".to_string())); + return Err(crate::NetworkError::ConnectionFailed("No connected peers".to_string())); } // For filter-related messages, we need a peer that supports compact filters @@ -854,7 +853,7 @@ impl PeerNetworkManager { } None => { log::warn!("No peers support compact filters, cannot send {}", message.cmd()); - return Err(NetworkError::ProtocolError( + return Err(crate::NetworkError::ProtocolError( "No peers support compact filters".to_string(), )); } @@ -921,10 +920,9 @@ impl PeerNetworkManager { }; // Find the peer for the selected address - let (addr, peer) = peers - .iter() - .find(|(a, _)| *a == selected_peer) - .ok_or_else(|| NetworkError::ConnectionFailed("Selected peer not found".to_string()))?; + let (addr, peer) = peers.iter().find(|(a, _)| *a == selected_peer).ok_or_else(|| { + crate::NetworkError::ConnectionFailed("Selected peer not found".to_string()) + })?; // Upgrade GetHeaders to GetHeaders2 if this specific peer supports it and not disabled let peer_supports_headers2 = { @@ -970,14 +968,11 @@ impl PeerNetworkManager { } let mut peer_guard = peer.write().await; - peer_guard - .send_message(message) - .await - .map_err(|e| NetworkError::ProtocolError(format!("Failed to send to {}: {}", addr, e))) + peer_guard.send_message(message).await } /// Broadcast a message to all connected peers - pub async fn broadcast(&self, message: NetworkMessage) -> Vec> { + pub async fn broadcast(&self, message: NetworkMessage) -> Vec> { let peers = self.pool.get_all_peers().await; let mut handles = Vec::new(); @@ -996,7 +991,7 @@ impl PeerNetworkManager { let handle = tokio::spawn(async move { let mut peer_guard = peer.write().await; - peer_guard.send_message(msg).await.map_err(Error::Network) + peer_guard.send_message(msg).await.map_err(crate::Error::Network) }); handles.push(handle); } @@ -1006,9 +1001,11 @@ impl PeerNetworkManager { for handle in handles { match handle.await { Ok(result) => results.push(result), - Err(_) => results.push(Err(Error::Network(NetworkError::ConnectionFailed( - "Task panicked during broadcast".to_string(), - )))), + Err(_) => { + results.push(Err(crate::Error::Network(crate::NetworkError::ConnectionFailed( + "Task panicked during broadcast".to_string(), + )))) + } } } @@ -1016,7 +1013,11 @@ impl PeerNetworkManager { } /// Disconnect a specific peer - pub async fn disconnect_peer(&self, addr: &SocketAddr, reason: &str) -> Result<(), Error> { + pub async fn disconnect_peer( + &self, + addr: &SocketAddr, + reason: &str, + ) -> Result<(), crate::Error> { log::info!("Disconnecting peer {} - reason: {}", addr, reason); // Remove the peer @@ -1058,7 +1059,7 @@ impl PeerNetworkManager { } /// Ban a specific peer manually - pub async fn ban_peer(&self, addr: &SocketAddr, reason: &str) -> Result<(), Error> { + pub async fn ban_peer(&self, addr: &SocketAddr, reason: &str) -> Result<(), crate::Error> { log::info!("Manually banning peer {} - reason: {}", addr, reason); // Disconnect the peer first @@ -1151,16 +1152,16 @@ impl NetworkManager for PeerNetworkManager { self } - async fn connect(&mut self) -> NetworkResult<()> { - self.start().await.map_err(|e| NetworkError::ConnectionFailed(e.to_string())) + async fn connect(&mut self) -> crate::NetworkResult<()> { + self.start().await.map_err(|e| crate::NetworkError::ConnectionFailed(e.to_string())) } - async fn disconnect(&mut self) -> NetworkResult<()> { + async fn disconnect(&mut self) -> crate::NetworkResult<()> { self.shutdown().await; Ok(()) } - async fn send_message(&mut self, message: NetworkMessage) -> NetworkResult<()> { + async fn send_message(&mut self, message: NetworkMessage) -> crate::NetworkResult<()> { // For sync messages that require consistent responses, send to only one peer match &message { NetworkMessage::GetHeaders(_) @@ -1175,12 +1176,14 @@ impl NetworkManager for PeerNetworkManager { // Return error if all sends failed if results.is_empty() { - return Err(NetworkError::ConnectionFailed("No connected peers".to_string())); + return Err(crate::NetworkError::ConnectionFailed( + "No connected peers".to_string(), + )); } let successes = results.iter().filter(|r| r.is_ok()).count(); if successes == 0 { - return Err(NetworkError::ProtocolError( + return Err(crate::NetworkError::ProtocolError( "Failed to send to any peer".to_string(), )); } @@ -1194,7 +1197,7 @@ impl NetworkManager for PeerNetworkManager { &self, score_change: i32, reason: &str, - ) -> NetworkResult<()> { + ) -> crate::NetworkResult<()> { // Get the last peer that sent us a message if let Some(addr) = self.get_last_message_peer().await { self.reputation_manager.update_reputation(addr, score_change, reason).await; @@ -1205,7 +1208,7 @@ impl NetworkManager for PeerNetworkManager { async fn penalize_last_message_peer_invalid_chainlock( &self, reason: &str, - ) -> NetworkResult<()> { + ) -> crate::NetworkResult<()> { if let Some(addr) = self.get_last_message_peer().await { match self.disconnect_peer(&addr, reason).await { Ok(()) => { @@ -1241,7 +1244,7 @@ impl NetworkManager for PeerNetworkManager { async fn penalize_last_message_peer_invalid_instantlock( &self, reason: &str, - ) -> NetworkResult<()> { + ) -> crate::NetworkResult<()> { if let Some(addr) = self.get_last_message_peer().await { // Apply misbehavior score and a short temporary ban self.reputation_manager @@ -1274,7 +1277,7 @@ impl NetworkManager for PeerNetworkManager { Ok(()) } - async fn receive_message(&mut self) -> NetworkResult> { + async fn receive_message(&mut self) -> crate::NetworkResult> { let mut rx = self.message_rx.lock().await; // Use a timeout to prevent indefinite blocking when peers disconnect @@ -1330,7 +1333,7 @@ impl NetworkManager for PeerNetworkManager { }) } - async fn get_peer_best_height(&self) -> NetworkResult> { + async fn get_peer_best_height(&self) -> crate::NetworkResult> { let peers = self.pool.get_all_peers().await; if peers.is_empty() { @@ -1409,12 +1412,12 @@ impl NetworkManager for PeerNetworkManager { self.get_last_message_peer_id().await } - async fn update_peer_dsq_preference(&mut self, wants_dsq: bool) -> NetworkResult<()> { + async fn update_peer_dsq_preference(&mut self, wants_dsq: bool) -> crate::NetworkResult<()> { // Get the last peer that sent us a message let peer_id = self.get_last_message_peer_id().await; if peer_id.0 == 0 { - return Err(NetworkError::ConnectionFailed("No peer to update".to_string())); + return Err(crate::NetworkError::ConnectionFailed("No peer to update".to_string())); } // Find the peer's address from the last message data @@ -1428,7 +1431,7 @@ impl NetworkManager for PeerNetworkManager { Ok(()) } - async fn mark_peer_sent_headers2(&mut self) -> NetworkResult<()> { + async fn mark_peer_sent_headers2(&mut self) -> crate::NetworkResult<()> { // Get the last peer that sent us a message let last_msg_peer = self.last_message_peer.lock().await; if let Some(addr) = &*last_msg_peer { diff --git a/dash-spv/src/network/mod.rs b/dash-spv/src/network/mod.rs index 7f8409773..a12ebe927 100644 --- a/dash-spv/src/network/mod.rs +++ b/dash-spv/src/network/mod.rs @@ -15,7 +15,7 @@ mod tests; use async_trait::async_trait; -use crate::error::NetworkResult; +use crate::NetworkResult; use dashcore::network::message::NetworkMessage; use dashcore::BlockHash; diff --git a/dash-spv/src/network/peer.rs b/dash-spv/src/network/peer.rs index 1147a663b..7fbbfaf6b 100644 --- a/dash-spv/src/network/peer.rs +++ b/dash-spv/src/network/peer.rs @@ -12,9 +12,9 @@ use dashcore::consensus::{encode, Decodable}; use dashcore::network::message::{NetworkMessage, RawNetworkMessage}; use dashcore::Network; -use crate::error::{NetworkError, NetworkResult}; use crate::network::constants::PING_INTERVAL; use crate::types::PeerInfo; +use crate::{NetworkError, NetworkResult}; /// Internal state for the TCP connection struct ConnectionState { diff --git a/dash-spv/src/network/persist.rs b/dash-spv/src/network/persist.rs index 814eedeff..f52b62c9c 100644 --- a/dash-spv/src/network/persist.rs +++ b/dash-spv/src/network/persist.rs @@ -4,8 +4,7 @@ use dashcore::Network; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use crate::error::{SpvError as Error, StorageError}; -use crate::storage::io::atomic_write; +use crate::{storage::io::atomic_write, StorageError}; /// Peer persistence for saving and loading known peer addresses pub struct PeerStore { @@ -43,7 +42,7 @@ impl PeerStore { pub async fn save_peers( &self, peers: &[dashcore::network::address::AddrV2Message], - ) -> Result<(), Error> { + ) -> Result<(), crate::Error> { let saved = SavedPeers { version: 1, network: format!("{:?}", self.network), @@ -59,21 +58,22 @@ impl PeerStore { .collect(), }; - let json = serde_json::to_string_pretty(&saved) - .map_err(|e| Error::Storage(StorageError::Serialization(e.to_string())))?; + let json = serde_json::to_string_pretty(&saved).map_err(|e| { + crate::Error::Storage(crate::StorageError::Serialization(e.to_string())) + })?; - atomic_write(&self.path, json.as_bytes()).await.map_err(Error::Storage)?; + atomic_write(&self.path, json.as_bytes()).await?; log::debug!("Saved {} peers to {:?}", saved.peers.len(), self.path); Ok(()) } /// Load peers from disk - pub async fn load_peers(&self) -> Result, Error> { + pub async fn load_peers(&self) -> crate::Result> { match tokio::fs::read_to_string(&self.path).await { Ok(json) => { let saved: SavedPeers = serde_json::from_str(&json).map_err(|e| { - Error::Storage(StorageError::Corruption(format!( + crate::Error::Storage(crate::StorageError::Corruption(format!( "Failed to parse peers file: {}", e ))) @@ -81,7 +81,7 @@ impl PeerStore { // Verify network matches if saved.network != format!("{:?}", self.network) { - return Err(Error::Storage(StorageError::Corruption(format!( + return Err(crate::Error::Storage(crate::StorageError::Corruption(format!( "Peers file is for network {} but we are on {:?}", saved.network, self.network )))); @@ -97,19 +97,19 @@ impl PeerStore { log::debug!("No saved peers file found at {:?}", self.path); Ok(vec![]) } - Err(e) => Err(Error::Storage(StorageError::ReadFailed(e.to_string()))), + Err(e) => Err(crate::Error::Storage(StorageError::from(e))), } } /// Delete the peers file - pub async fn clear(&self) -> Result<(), Error> { + pub async fn clear(&self) -> crate::Result<()> { match tokio::fs::remove_file(&self.path).await { Ok(_) => { log::info!("Cleared peer store at {:?}", self.path); Ok(()) } Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), - Err(e) => Err(Error::Storage(StorageError::WriteFailed(e.to_string()))), + Err(e) => Err(crate::Error::Storage(StorageError::from(e))), } } } diff --git a/dash-spv/src/network/pool.rs b/dash-spv/src/network/pool.rs index b66777859..f8b19fde1 100644 --- a/dash-spv/src/network/pool.rs +++ b/dash-spv/src/network/pool.rs @@ -5,7 +5,6 @@ use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::RwLock; -use crate::error::{NetworkError, SpvError as Error}; use crate::network::constants::{MAX_PEERS, MIN_PEERS}; use crate::network::peer::Peer; @@ -33,7 +32,7 @@ impl PeerPool { } /// Add a peer to the pool - pub async fn add_peer(&self, addr: SocketAddr, peer: Peer) -> Result<(), Error> { + pub async fn add_peer(&self, addr: SocketAddr, peer: Peer) -> crate::Result<()> { let mut peers = self.peers.write().await; let mut connecting = self.connecting.write().await; @@ -42,7 +41,7 @@ impl PeerPool { // Check if we're at capacity if peers.len() >= MAX_PEERS { - return Err(Error::Network(NetworkError::ConnectionFailed(format!( + return Err(crate::Error::Network(crate::NetworkError::ConnectionFailed(format!( "Maximum peers ({}) reached", MAX_PEERS )))); @@ -50,7 +49,7 @@ impl PeerPool { // Check if already connected if peers.contains_key(&addr) { - return Err(Error::Network(NetworkError::ConnectionFailed(format!( + return Err(crate::Error::Network(crate::NetworkError::ConnectionFailed(format!( "Already connected to {}", addr )))); diff --git a/dash-spv/src/storage/blocks.rs b/dash-spv/src/storage/blocks.rs index 36d8dabfb..262c847e5 100644 --- a/dash-spv/src/storage/blocks.rs +++ b/dash-spv/src/storage/blocks.rs @@ -4,16 +4,17 @@ use std::collections::HashMap; use std::ops::Range; use std::path::PathBuf; -use crate::error::StorageResult; -use crate::storage::segments::SegmentCache; -use crate::storage::PersistentStorage; -use crate::types::HashedBlockHeader; use async_trait::async_trait; use dashcore::block::Header as BlockHeader; use dashcore::prelude::CoreBlockHeight; use dashcore::BlockHash; use tokio::sync::RwLock; +use crate::storage::segments::SegmentCache; +use crate::storage::PersistentStorage; +use crate::types::HashedBlockHeader; +use crate::StorageResult; + #[derive(Debug, PartialEq)] pub struct BlockHeaderTip { height: CoreBlockHeight, diff --git a/dash-spv/src/storage/chainstate.rs b/dash-spv/src/storage/chainstate.rs index c6c3b69af..c3b621015 100644 --- a/dash-spv/src/storage/chainstate.rs +++ b/dash-spv/src/storage/chainstate.rs @@ -68,7 +68,7 @@ impl ChainStateStorage for PersistentChainStateStorage { let content = tokio::fs::read_to_string(path).await?; let value: serde_json::Value = serde_json::from_str(&content).map_err(|e| { - crate::error::StorageError::Serialization(format!("Failed to parse chain state: {}", e)) + crate::StorageError::Serialization(format!("Failed to parse chain state: {}", e)) })?; let state = ChainState { diff --git a/dash-spv/src/storage/io.rs b/dash-spv/src/storage/io.rs index 9854d5ab0..e52dc373f 100644 --- a/dash-spv/src/storage/io.rs +++ b/dash-spv/src/storage/io.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; -use crate::error::{StorageError, StorageResult}; +use crate::{StorageError, StorageResult}; use tokio::io::AsyncWriteExt; /// Get the temporary file path for atomic writes. diff --git a/dash-spv/src/storage/lockfile.rs b/dash-spv/src/storage/lockfile.rs index 03d3a3141..376ff6462 100644 --- a/dash-spv/src/storage/lockfile.rs +++ b/dash-spv/src/storage/lockfile.rs @@ -4,7 +4,7 @@ use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; -use crate::error::{StorageError, StorageResult}; +use crate::{StorageError, StorageResult}; /// Lock file that prevents concurrent access from multiple processes. pub(super) struct LockFile { diff --git a/dash-spv/src/storage/masternode.rs b/dash-spv/src/storage/masternode.rs index d7ec1dd9f..6cfe74049 100644 --- a/dash-spv/src/storage/masternode.rs +++ b/dash-spv/src/storage/masternode.rs @@ -46,7 +46,7 @@ impl MasternodeStateStorage for PersistentMasternodeStateStorage { tokio::fs::create_dir_all(masternodestate_folder).await?; let json = serde_json::to_string_pretty(state).map_err(|e| { - crate::error::StorageError::Serialization(format!( + crate::StorageError::Serialization(format!( "Failed to serialize masternode state: {}", e )) @@ -65,7 +65,7 @@ impl MasternodeStateStorage for PersistentMasternodeStateStorage { let content = tokio::fs::read_to_string(path).await?; let state = serde_json::from_str(&content).map_err(|e| { - crate::error::StorageError::Serialization(format!( + crate::StorageError::Serialization(format!( "Failed to deserialize masternode state: {}", e )) diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 2baaa6d54..0e3ecdefa 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -23,7 +23,6 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; -use crate::error::StorageResult; use crate::storage::blocks::{BlockHeaderTip, PersistentBlockHeaderStorage}; use crate::storage::chainstate::PersistentChainStateStorage; use crate::storage::filters::{PersistentFilterHeaderStorage, PersistentFilterStorage}; @@ -33,6 +32,7 @@ use crate::storage::metadata::PersistentMetadataStorage; use crate::storage::transactions::PersistentTransactionStorage; use crate::types::{MempoolState, UnconfirmedTransaction}; use crate::ChainState; +use crate::StorageResult; pub use crate::storage::blocks::BlockHeaderStorage; pub use crate::storage::chainstate::ChainStateStorage; @@ -216,7 +216,7 @@ impl StorageManager for DiskStorageManager { tokio::time::sleep(std::time::Duration::from_millis(50)).await; tokio::fs::remove_dir_all(&self.storage_path).await?; } - Err(e) => return Err(crate::error::StorageError::Io(e)), + Err(e) => return Err(crate::StorageError::Io(e)), } tokio::fs::create_dir_all(&self.storage_path).await?; } diff --git a/dash-spv/src/sync/filters/download.rs b/dash-spv/src/sync/filters/download.rs index ce1052671..5de94971f 100644 --- a/dash-spv/src/sync/filters/download.rs +++ b/dash-spv/src/sync/filters/download.rs @@ -16,10 +16,10 @@ use dashcore::{ BlockHash, }; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::SyncProgress; +use crate::{SyncError, SyncResult}; impl super::manager::FilterSyncManager { pub async fn verify_cfilter_against_headers( diff --git a/dash-spv/src/sync/filters/headers.rs b/dash-spv/src/sync/filters/headers.rs index e1b53da1f..4d075ae14 100644 --- a/dash-spv/src/sync/filters/headers.rs +++ b/dash-spv/src/sync/filters/headers.rs @@ -22,9 +22,9 @@ use dashcore::{ use dashcore_hashes::{sha256d, Hash}; use super::types::*; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; impl super::manager::FilterSyncManager { pub(super) async fn find_available_header_at_or_before( diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index 1ae55501f..44fd3edad 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -4,10 +4,10 @@ //! that delegates to specialized sub-modules for headers, downloads, matching, etc. use crate::client::ClientConfig; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::SharedFilterHeights; +use crate::{SyncError, SyncResult}; use dashcore::{hash_types::FilterHeader, network::message_filter::CFHeaders, BlockHash}; use dashcore_hashes::{sha256d, Hash}; use std::collections::{HashMap, HashSet, VecDeque}; diff --git a/dash-spv/src/sync/filters/matching.rs b/dash-spv/src/sync/filters/matching.rs index 56161a148..9595d4c33 100644 --- a/dash-spv/src/sync/filters/matching.rs +++ b/dash-spv/src/sync/filters/matching.rs @@ -15,9 +15,9 @@ use dashcore::{ BlockHash, ScriptBuf, }; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; impl super::manager::FilterSyncManager { pub async fn check_filter_for_matches< diff --git a/dash-spv/src/sync/filters/requests.rs b/dash-spv/src/sync/filters/requests.rs index e6f61d806..746f58d1e 100644 --- a/dash-spv/src/sync/filters/requests.rs +++ b/dash-spv/src/sync/filters/requests.rs @@ -7,9 +7,9 @@ //! - Sending individual requests to the network use super::types::*; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; impl super::manager::FilterSyncManager { /// Build a queue of filter requests covering the specified range. diff --git a/dash-spv/src/sync/filters/retry.rs b/dash-spv/src/sync/filters/retry.rs index c0bd7fdbc..d5a7aa981 100644 --- a/dash-spv/src/sync/filters/retry.rs +++ b/dash-spv/src/sync/filters/retry.rs @@ -7,9 +7,9 @@ //! - Sync progress timeout detection use super::types::*; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; use dashcore::BlockHash; impl super::manager::FilterSyncManager { diff --git a/dash-spv/src/sync/headers/manager.rs b/dash-spv/src/sync/headers/manager.rs index 4498c16d9..fcdbb3f15 100644 --- a/dash-spv/src/sync/headers/manager.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -9,12 +9,12 @@ use dashcore_hashes::Hash; use crate::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, CheckpointManager}; use crate::chain::{ChainTip, ChainTipManager, ChainWork}; use crate::client::ClientConfig; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::{ChainState, HashedBlockHeader}; use crate::validation::{BlockHeaderValidator, Validator}; use crate::ValidationMode; +use crate::{SyncError, SyncResult}; use std::sync::Arc; use tokio::sync::RwLock; diff --git a/dash-spv/src/sync/manager.rs b/dash-spv/src/sync/manager.rs index 3b1d5d1fc..94c89f836 100644 --- a/dash-spv/src/sync/manager.rs +++ b/dash-spv/src/sync/manager.rs @@ -3,11 +3,11 @@ use super::phases::{PhaseTransition, SyncPhase}; use super::transitions::TransitionManager; use crate::client::ClientConfig; -use crate::error::SyncResult; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::sync::{FilterSyncManager, HeaderSyncManager, MasternodeSyncManager, ReorgConfig}; use crate::types::{SharedFilterHeights, SyncProgress}; +use crate::SyncResult; use crate::{SpvStats, SyncError}; use dashcore::prelude::CoreBlockHeight; use dashcore::BlockHash; diff --git a/dash-spv/src/sync/masternodes/manager.rs b/dash-spv/src/sync/masternodes/manager.rs index 38b35918c..024039a9c 100644 --- a/dash-spv/src/sync/masternodes/manager.rs +++ b/dash-spv/src/sync/masternodes/manager.rs @@ -15,9 +15,9 @@ use std::collections::HashMap; use std::time::{Duration, Instant}; use crate::client::ClientConfig; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; /// Simplified masternode synchronization following dash-evo-tool pattern. pub struct MasternodeSyncManager { diff --git a/dash-spv/src/sync/message_handlers.rs b/dash-spv/src/sync/message_handlers.rs index ef2f7b56a..d20cb3f49 100644 --- a/dash-spv/src/sync/message_handlers.rs +++ b/dash-spv/src/sync/message_handlers.rs @@ -7,9 +7,9 @@ use dashcore::block::Block; use dashcore::network::message::NetworkMessage; use dashcore::network::message_blockdata::Inventory; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; use key_wallet_manager::wallet_interface::WalletInterface; use super::manager::SyncManager; diff --git a/dash-spv/src/sync/phase_execution.rs b/dash-spv/src/sync/phase_execution.rs index 5922e43ca..8cb6b765d 100644 --- a/dash-spv/src/sync/phase_execution.rs +++ b/dash-spv/src/sync/phase_execution.rs @@ -2,9 +2,9 @@ use std::time::Instant; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; use key_wallet_manager::wallet_interface::WalletInterface; use super::manager::SyncManager; diff --git a/dash-spv/src/sync/post_sync.rs b/dash-spv/src/sync/post_sync.rs index c505ebf25..7a764d068 100644 --- a/dash-spv/src/sync/post_sync.rs +++ b/dash-spv/src/sync/post_sync.rs @@ -5,9 +5,9 @@ use dashcore::network::message::NetworkMessage; use dashcore::network::message_blockdata::Inventory; use dashcore::BlockHash; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; use key_wallet_manager::wallet_interface::WalletInterface; use super::manager::{SyncManager, CHAINLOCK_VALIDATION_MASTERNODE_OFFSET}; diff --git a/dash-spv/src/sync/transitions.rs b/dash-spv/src/sync/transitions.rs index e8ce58e93..e410e8db1 100644 --- a/dash-spv/src/sync/transitions.rs +++ b/dash-spv/src/sync/transitions.rs @@ -1,9 +1,9 @@ //! Phase transition logic for sequential sync use crate::client::ClientConfig; -use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; +use crate::{SyncError, SyncResult}; use dashcore::network::constants::ServiceFlags; use super::phases::{PhaseTransition, SyncPhase}; diff --git a/dash-spv/src/test_utils/network.rs b/dash-spv/src/test_utils/network.rs index 66ce349fc..61ebb91d3 100644 --- a/dash-spv/src/test_utils/network.rs +++ b/dash-spv/src/test_utils/network.rs @@ -8,9 +8,9 @@ use dashcore::{ }; use dashcore_hashes::Hash; -use crate::error::{NetworkError, NetworkResult}; use crate::network::NetworkManager; use crate::types::PeerInfo; +use crate::{NetworkError, NetworkResult}; /// Mock network manager for testing pub struct MockNetworkManager { diff --git a/dash-spv/src/validation/header.rs b/dash-spv/src/validation/header.rs index 8141e9992..0b4474e20 100644 --- a/dash-spv/src/validation/header.rs +++ b/dash-spv/src/validation/header.rs @@ -1,9 +1,9 @@ use rayon::prelude::*; use std::time::Instant; -use crate::error::{ValidationError, ValidationResult}; use crate::types::HashedBlockHeader; use crate::validation::Validator; +use crate::{ValidationError, ValidationResult}; #[derive(Default)] pub struct BlockHeaderValidator {} diff --git a/dash-spv/tests/chainlock_simple_test.rs b/dash-spv/tests/chainlock_simple_test.rs index 14e68d81a..c315429d4 100644 --- a/dash-spv/tests/chainlock_simple_test.rs +++ b/dash-spv/tests/chainlock_simple_test.rs @@ -57,7 +57,7 @@ async fn test_chainlock_validation_flow() { DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); // Test that update_chainlock_validation works - let updated = client.update_chainlock_validation().unwrap(); + let updated = client.update_chainlock_validation().await.unwrap(); // The update may succeed if masternodes are enabled and terminal block data is available // This is expected behavior - the client pre-loads terminal block data for mainnet diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index f1fe6a85c..893f0964b 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -7,10 +7,10 @@ use tokio::sync::Mutex; use dash_spv::{ client::ClientConfig, - error::NetworkResult, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, + NetworkResult, }; use dashcore::{ block::Header as BlockHeader, hash_types::FilterHeader, network::message::NetworkMessage, @@ -87,7 +87,7 @@ impl NetworkManager for MockNetworkManager { Vec::new() } - async fn get_peer_best_height(&self) -> dash_spv::error::NetworkResult> { + async fn get_peer_best_height(&self) -> dash_spv::NetworkResult> { Ok(Some(100)) } diff --git a/dash-spv/tests/error_types_test.rs b/dash-spv/tests/error_types_test.rs deleted file mode 100644 index 96e3c603e..000000000 --- a/dash-spv/tests/error_types_test.rs +++ /dev/null @@ -1,445 +0,0 @@ -//! Unit tests for error types, conversions, and formatting -//! -//! This test suite focuses on: -//! - Error type conversions and From implementations -//! - Error message formatting and context preservation -//! - Error category classification -//! - Nested error handling - -use dashcore::{OutPoint, Txid}; -use dashcore_hashes::Hash; -use std::io; - -use dash_spv::error::*; - -#[test] -fn test_network_error_from_io_error() { - let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "Connection refused"); - let net_err: NetworkError = io_err.into(); - - match net_err { - NetworkError::Io(_) => { - assert!(net_err.to_string().contains("Connection refused")); - } - _ => panic!("Expected NetworkError::Io variant"), - } -} - -#[test] -fn test_storage_error_from_io_error() { - let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "Permission denied"); - let storage_err: StorageError = io_err.into(); - - match storage_err { - StorageError::Io(_) => { - assert!(storage_err.to_string().contains("Permission denied")); - } - _ => panic!("Expected StorageError::Io variant"), - } -} - -#[test] -fn test_spv_error_from_network_error() { - let net_err = NetworkError::Timeout; - let spv_err: SpvError = net_err.into(); - - match spv_err { - SpvError::Network(NetworkError::Timeout) => { - assert_eq!(spv_err.to_string(), "Network error: Timeout occurred"); - } - _ => panic!("Expected SpvError::Network variant"), - } -} - -#[test] -fn test_spv_error_from_storage_error() { - let storage_err = StorageError::Corruption("Header checksum mismatch".to_string()); - let spv_err: SpvError = storage_err.into(); - - match &spv_err { - SpvError::Storage(StorageError::Corruption(msg)) => { - assert_eq!(msg, "Header checksum mismatch"); - assert!(spv_err.to_string().contains("Header checksum mismatch")); - } - _ => panic!("Expected SpvError::Storage variant"), - } -} - -#[test] -fn test_spv_error_from_validation_error() { - let val_err = ValidationError::InvalidProofOfWork; - let spv_err: SpvError = val_err.into(); - - match spv_err { - SpvError::Validation(ValidationError::InvalidProofOfWork) => { - assert_eq!(spv_err.to_string(), "Validation error: Invalid proof of work"); - } - _ => panic!("Expected SpvError::Validation variant"), - } -} - -#[test] -fn test_spv_error_from_sync_error() { - let sync_err = SyncError::SyncInProgress; - let spv_err: SpvError = sync_err.into(); - - match spv_err { - SpvError::Sync(SyncError::SyncInProgress) => { - assert_eq!(spv_err.to_string(), "Sync error: Sync already in progress"); - } - _ => panic!("Expected SpvError::Sync variant"), - } -} - -#[test] -fn test_spv_error_from_io_error() { - let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "Unexpected end of file"); - let spv_err: SpvError = io_err.into(); - - match spv_err { - SpvError::Io(_) => { - assert!(spv_err.to_string().contains("Unexpected end of file")); - } - _ => panic!("Expected SpvError::Io variant"), - } -} - -#[test] -fn test_validation_error_from_storage_error() { - let storage_err = StorageError::NotFound("Block header at height 12345".to_string()); - let val_err: ValidationError = storage_err.into(); - - match val_err { - ValidationError::StorageError(StorageError::NotFound(msg)) => { - assert_eq!(msg, "Block header at height 12345"); - } - _ => panic!("Expected ValidationError::StorageError variant"), - } -} - -#[test] -fn test_network_error_variants() { - let errors = vec![ - ( - NetworkError::ConnectionFailed("127.0.0.1:9999 refused connection".to_string()), - "Connection failed: 127.0.0.1:9999 refused connection", - ), - ( - NetworkError::HandshakeFailed("Version mismatch".to_string()), - "Handshake failed: Version mismatch", - ), - ( - NetworkError::ProtocolError("Invalid message format".to_string()), - "Protocol error: Invalid message format", - ), - (NetworkError::Timeout, "Timeout occurred"), - (NetworkError::PeerDisconnected, "Peer disconnected"), - (NetworkError::NotConnected, "Not connected"), - ( - NetworkError::AddressParse("Invalid IP address".to_string()), - "Address parse error: Invalid IP address", - ), - ( - NetworkError::SystemTime("Clock drift detected".to_string()), - "System time error: Clock drift detected", - ), - ]; - - for (error, expected_msg) in errors { - assert_eq!(error.to_string(), expected_msg); - } -} - -#[test] -fn test_storage_error_variants() { - let errors = vec![ - ( - StorageError::Corruption("Invalid segment header".to_string()), - "Corruption detected: Invalid segment header", - ), - ( - StorageError::NotFound("Header at height 1000".to_string()), - "Data not found: Header at height 1000", - ), - ( - StorageError::WriteFailed("/tmp/headers.dat: Permission denied".to_string()), - "Write failed: /tmp/headers.dat: Permission denied", - ), - ( - StorageError::ReadFailed("Segment file truncated".to_string()), - "Read failed: Segment file truncated", - ), - ( - StorageError::Serialization("Invalid encoding".to_string()), - "Serialization error: Invalid encoding", - ), - ( - StorageError::InconsistentState("Height mismatch".to_string()), - "Inconsistent state: Height mismatch", - ), - ( - StorageError::LockPoisoned("Mutex poisoned by panic".to_string()), - "Lock poisoned: Mutex poisoned by panic", - ), - ]; - - for (error, expected_msg) in errors { - assert_eq!(error.to_string(), expected_msg); - } -} - -#[test] -fn test_validation_error_variants() { - let errors = vec![ - (ValidationError::InvalidProofOfWork, "Invalid proof of work"), - ( - ValidationError::InvalidHeaderChain("Height 5000: timestamp regression".to_string()), - "Invalid header chain: Height 5000: timestamp regression", - ), - ( - ValidationError::InvalidChainLock("Signature verification failed".to_string()), - "Invalid ChainLock: Signature verification failed", - ), - ( - ValidationError::InvalidInstantLock("Quorum not found".to_string()), - "Invalid InstantLock: Quorum not found", - ), - ( - ValidationError::InvalidFilterHeaderChain("Hash mismatch at height 3000".to_string()), - "Invalid filter header chain: Hash mismatch at height 3000", - ), - ( - ValidationError::Consensus("Block size exceeds limit".to_string()), - "Consensus error: Block size exceeds limit", - ), - ( - ValidationError::MasternodeVerification("Invalid ProRegTx".to_string()), - "Masternode verification failed: Invalid ProRegTx", - ), - ]; - - for (error, expected_msg) in errors { - assert_eq!(error.to_string(), expected_msg); - } -} - -#[test] -fn test_sync_error_variants_and_categories() { - let test_cases = vec![ - (SyncError::SyncInProgress, "state", "Sync already in progress"), - ( - SyncError::InvalidState("Unexpected phase transition".to_string()), - "state", - "Invalid sync state: Unexpected phase transition", - ), - ( - SyncError::MissingDependency("Previous block not found".to_string()), - "dependency", - "Missing dependency: Previous block not found", - ), - ( - SyncError::Timeout("Peer response timeout".to_string()), - "timeout", - "Timeout error: Peer response timeout", - ), - ( - SyncError::Network("Connection lost".to_string()), - "network", - "Network error: Connection lost", - ), - ( - SyncError::Validation("Invalid block header".to_string()), - "validation", - "Validation error: Invalid block header", - ), - ( - SyncError::Storage("Database locked".to_string()), - "storage", - "Storage error: Database locked", - ), - ( - SyncError::Headers2DecompressionFailed("Invalid zstd stream".to_string()), - "headers2", - "Headers2 decompression failed: Invalid zstd stream", - ), - ]; - - for (error, expected_category, expected_msg) in test_cases { - assert_eq!(error.category(), expected_category); - assert_eq!(error.to_string(), expected_msg); - } -} - -#[test] -fn test_wallet_error_variants() { - let outpoint = OutPoint { - txid: Txid::from_byte_array([0xAB; 32]), - vout: 5, - }; - - let errors = vec![ - (WalletError::BalanceOverflow, "Balance calculation overflow"), - ( - WalletError::UnsupportedAddressType("P2WSH".to_string()), - "Unsupported address type: P2WSH", - ), - (WalletError::InvalidScriptPubkey, "Invalid script pubkey"), - (WalletError::NotInitialized, "Wallet not initialized"), - ( - WalletError::TransactionValidation("Invalid signature".to_string()), - "Transaction validation failed: Invalid signature", - ), - (WalletError::InvalidOutput(3), "Invalid transaction output at index 3"), - ( - WalletError::AddressError("Invalid network byte".to_string()), - "Address error: Invalid network byte", - ), - ( - WalletError::ScriptError("Script execution failed".to_string()), - "Script error: Script execution failed", - ), - ]; - - for (error, expected_msg) in errors { - assert_eq!(error.to_string(), expected_msg); - } - - // Special case for UTXO not found (contains hex) - let utxo_error = WalletError::UtxoNotFound(outpoint); - assert!(utxo_error.to_string().contains("UTXO not found")); - assert!(utxo_error.to_string().contains("abab")); // Partial hex from txid -} - -#[test] -fn test_parse_error_variants() { - let errors = vec![ - (ParseError::InvalidAddress("xyz123".to_string()), "Invalid network address: xyz123"), - (ParseError::InvalidNetwork("mainnet2".to_string()), "Invalid network name: mainnet2"), - ( - ParseError::MissingArgument("--storage-path".to_string()), - "Missing required argument: --storage-path", - ), - ( - ParseError::InvalidArgument("port".to_string(), "abc".to_string()), - "Invalid argument value for port: abc", - ), - ]; - - for (error, expected_msg) in errors { - assert_eq!(error.to_string(), expected_msg); - } -} - -#[test] -fn test_error_context_preservation() { - // Create a chain of errors to test context preservation - let io_err = io::Error::other("Disk failure"); - let storage_err: StorageError = io_err.into(); - let val_err: ValidationError = storage_err.into(); - let spv_err: SpvError = val_err.into(); - - // The final error should still contain the original context - let error_string = spv_err.to_string(); - assert!(error_string.contains("Validation error")); - assert!(error_string.contains("Storage error")); - assert!(error_string.contains("Disk failure")); -} - -#[test] -fn test_result_type_aliases() { - // Test that type aliases work correctly - fn network_operation() -> NetworkResult { - Err(NetworkError::Timeout) - } - - fn storage_operation() -> StorageResult { - Err(StorageError::NotFound("test".to_string())) - } - - fn validation_operation() -> ValidationResult { - Err(ValidationError::InvalidProofOfWork) - } - - fn sync_operation() -> SyncResult<()> { - Err(SyncError::SyncInProgress) - } - - fn wallet_operation() -> WalletResult { - Err(WalletError::BalanceOverflow) - } - - assert!(network_operation().is_err()); - assert!(storage_operation().is_err()); - assert!(validation_operation().is_err()); - assert!(sync_operation().is_err()); - assert!(wallet_operation().is_err()); -} - -#[test] -#[ignore] -fn test_error_display_formatting() { - // Test that errors format nicely for user display - let errors: Vec> = vec![ - Box::new(NetworkError::ConnectionFailed( - "peer1.example.com:9999 - Connection timed out after 30s".to_string(), - )), - Box::new(StorageError::WriteFailed( - "Cannot write to /var/lib/dash-spv/headers.dat: No space left on device (28)" - .to_string(), - )), - Box::new(ValidationError::InvalidHeaderChain( - "Block 523412: Previous block hash mismatch. Expected: 0x1234..., Got: 0x5678..." - .to_string(), - )), - Box::new(SyncError::Timeout( - "No response from peer after 60 seconds during header download".to_string(), - )), - Box::new(WalletError::TransactionValidation( - "Transaction abc123... has invalid signature in input 0".to_string(), - )), - ]; - - for error in errors { - let formatted = format!("{}", error); - assert!(!formatted.is_empty()); - assert!(formatted.len() > 10); // Should have meaningful content - - // Test that error chain formatting works - let debug_formatted = format!("{:?}", error); - assert!(debug_formatted.len() > formatted.len()); // Debug format should be more verbose - } -} - -#[test] -fn test_sync_error_deprecated_variant() { - // Test that deprecated SyncFailed variant still works but is marked deprecated - #[allow(deprecated)] - let error = SyncError::SyncFailed("This should not be used".to_string()); - - assert_eq!(error.category(), "unknown"); - assert!(error.to_string().contains("This should not be used")); -} - -#[test] -fn test_error_source_chain() { - // Test std::error::Error source() implementation - let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "Access denied"); - let storage_err = StorageError::Io(io_err); - let spv_err = SpvError::Storage(storage_err); - - // Should be able to walk the error chain - let mut error_messages = vec![]; - let mut current_error: &dyn std::error::Error = &spv_err; - - loop { - error_messages.push(current_error.to_string()); - match current_error.source() { - Some(source) => current_error = source, - None => break, - } - } - - assert!(error_messages.len() >= 2); - assert!(error_messages[0].contains("Storage error")); - assert!(error_messages.iter().any(|m| m.contains("Access denied"))); -} diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index 60b748294..5c53806b3 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -10,11 +10,11 @@ use dash_spv::{ client::ClientConfig, - error::{NetworkError, NetworkResult, SyncError}, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, types::PeerInfo, + {NetworkError, NetworkResult, SyncError}, }; use dashcore::{ block::{Header as BlockHeader, Version}, @@ -83,7 +83,7 @@ impl NetworkManager for MockNetworkManager { self } - async fn get_peer_best_height(&self) -> dash_spv::error::NetworkResult> { + async fn get_peer_best_height(&self) -> dash_spv::NetworkResult> { Ok(Some(100)) }