diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index ad9c45665..e05504e59 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -181,7 +181,6 @@ impl From for FFIDetailedSyncProgress { #[repr(C)] pub struct FFIChainState { - pub header_height: u32, pub masternode_height: u32, pub last_chainlock_height: u32, pub last_chainlock_hash: FFIString, @@ -191,7 +190,6 @@ pub struct FFIChainState { impl From for FFIChainState { fn from(state: ChainState) -> Self { FFIChainState { - header_height: state.headers.len() as u32, masternode_height: state.last_masternode_diff_height.unwrap_or(0), last_chainlock_height: state.last_chainlock_height.unwrap_or(0), last_chainlock_hash: FFIString::new( diff --git a/dash-spv-ffi/tests/unit/test_type_conversions.rs b/dash-spv-ffi/tests/unit/test_type_conversions.rs index 22a192cc6..264593c8f 100644 --- a/dash-spv-ffi/tests/unit/test_type_conversions.rs +++ b/dash-spv-ffi/tests/unit/test_type_conversions.rs @@ -163,7 +163,6 @@ mod tests { #[test] fn test_chain_state_none_values() { let state = dash_spv::ChainState { - headers: vec![], last_chainlock_height: None, last_chainlock_hash: None, current_filter_tip: None, @@ -173,7 +172,7 @@ mod tests { }; let ffi_state = FFIChainState::from(state); - assert_eq!(ffi_state.header_height, 0); + assert_eq!(ffi_state.masternode_height, 0); assert_eq!(ffi_state.last_chainlock_height, 0); assert_eq!(ffi_state.current_filter_tip, 0); diff --git a/dash-spv/src/chain/chainlock_manager.rs b/dash-spv/src/chain/chainlock_manager.rs index b4780bbd7..0bdcfb5c3 100644 --- a/dash-spv/src/chain/chainlock_manager.rs +++ b/dash-spv/src/chain/chainlock_manager.rs @@ -175,7 +175,11 @@ impl ChainLockManager { } // Verify the block exists in our chain - if let Some(header) = chain_state.header_at_height(chain_lock.block_height) { + if let Some(header) = storage + .get_header(chain_lock.block_height) + .await + .map_err(ValidationError::StorageError)? + { let header_hash = header.block_hash(); if header_hash != chain_lock.block_hash { return Err(ValidationError::InvalidChainLock(format!( diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index a003c8300..e3c1011d2 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -189,14 +189,17 @@ impl< /// Returns the current chain tip hash if available. pub async fn tip_hash(&self) -> Option { - let state = self.state.read().await; - state.tip_hash() + let storage = self.storage.lock().await; + + let tip_height = storage.get_tip_height().await?; + let header = storage.get_header(tip_height).await.ok()??; + + Some(header.block_hash()) } /// Returns the current chain tip height (absolute), accounting for checkpoint base. pub async fn tip_height(&self) -> u32 { - let state = self.state.read().await; - state.tip_height() + self.storage.lock().await.get_tip_height().await.unwrap_or(0) } /// Get current chain state (read-only). diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index a6acb2a25..548d5683b 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -168,30 +168,12 @@ impl< // This ensures the ChainState has headers loaded for both checkpoint and normal sync let tip_height = { let storage = self.storage.lock().await; - storage.get_tip_height().await.map_err(SpvError::Storage)?.unwrap_or(0) + storage.get_tip_height().await.unwrap_or(0) }; if tip_height > 0 { tracing::info!("Found {} headers in storage, loading into sync manager...", tip_height); - let loaded_count = { - let storage = self.storage.lock().await; - self.sync_manager.load_headers_from_storage(&storage).await - }; - - match loaded_count { - Ok(loaded_count) => { - tracing::info!("✅ Sync manager loaded {} headers from storage", loaded_count); - } - Err(e) => { - tracing::error!("Failed to load headers into sync manager: {}", e); - // For checkpoint sync, this is critical - let state = self.state.read().await; - if state.synced_from_checkpoint() { - return Err(SpvError::Sync(e)); - } - // For normal sync, we can continue as headers will be re-synced - tracing::warn!("Continuing without pre-loaded headers for normal sync"); - } - } + let storage = self.storage.lock().await; + self.sync_manager.load_headers_from_storage(&storage).await } // Connect to network @@ -208,8 +190,7 @@ impl< // Get initial header count from storage let (header_height, filter_height) = { let storage = self.storage.lock().await; - let h_height = - storage.get_tip_height().await.map_err(SpvError::Storage)?.unwrap_or(0); + let h_height = storage.get_tip_height().await.unwrap_or(0); let f_height = storage.get_filter_tip_height().await.map_err(SpvError::Storage)?.unwrap_or(0); (h_height, f_height) @@ -270,7 +251,7 @@ impl< // Check if we already have any headers in storage let current_tip = { let storage = self.storage.lock().await; - storage.get_tip_height().await.map_err(SpvError::Storage)? + storage.get_tip_height().await }; if current_tip.is_some() { @@ -343,12 +324,12 @@ impl< // Clone the chain state for storage let chain_state_for_storage = (*chain_state).clone(); - let headers_len = chain_state_for_storage.headers.len() as u32; drop(chain_state); // Update storage with chain state including sync_base_height { let mut storage = self.storage.lock().await; + storage.store_headers(&[checkpoint_header]).await?; storage .store_chain_state(&chain_state_for_storage) .await @@ -365,7 +346,7 @@ impl< ); // Update the sync manager's cached flags from the checkpoint-initialized state - self.sync_manager.update_chain_state_cache(checkpoint.height, headers_len); + self.sync_manager.update_chain_state_cache(checkpoint.height); tracing::info!( "Updated sync manager with checkpoint-initialized chain state" ); @@ -413,7 +394,7 @@ impl< // Verify it was stored correctly let stored_height = { let storage = self.storage.lock().await; - storage.get_tip_height().await.map_err(SpvError::Storage)? + storage.get_tip_height().await }; tracing::info!( "✅ Genesis block initialized at height 0, storage reports tip height: {:?}", diff --git a/dash-spv/src/client/progress.rs b/dash-spv/src/client/progress.rs index 7998560a6..5bc2b8d4c 100644 --- a/dash-spv/src/client/progress.rs +++ b/dash-spv/src/client/progress.rs @@ -38,7 +38,7 @@ impl< // Get current heights from storage { let storage = self.storage.lock().await; - if let Ok(Some(header_height)) = storage.get_tip_height().await { + if let Some(header_height) = storage.get_tip_height().await { stats.header_height = header_height; } diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index 0324fe964..3b07fca9d 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -76,7 +76,7 @@ impl<'a, S: StorageManager + Send + Sync + 'static, W: WalletInterface + Send + // For genesis sync: sync_base_height = 0, so height = 0 + storage_count // For checkpoint sync: height = checkpoint_height + storage_count let storage = self.storage.lock().await; - if let Ok(Some(storage_tip)) = storage.get_tip_height().await { + if let Some(storage_tip) = storage.get_tip_height().await { let blockchain_height = storage_tip; if with_logging { tracing::debug!( diff --git a/dash-spv/src/client/sync_coordinator.rs b/dash-spv/src/client/sync_coordinator.rs index de06633ec..2af4716dc 100644 --- a/dash-spv/src/client/sync_coordinator.rs +++ b/dash-spv/src/client/sync_coordinator.rs @@ -42,7 +42,7 @@ impl< let result = SyncProgress { header_height: { let storage = self.storage.lock().await; - storage.get_tip_height().await.map_err(SpvError::Storage)?.unwrap_or(0) + storage.get_tip_height().await.unwrap_or(0) }, filter_header_height: { let storage = self.storage.lock().await; @@ -241,7 +241,7 @@ impl< // Storage tip now represents the absolute blockchain height. let current_tip_height = { let storage = self.storage.lock().await; - storage.get_tip_height().await.ok().flatten().unwrap_or(0) + storage.get_tip_height().await.unwrap_or(0) }; let current_height = current_tip_height; let peer_best = self @@ -315,7 +315,7 @@ impl< // Emit filter headers progress only when heights change let (abs_header_height, filter_header_height) = { let storage = self.storage.lock().await; - let storage_tip = storage.get_tip_height().await.ok().flatten().unwrap_or(0); + let storage_tip = storage.get_tip_height().await.unwrap_or(0); let filter_tip = storage.get_filter_tip_height().await.ok().flatten().unwrap_or(0); (storage_tip, filter_tip) diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index aa8e0387a..8232b914d 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -81,7 +81,11 @@ pub trait StorageManager: Send + Sync { async fn get_header(&self, height: u32) -> StorageResult>; /// Get the current tip blockchain height. - async fn get_tip_height(&self) -> StorageResult>; + async fn get_tip_height(&self) -> Option; + + async fn get_start_height(&self) -> Option; + + async fn get_stored_headers_len(&self) -> u32; /// Store filter headers. async fn store_filter_headers(&mut self, headers: &[FilterHeader]) -> StorageResult<()>; diff --git a/dash-spv/src/storage/segments.rs b/dash-spv/src/storage/segments.rs index 2cab820af..c33c669d1 100644 --- a/dash-spv/src/storage/segments.rs +++ b/dash-spv/src/storage/segments.rs @@ -75,6 +75,7 @@ pub struct SegmentCache { segments: HashMap>, evicted: HashMap>, tip_height: Option, + start_height: Option, base_path: PathBuf, } @@ -133,12 +134,14 @@ impl SegmentCache { segments: HashMap::with_capacity(Self::MAX_ACTIVE_SEGMENTS), evicted: HashMap::new(), tip_height: None, + start_height: None, base_path, }; // Building the metadata if let Ok(entries) = fs::read_dir(&items_dir) { - let mut max_segment_id = None; + let mut max_seg_id = None; + let mut min_seg_id = None; for entry in entries.flatten() { if let Some(name) = entry.file_name().to_str() { @@ -149,19 +152,27 @@ impl SegmentCache { let segment_id_end = segment_id_start + 4; if let Ok(id) = name[segment_id_start..segment_id_end].parse::() { - max_segment_id = - Some(max_segment_id.map_or(id, |max: u32| max.max(id))); + max_seg_id = Some(max_seg_id.map_or(id, |max: u32| max.max(id))); + min_seg_id = Some(min_seg_id.map_or(id, |min: u32| min.min(id))); } } } } - if let Some(segment_id) = max_segment_id { + if let Some(segment_id) = max_seg_id { let segment = cache.get_segment(&segment_id).await?; cache.tip_height = segment .last_valid_offset() - .map(|offset| segment_id * Segment::::ITEMS_PER_SEGMENT + offset); + .map(|offset| Self::segment_id_to_start_height(segment_id) + offset); + } + + if let Some(segment_id) = min_seg_id { + let segment = cache.get_segment(&segment_id).await?; + + cache.start_height = segment + .first_valid_offset() + .map(|offset| Self::segment_id_to_start_height(segment_id) + offset); } } @@ -349,6 +360,11 @@ impl SegmentCache { None => Some(height - 1), }; + self.start_height = match self.start_height { + Some(current) => Some(current.min(start_height)), + None => Some(start_height), + }; + Ok(()) } @@ -377,6 +393,11 @@ impl SegmentCache { self.tip_height } + #[inline] + pub fn start_height(&self) -> Option { + self.start_height + } + #[inline] pub fn next_height(&self) -> u32 { match self.tip_height() { diff --git a/dash-spv/src/storage/state.rs b/dash-spv/src/storage/state.rs index 664c7e0fd..31f5fdda8 100644 --- a/dash-spv/src/storage/state.rs +++ b/dash-spv/src/storage/state.rs @@ -16,10 +16,6 @@ use super::manager::DiskStorageManager; impl DiskStorageManager { /// Store chain state to disk. pub async fn store_chain_state(&mut self, state: &ChainState) -> StorageResult<()> { - // First store all headers - // For checkpoint sync, we need to store headers starting from the checkpoint height - self.store_headers_at_height(&state.headers, state.sync_base_height).await?; - // Store other state as JSON let state_data = serde_json::json!({ "last_chainlock_height": state.last_chainlock_height, @@ -48,7 +44,7 @@ impl DiskStorageManager { crate::error::StorageError::Serialization(format!("Failed to parse chain state: {}", e)) })?; - let mut state = ChainState { + let state = ChainState { last_chainlock_height: value .get("last_chainlock_height") .and_then(|v| v.as_u64()) @@ -71,14 +67,8 @@ impl DiskStorageManager { .and_then(|v| v.as_u64()) .map(|h| h as u32) .unwrap_or(0), - ..Default::default() }; - let range_start = state.sync_base_height; - if let Some(tip_height) = self.get_tip_height().await? { - state.headers = self.load_headers(range_start..tip_height + 1).await?; - } - Ok(Some(state)) } @@ -340,11 +330,40 @@ impl StorageManager for DiskStorageManager { } async fn get_header(&self, height: u32) -> StorageResult> { + if self.get_tip_height().await.is_none_or(|tip_height| height > tip_height) { + return Ok(None); + } + + if self.get_start_height().await.is_none_or(|start_height| height < start_height) { + return Ok(None); + } + Ok(self.block_headers.write().await.get_items(height..height + 1).await?.first().copied()) } - async fn get_tip_height(&self) -> StorageResult> { - Ok(self.block_headers.read().await.tip_height()) + async fn get_tip_height(&self) -> Option { + self.block_headers.read().await.tip_height() + } + + async fn get_start_height(&self) -> Option { + self.block_headers.read().await.start_height() + } + + async fn get_stored_headers_len(&self) -> u32 { + let headers_guard = self.block_headers.read().await; + let start_height = if let Some(start_height) = headers_guard.start_height() { + start_height + } else { + return 0; + }; + + let end_height = if let Some(end_height) = headers_guard.tip_height() { + end_height + } else { + return 0; + }; + + end_height - start_height + 1 } async fn store_filter_headers( @@ -575,6 +594,7 @@ mod tests { storage.store_chain_state(&base_state).await?; storage.store_headers_at_height(&headers, checkpoint_height).await?; + assert_eq!(storage.get_stored_headers_len().await, headers.len() as u32); // Verify headers are stored at correct blockchain heights let header_at_base = storage.get_header(checkpoint_height).await?; diff --git a/dash-spv/src/sync/filters/headers.rs b/dash-spv/src/sync/filters/headers.rs index 40ce1622f..f1f165949 100644 --- a/dash-spv/src/sync/filters/headers.rs +++ b/dash-spv/src/sync/filters/headers.rs @@ -82,13 +82,9 @@ impl SyncResult<(u32, u32, u32)> { - let header_tip_height = storage - .get_tip_height() - .await - .map_err(|e| SyncError::Storage(format!("Failed to get header tip height: {}", e)))? - .ok_or_else(|| { - SyncError::Storage("No headers available for filter sync".to_string()) - })?; + let header_tip_height = storage.get_tip_height().await.ok_or_else(|| { + SyncError::Storage("No headers available for filter sync".to_string()) + })?; let stop_height = self .find_height_for_block_hash(&cf_headers.stop_hash, storage, 0, header_tip_height) @@ -188,13 +184,9 @@ impl= header_tip_height { tracing::info!("Filter headers already synced to header tip"); @@ -773,11 +761,7 @@ impl header_tip) } diff --git a/dash-spv/src/sync/filters/retry.rs b/dash-spv/src/sync/filters/retry.rs index f998066d0..fe7103792 100644 --- a/dash-spv/src/sync/filters/retry.rs +++ b/dash-spv/src/sync/filters/retry.rs @@ -35,13 +35,9 @@ impl { chain_state: Arc>, // WalletState removed - wallet functionality is now handled externally headers2_state: Headers2StateManager, - total_headers_synced: u32, syncing_headers: bool, last_sync_progress: std::time::Instant, headers2_failed: bool, @@ -88,7 +87,6 @@ impl SyncResult { - let start_time = std::time::Instant::now(); - let mut loaded_count = 0; - let mut tip_height = 0; + pub async fn load_headers_from_storage(&mut self, storage: &S) { // First, try to load the persisted chain state which may contain sync_base_height if let Ok(Some(stored_chain_state)) = storage.load_chain_state().await { tracing::info!( @@ -111,26 +106,11 @@ impl {}, chain_state.headers.len()={}", - batch_size, - previous_total, - self.total_headers_synced, - self.chain_state.read().await.headers.len() + "Header sync progress: processed {} headers in batch, total_headers_synced: {}", + headers.len() as u32, + storage.get_stored_headers_len().await, ); // Update chain tip manager with the last header in the batch if let Some(last_header) = headers.last() { - let final_height = self.chain_state.read().await.get_height(); + let final_height = storage.get_tip_height().await.unwrap_or(0); let chain_work = ChainWork::from_height_and_header(final_height, last_header); let tip = ChainTip::new(*last_header, final_height, chain_work); self.tip_manager @@ -290,7 +262,7 @@ impl, + storage: &S, ) -> SyncResult<()> { let block_locator = match base_hash { Some(hash) => vec![hash], None => { // Check if we're syncing from a checkpoint - if self.is_synced_from_checkpoint() - && !self.chain_state.read().await.headers.is_empty() - { + if self.is_synced_from_checkpoint() && storage.get_stored_headers_len().await > 0 { + let first_height = storage + .get_start_height() + .await + .ok_or(SyncError::Storage("Failed to get start height".to_string()))?; + let checkpoint_header = storage + .get_header(first_height) + .await + .map_err(|e| { + SyncError::Storage(format!("Failed to get first header: {}", e)) + })? + .ok_or(SyncError::Storage( + "Storage didn't return first header".to_string(), + ))?; + // Use the checkpoint hash from chain state - let checkpoint_hash = self.chain_state.read().await.headers[0].block_hash(); + let checkpoint_hash = checkpoint_header.block_hash(); tracing::info!( "📍 No base_hash provided but syncing from checkpoint at height {}. Using checkpoint hash: {}", self.get_sync_base_height(), @@ -348,7 +333,7 @@ impl { // No headers in storage - check if we're syncing from a checkpoint - if self.is_synced_from_checkpoint() - && !self.chain_state.read().await.headers.is_empty() - { - // We're syncing from a checkpoint and have the checkpoint header - let checkpoint_header = &self.chain_state.read().await.headers[0]; + if self.is_synced_from_checkpoint() && storage.get_stored_headers_len().await > 0 { let checkpoint_hash = checkpoint_header.block_hash(); tracing::info!( "No headers in storage but syncing from checkpoint at height {}. Using checkpoint hash: {}", @@ -545,8 +538,12 @@ impl 0 { let hash = checkpoint_header.block_hash(); tracing::info!("Using checkpoint hash for height {}: {}", height, hash); Some(hash) @@ -639,7 +635,7 @@ impl { // No headers in storage - check if we're syncing from a checkpoint if self.is_synced_from_checkpoint() { // Use the checkpoint hash from chain state - if !self.chain_state.read().await.headers.is_empty() { - let checkpoint_hash = - self.chain_state.read().await.headers[0].block_hash(); + if storage.get_stored_headers_len().await > 0 { + let checkpoint_hash = checkpoint_header.block_hash(); tracing::info!( "Using checkpoint hash for recovery: {} (chain state has {} headers, first header time: {})", checkpoint_hash, - self.chain_state.read().await.headers.len(), - self.chain_state.read().await.headers[0].time + storage.get_stored_headers_len().await, + checkpoint_header.time ); Some(checkpoint_hash) } else { @@ -720,7 +722,7 @@ impl u32 { - // Always use total_headers_synced which tracks the absolute blockchain height - self.total_headers_synced - } - - /// Get the tip hash - pub async fn get_tip_hash(&self) -> Option { - self.chain_state.read().await.tip_hash() + pub async fn get_chain_height(&self, storage: &S) -> u32 { + storage.get_tip_height().await.unwrap_or(0) } /// Get the sync base height (used when syncing from checkpoint) @@ -865,9 +857,7 @@ impl SyncResult { + pub async fn load_headers_from_storage(&mut self, storage: &S) { // Load headers into the header sync manager - let loaded_count = self.header_sync.load_headers_from_storage(storage).await?; - - if loaded_count > 0 { - tracing::info!("Sequential sync manager loaded {} headers from storage", loaded_count); - - // Update the current phase if we have headers - // This helps the sync manager understand where to resume from - if matches!(self.current_phase, SyncPhase::Idle) { - // We have headers but haven't started sync yet - // The phase will be properly set when start_sync is called - tracing::debug!("Headers loaded but sync not started yet"); - } - } - - Ok(loaded_count) + self.header_sync.load_headers_from_storage(storage).await; } /// Get the earliest wallet birth height hint for the configured network, if available. @@ -226,7 +212,7 @@ impl< let base_hash = self.get_base_hash_from_storage(storage).await?; // Request headers starting from our current tip - self.header_sync.request_headers(network, base_hash).await?; + self.header_sync.request_headers(network, base_hash, storage).await?; } else { // Otherwise start sync normally self.header_sync.start_sync(network, storage).await?; @@ -257,10 +243,7 @@ impl< &self, storage: &S, ) -> SyncResult> { - let current_tip_height = storage - .get_tip_height() - .await - .map_err(|e| SyncError::Storage(format!("Failed to get tip height: {}", e)))?; + let current_tip_height = storage.get_tip_height().await; let base_hash = match current_tip_height { None => None, @@ -276,11 +259,6 @@ impl< Ok(base_hash) } - /// Get the current chain height from the header sync manager - pub fn get_chain_height(&self) -> u32 { - self.header_sync.get_chain_height() - } - /// Get current sync progress template. /// /// **IMPORTANT**: This method returns a TEMPLATE ONLY. It does NOT query storage or network @@ -370,8 +348,8 @@ impl< } /// Update the chain state (used for checkpoint sync initialization) - pub fn update_chain_state_cache(&mut self, sync_base_height: u32, headers_len: u32) { - self.header_sync.update_cached_from_state_snapshot(sync_base_height, headers_len); + pub fn update_chain_state_cache(&mut self, sync_base_height: u32) { + self.header_sync.update_cached_from_state_snapshot(sync_base_height); } /// Get reference to the masternode engine if available. @@ -393,22 +371,7 @@ impl< } /// Get the actual blockchain height from storage height, accounting for checkpoints - pub(super) async fn get_blockchain_height_from_storage(&self, storage: &S) -> SyncResult { - let storage_height = storage - .get_tip_height() - .await - .map_err(|e| { - crate::error::SyncError::Storage(format!("Failed to get tip height: {}", e)) - })? - .unwrap_or(0); - - // Check if we're syncing from a checkpoint - if self.header_sync.is_synced_from_checkpoint() { - // For checkpoint sync, blockchain height = sync_base_height + storage_height - Ok(self.header_sync.get_sync_base_height() + storage_height) - } else { - // Normal sync: storage height IS the blockchain height - Ok(storage_height) - } + pub(super) async fn get_blockchain_height_from_storage(&self, storage: &S) -> u32 { + storage.get_tip_height().await.unwrap_or(0) } } diff --git a/dash-spv/src/sync/masternodes/manager.rs b/dash-spv/src/sync/masternodes/manager.rs index 065f26dbc..c5eebcbf0 100644 --- a/dash-spv/src/sync/masternodes/manager.rs +++ b/dash-spv/src/sync/masternodes/manager.rs @@ -391,11 +391,7 @@ impl { + Some(tip_height) => { let state = crate::storage::MasternodeState { last_height: tip_height, engine_state: Vec::new(), @@ -477,17 +473,11 @@ impl { + None => { tracing::warn!( "⚠️ Storage returned no tip height when persisting masternode state" ); } - Err(e) => { - tracing::warn!( - "⚠️ Failed to read tip height to persist masternode state: {}", - e - ); - } } } } @@ -518,13 +508,7 @@ impl { + Some(tip_height) => { let state = crate::storage::MasternodeState { last_height: tip_height, engine_state: Vec::new(), @@ -688,17 +672,11 @@ impl { + None => { tracing::warn!( "⚠️ Storage returned no tip height when persisting masternode state" ); } - Err(e) => { - tracing::warn!( - "⚠️ Failed to read tip height to persist masternode state: {}", - e - ); - } } } else { tracing::info!( diff --git a/dash-spv/src/sync/message_handlers.rs b/dash-spv/src/sync/message_handlers.rs index 33a359a91..b9ad0bc25 100644 --- a/dash-spv/src/sync/message_handlers.rs +++ b/dash-spv/src/sync/message_handlers.rs @@ -345,7 +345,7 @@ impl< storage: &mut S, transition_reason: &str, ) -> SyncResult<()> { - let blockchain_height = self.get_blockchain_height_from_storage(storage).await.unwrap_or(0); + let blockchain_height = self.get_blockchain_height_from_storage(storage).await; let should_transition = if let SyncPhase::DownloadingHeaders { current_height, diff --git a/dash-spv/src/sync/phase_execution.rs b/dash-spv/src/sync/phase_execution.rs index 77758d833..252f58ba6 100644 --- a/dash-spv/src/sync/phase_execution.rs +++ b/dash-spv/src/sync/phase_execution.rs @@ -32,7 +32,7 @@ impl< // Already prepared, just send the initial request let base_hash = self.get_base_hash_from_storage(storage).await?; - self.header_sync.request_headers(network, base_hash).await?; + self.header_sync.request_headers(network, base_hash, storage).await?; } else { // Not prepared yet, start sync normally self.header_sync.start_sync(network, storage).await?; @@ -43,47 +43,6 @@ impl< .. } => { tracing::info!("📥 Starting masternode list download phase"); - // Get the effective chain height from header sync which accounts for checkpoint base - let effective_height = self.header_sync.get_chain_height(); - let sync_base_height = self.header_sync.get_sync_base_height(); - - // Also get the actual tip height to verify (blockchain height) - let storage_tip = storage - .get_tip_height() - .await - .map_err(|e| SyncError::Storage(format!("Failed to get storage tip: {}", e)))?; - - // Debug: Check chain state - let chain_state = storage.load_chain_state().await.map_err(|e| { - SyncError::Storage(format!("Failed to load chain state: {}", e)) - })?; - let chain_state_height = chain_state.as_ref().map(|s| s.get_height()).unwrap_or(0); - - tracing::info!( - "Starting masternode sync: effective_height={}, sync_base={}, storage_tip={:?}, chain_state_height={}, expected_storage_index={}", - effective_height, - sync_base_height, - storage_tip, - chain_state_height, - if sync_base_height > 0 { effective_height.saturating_sub(sync_base_height) } else { effective_height } - ); - - // Use the minimum of effective height and what's actually in storage - let _safe_height = if let Some(tip) = storage_tip { - let storage_based_height = tip; - if storage_based_height < effective_height { - tracing::warn!( - "Chain state height {} exceeds storage height {}, using storage height", - effective_height, - storage_based_height - ); - storage_based_height - } else { - effective_height - } - } else { - effective_height - }; // Start masternode sync (unified processing) match self.masternode_sync.start_sync(network, storage).await { diff --git a/dash-spv/src/sync/transitions.rs b/dash-spv/src/sync/transitions.rs index 505e2a541..e8ce58e93 100644 --- a/dash-spv/src/sync/transitions.rs +++ b/dash-spv/src/sync/transitions.rs @@ -177,11 +177,7 @@ impl TransitionManager { match current_phase { SyncPhase::Idle => { // Always start with headers - let start_height = storage - .get_tip_height() - .await - .map_err(|e| SyncError::Storage(format!("Failed to get tip height: {}", e)))? - .unwrap_or(0); + let start_height = storage.get_tip_height().await.unwrap_or(0); Ok(Some(SyncPhase::DownloadingHeaders { start_time: Instant::now(), @@ -199,13 +195,7 @@ impl TransitionManager { .. } => { if self.config.enable_masternodes { - let header_tip = storage - .get_tip_height() - .await - .map_err(|e| { - SyncError::Storage(format!("Failed to get header tip: {}", e)) - })? - .unwrap_or(0); + let header_tip = storage.get_tip_height().await.unwrap_or(0); let mn_height = match storage.load_masternode_state().await { Ok(Some(state)) => state.last_height, @@ -417,11 +407,7 @@ impl TransitionManager { &self, storage: &S, ) -> SyncResult> { - let header_tip = storage - .get_tip_height() - .await - .map_err(|e| SyncError::Storage(format!("Failed to get header tip: {}", e)))? - .unwrap_or(0); + let header_tip = storage.get_tip_height().await.unwrap_or(0); let filter_tip = storage .get_filter_tip_height() diff --git a/dash-spv/src/types.rs b/dash-spv/src/types.rs index 71e65cb16..47dff94cd 100644 --- a/dash-spv/src/types.rs +++ b/dash-spv/src/types.rs @@ -245,17 +245,8 @@ impl DetailedSyncProgress { /// /// ## Checkpoint Sync /// When syncing from a checkpoint (not genesis), `sync_base_height` is non-zero. -/// The `headers` vector contains headers starting from the checkpoint, not from genesis. -/// Use `tip_height()` to get the absolute blockchain height. -/// -/// ## Memory Considerations -/// - headers: ~80 bytes per header -/// - At 2M blocks: ~160MB for headers #[derive(Clone, Default)] pub struct ChainState { - /// Block headers indexed by height. - pub headers: Vec, - /// Last ChainLock height. pub last_chainlock_height: Option, @@ -285,28 +276,6 @@ impl ChainState { pub fn new_for_network(network: Network) -> Self { let mut state = Self::default(); - // Initialize with genesis block - let genesis_header = match network { - Network::Dash => { - // Use known genesis for mainnet - dashcore::blockdata::constants::genesis_block(network).header - } - Network::Testnet => { - // Use known genesis for testnet - dashcore::blockdata::constants::genesis_block(network).header - } - _ => { - // For other networks, use the existing genesis block function - dashcore::blockdata::constants::genesis_block(network).header - } - }; - - // Add genesis header to the chain state - state.headers.push(genesis_header); - - tracing::debug!("Initialized ChainState with genesis block - network: {:?}, hash: {}, headers_count: {}", - network, genesis_header.block_hash(), state.headers.len()); - // Initialize masternode engine for the network let mut engine = MasternodeListEngine::default_for_network(network); if let Some(genesis_hash) = network.known_genesis_block_hash() { @@ -325,57 +294,6 @@ impl ChainState { self.sync_base_height > 0 } - /// Get the current tip height. - pub fn tip_height(&self) -> u32 { - if self.headers.is_empty() { - // When headers is empty, sync_base_height represents our current position - // This happens when we're syncing from a checkpoint but haven't received headers yet - self.sync_base_height - } else { - // Normal case: base + number of headers - 1 - self.sync_base_height + self.headers.len() as u32 - 1 - } - } - - /// Get the current tip hash. - pub fn tip_hash(&self) -> Option { - self.headers.last().map(|h| h.block_hash()) - } - - /// Get header at the given height. - pub fn header_at_height(&self, height: u32) -> Option<&BlockHeader> { - if height < self.sync_base_height { - return None; // Height is before our sync base - } - let index = (height - self.sync_base_height) as usize; - self.headers.get(index) - } - - /// Add headers to the chain. - pub fn add_headers(&mut self, headers: Vec) { - self.headers.extend(headers); - } - - /// Get the tip header - pub fn get_tip_header(&self) -> Option { - self.headers.last().copied() - } - - /// Get the height - pub fn get_height(&self) -> u32 { - self.tip_height() - } - - /// Add a single header - pub fn add_header(&mut self, header: BlockHeader) { - self.headers.push(header); - } - - /// Remove the tip header (for reorgs) - pub fn remove_tip(&mut self) -> Option { - self.headers.pop() - } - /// Update chain lock status pub fn update_chain_lock(&mut self, height: u32, hash: BlockHash) { // Only update if this is a newer chain lock @@ -408,26 +326,6 @@ impl ChainState { Some(Vec::new()) } - /// Calculate the total chain work up to the tip - pub fn calculate_chain_work(&self) -> Option { - use crate::chain::chain_work::ChainWork; - - // If we have no headers, return None - if self.headers.is_empty() { - return None; - } - - // Start with zero work - let mut total_work = ChainWork::zero(); - - // Add work from each header - for header in &self.headers { - total_work = total_work.add_header(header); - } - - Some(total_work) - } - /// Initialize chain state from a checkpoint. pub fn init_from_checkpoint( &mut self, @@ -435,15 +333,9 @@ impl ChainState { checkpoint_header: BlockHeader, network: Network, ) { - // Clear any existing headers - self.headers.clear(); - // Set sync base height to checkpoint self.sync_base_height = checkpoint_height; - // Add the checkpoint header as our first header - self.headers.push(checkpoint_header); - tracing::info!( "Initialized ChainState from checkpoint - height: {}, hash: {}, network: {:?}", checkpoint_height, @@ -475,7 +367,6 @@ impl ChainState { impl std::fmt::Debug for ChainState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ChainState") - .field("headers", &format!("{} headers", self.headers.len())) .field("last_chainlock_height", &self.last_chainlock_height) .field("last_chainlock_hash", &self.last_chainlock_hash) .field("current_filter_tip", &self.current_filter_tip) diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index 370cf88d8..c5d4760b5 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -144,7 +144,7 @@ async fn test_filter_sync_at_tip_edge_case() { storage.store_filter_headers(&filter_headers).await.unwrap(); // Verify initial state - let tip_height = storage.get_tip_height().await.unwrap().unwrap(); + let tip_height = storage.get_tip_height().await.unwrap(); let filter_tip_height = storage.get_filter_tip_height().await.unwrap().unwrap(); assert_eq!(tip_height, height - 1); // 0-indexed assert_eq!(filter_tip_height, height - 1); // 0-indexed diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index 0cb6a5fa5..e8753411e 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -197,7 +197,7 @@ async fn test_filter_header_verification_failure_reproduction() { let initial_headers = create_test_headers_range(1000, 5000); // Headers 1000-4999 storage.store_headers(&initial_headers).await.expect("Failed to store initial headers"); - let tip_height = storage.get_tip_height().await.unwrap().unwrap(); + let tip_height = storage.get_tip_height().await.unwrap(); println!("Initial header chain stored: tip height = {}", tip_height); assert_eq!(tip_height, 4999); @@ -361,7 +361,7 @@ async fn test_overlapping_batches_from_different_peers() { let initial_headers = create_test_headers_range(1, 3000); // Headers 1-2999 storage.store_headers(&initial_headers).await.expect("Failed to store initial headers"); - let tip_height = storage.get_tip_height().await.unwrap().unwrap(); + let tip_height = storage.get_tip_height().await.unwrap(); println!("Header chain stored: tip height = {}", tip_height); assert_eq!(tip_height, 2999); diff --git a/dash-spv/tests/header_sync_test.rs b/dash-spv/tests/header_sync_test.rs index 35148f209..b5a795644 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -30,7 +30,7 @@ async fn test_basic_header_sync_from_genesis() { .expect("Failed to create tmp storage"); // Verify empty initial state - assert_eq!(storage.get_tip_height().await.unwrap(), None); + assert_eq!(storage.get_tip_height().await, None); // Create test chain state for mainnet let chain_state = ChainState::new_for_network(Network::Dash); @@ -57,7 +57,7 @@ async fn test_header_sync_continuation() { storage.store_headers(&existing_headers).await.expect("Failed to store existing headers"); // Verify we have the expected tip - assert_eq!(storage.get_tip_height().await.unwrap(), Some(99)); + assert_eq!(storage.get_tip_height().await, Some(99)); // Simulate adding more headers (continuation) let continuation_headers = create_test_header_chain_from(100, 50); @@ -67,7 +67,7 @@ async fn test_header_sync_continuation() { .expect("Failed to store continuation headers"); // Verify the chain extended properly - assert_eq!(storage.get_tip_height().await.unwrap(), Some(149)); + assert_eq!(storage.get_tip_height().await, Some(149)); // Verify continuity by checking some headers for height in 95..105 { @@ -102,7 +102,7 @@ async fn test_header_batch_processing() { let expected_tip = batch_end - 1; assert_eq!( - storage.get_tip_height().await.unwrap(), + storage.get_tip_height().await, Some(expected_tip as u32), "Tip height should be {} after batch {}-{}", expected_tip, @@ -112,7 +112,7 @@ async fn test_header_batch_processing() { } // Verify total count - let final_tip = storage.get_tip_height().await.unwrap(); + let final_tip = storage.get_tip_height().await; assert_eq!(final_tip, Some((total_headers - 1) as u32)); // Verify we can retrieve headers from different parts of the chain @@ -140,17 +140,17 @@ async fn test_header_sync_edge_cases() { // Test 1: Empty header batch let empty_headers: Vec = vec![]; storage.store_headers(&empty_headers).await.expect("Should handle empty header batch"); - assert_eq!(storage.get_tip_height().await.unwrap(), None); + assert_eq!(storage.get_tip_height().await, None); // Test 2: Single header let single_header = create_test_header_chain(1); storage.store_headers(&single_header).await.expect("Should handle single header"); - assert_eq!(storage.get_tip_height().await.unwrap(), Some(0)); + assert_eq!(storage.get_tip_height().await, Some(0)); // Test 3: Large batch let large_batch = create_test_header_chain_from(1, 5000); storage.store_headers(&large_batch).await.expect("Should handle large header batch"); - assert_eq!(storage.get_tip_height().await.unwrap(), Some(5000)); + assert_eq!(storage.get_tip_height().await, Some(5000)); // Test 4: Out-of-order access let header_4500 = storage.get_header(4500).await.unwrap(); @@ -191,7 +191,7 @@ async fn test_header_chain_validation() { storage.store_headers(&chain).await.expect("Failed to store header chain"); // Verify the chain is stored correctly - assert_eq!(storage.get_tip_height().await.unwrap(), Some(9)); + assert_eq!(storage.get_tip_height().await, Some(9)); // Verify we can retrieve the entire chain let retrieved_chain = storage.load_headers(0..10).await.unwrap(); @@ -229,7 +229,7 @@ async fn test_header_sync_performance() { let sync_duration = start_time.elapsed(); // Verify sync completed correctly - assert_eq!(storage.get_tip_height().await.unwrap(), Some((total_headers - 1) as u32)); + assert_eq!(storage.get_tip_height().await, Some((total_headers - 1) as u32)); // Performance assertions (these are rough benchmarks) assert!( @@ -338,7 +338,7 @@ async fn test_header_storage_consistency() { storage.store_headers(&headers).await.expect("Failed to store headers"); // Test consistency: get tip and verify it matches the last stored header - let tip_height = storage.get_tip_height().await.unwrap().unwrap(); + let tip_height = storage.get_tip_height().await.unwrap(); let tip_header = storage.get_header(tip_height).await.unwrap().unwrap(); let expected_tip = &headers[headers.len() - 1]; @@ -358,48 +358,6 @@ async fn test_header_storage_consistency() { info!("Header storage consistency test completed"); } -#[test_case(0, 0 ; "genesis_0_blocks")] -#[test_case(0, 1 ; "genesis_1_block")] -#[test_case(0, 60000 ; "genesis_60000_blocks")] -#[test_case(100, 0 ; "checkpoint_0_blocks")] -#[test_case(170000, 1 ; "checkpoint_1_block")] -#[test_case(12345, 60000 ; "checkpoint_60000_blocks")] -#[tokio::test] -async fn test_load_headers_from_storage(sync_base_height: u32, header_count: usize) { - // Setup: Create storage with 100 headers - let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let mut storage = DiskStorageManager::new(temp_dir.path().to_path_buf()) - .await - .expect("Failed to create storage"); - - let test_headers = create_test_header_chain(header_count); - - // Store chain state - let mut chain_state = ChainState::new_for_network(Network::Dash); - chain_state.sync_base_height = sync_base_height; - chain_state.headers = test_headers.clone(); - storage.store_chain_state(&chain_state).await.expect("Failed to store chain state"); - - // Create HeaderSyncManager and load headers - let config = ClientConfig::new(Network::Dash); - let chain_state = Arc::new(RwLock::new(ChainState::new_for_network(Network::Dash))); - let mut header_sync = HeaderSyncManager::::new( - &config, - ReorgConfig::default(), - chain_state.clone(), - ) - .expect("Failed to create HeaderSyncManager"); - - // Load headers from storage - let loaded_count = - header_sync.load_headers_from_storage(&storage).await.expect("Failed to load headers"); - - let cs = chain_state.read().await; - - assert_eq!(loaded_count as usize, header_count, "Loaded count mismatch"); - assert_eq!(header_count, cs.headers.len(), "Chain state count mismatch"); -} - #[test_case(0, 1 ; "genesis_1_block")] #[test_case(0, 70000 ; "genesis_70000_blocks")] #[test_case(5000, 1 ; "checkpoint_1_block")] @@ -417,8 +375,8 @@ async fn test_prepare_sync(sync_base_height: u32, header_count: usize) { // Create and store chain state let mut chain_state = ChainState::new_for_network(Network::Dash); chain_state.sync_base_height = sync_base_height; - chain_state.headers = headers; storage.store_chain_state(&chain_state).await.expect("Failed to store chain state"); + storage.store_headers(&headers).await.expect("Failed to store headers"); // Create HeaderSyncManager and load from storage let config = ClientConfig::new(Network::Dash); diff --git a/dash-spv/tests/integration_real_node_test.rs b/dash-spv/tests/integration_real_node_test.rs index 07fd32a6b..f493a7abd 100644 --- a/dash-spv/tests/integration_real_node_test.rs +++ b/dash-spv/tests/integration_real_node_test.rs @@ -206,7 +206,7 @@ async fn test_real_header_sync_up_to_10k() { .expect("Failed to create tmp storage"); // Verify starting from empty state - assert_eq!(storage.get_tip_height().await.unwrap(), None); + assert_eq!(storage.get_tip_height().await, None); let mut client = create_test_client(config.clone()).await.expect("Failed to create SPV client"); diff --git a/dash-spv/tests/rollback_test.rs b/dash-spv/tests/rollback_test.rs index d2424f972..7634648c6 100644 --- a/dash-spv/tests/rollback_test.rs +++ b/dash-spv/tests/rollback_test.rs @@ -42,7 +42,7 @@ async fn test_disk_storage_rollback() -> Result<(), Box> storage.store_headers(&headers).await?; // Verify we have 10 headers - let tip_height = storage.get_tip_height().await?; + let tip_height = storage.get_tip_height().await; assert_eq!(tip_height, Some(9)); // Load all headers to verify @@ -54,7 +54,7 @@ async fn test_disk_storage_rollback() -> Result<(), Box> // TODO: Test assertions commented out because rollback_to_height is not implemented // Verify tip height is now 5 - let _ = storage.get_tip_height().await?; + let _ = storage.get_tip_height().await; // assert_eq!(tip_height_after_rollback, Some(5)); // Verify we can only load headers up to height 5 diff --git a/dash-spv/tests/segmented_storage_debug.rs b/dash-spv/tests/segmented_storage_debug.rs index 611a5eaa0..a26bec774 100644 --- a/dash-spv/tests/segmented_storage_debug.rs +++ b/dash-spv/tests/segmented_storage_debug.rs @@ -38,7 +38,7 @@ async fn test_basic_storage() { println!("Headers stored"); // Check tip height - let tip = storage.get_tip_height().await.unwrap(); + let tip = storage.get_tip_height().await; println!("Tip height: {:?}", tip); assert_eq!(tip, Some(9)); diff --git a/dash-spv/tests/segmented_storage_test.rs b/dash-spv/tests/segmented_storage_test.rs index bc570e67a..a9bcf4917 100644 --- a/dash-spv/tests/segmented_storage_test.rs +++ b/dash-spv/tests/segmented_storage_test.rs @@ -46,7 +46,7 @@ async fn test_segmented_storage_basic_operations() { } // Verify we can read them back - assert_eq!(storage.get_tip_height().await.unwrap(), Some(99_999)); + assert_eq!(storage.get_tip_height().await, Some(99_999)); // Check individual headers assert_eq!(storage.get_header(0).await.unwrap().unwrap().time, 0); @@ -76,7 +76,7 @@ async fn test_segmented_storage_persistence() { let mut storage = DiskStorageManager::new(path.clone()).await.unwrap(); // Verify storage starts empty - assert_eq!(storage.get_tip_height().await.unwrap(), None, "Storage should start empty"); + assert_eq!(storage.get_tip_height().await, None, "Storage should start empty"); let headers: Vec = (0..75_000).map(create_test_header).collect(); storage.store_headers(&headers).await.unwrap(); @@ -91,7 +91,7 @@ async fn test_segmented_storage_persistence() { { let storage = DiskStorageManager::new(path).await.unwrap(); - let actual_tip = storage.get_tip_height().await.unwrap(); + let actual_tip = storage.get_tip_height().await; if actual_tip != Some(74_999) { println!("Expected tip 74,999 but got {:?}", actual_tip); // Try to understand what's stored @@ -265,7 +265,7 @@ async fn test_background_save_timing() { // Verify data was saved { let storage = DiskStorageManager::new(path).await.unwrap(); - assert_eq!(storage.get_tip_height().await.unwrap(), Some(19_999)); + assert_eq!(storage.get_tip_height().await, Some(19_999)); assert_eq!(storage.get_header(15_000).await.unwrap().unwrap().time, 15_000); } } @@ -279,13 +279,13 @@ async fn test_clear_storage() { let headers: Vec = (0..10_000).map(create_test_header).collect(); storage.store_headers(&headers).await.unwrap(); - assert_eq!(storage.get_tip_height().await.unwrap(), Some(9_999)); + assert_eq!(storage.get_tip_height().await, Some(9_999)); // Clear storage storage.clear().await.unwrap(); // Verify everything is cleared - assert_eq!(storage.get_tip_height().await.unwrap(), None); + assert_eq!(storage.get_tip_height().await, None); assert_eq!(storage.get_header_height_by_hash(&headers[0].block_hash()).await.unwrap(), None); } @@ -311,7 +311,7 @@ async fn test_mixed_operations() { storage.store_metadata("test_key", b"test_value").await.unwrap(); // Verify everything - assert_eq!(storage.get_tip_height().await.unwrap(), Some(74_999)); + assert_eq!(storage.get_tip_height().await, Some(74_999)); assert_eq!(storage.get_filter_tip_height().await.unwrap(), Some(74_999)); let filters = storage.load_filters(1000..1001).await.unwrap(); diff --git a/dash-spv/tests/simple_header_test.rs b/dash-spv/tests/simple_header_test.rs index 0ea61c395..26c46d065 100644 --- a/dash-spv/tests/simple_header_test.rs +++ b/dash-spv/tests/simple_header_test.rs @@ -57,7 +57,7 @@ async fn test_simple_header_sync() { .expect("Failed to create tmp storage"); // Verify starting from empty state - assert_eq!(storage.get_tip_height().await.unwrap(), None); + assert_eq!(storage.get_tip_height().await, None); // Create network manager let network_manager = diff --git a/dash-spv/tests/simple_segmented_test.rs b/dash-spv/tests/simple_segmented_test.rs index 422bb78ed..327c08779 100644 --- a/dash-spv/tests/simple_segmented_test.rs +++ b/dash-spv/tests/simple_segmented_test.rs @@ -28,7 +28,7 @@ async fn test_simple_storage() { let mut storage = DiskStorageManager::new(temp_dir.path().to_path_buf()).await.unwrap(); println!("Testing get_tip_height before storing anything..."); - let initial_tip = storage.get_tip_height().await.unwrap(); + let initial_tip = storage.get_tip_height().await; println!("Initial tip: {:?}", initial_tip); assert_eq!(initial_tip, None); @@ -40,7 +40,7 @@ async fn test_simple_storage() { println!("Single header stored"); println!("Checking tip height..."); - let tip = storage.get_tip_height().await.unwrap(); + let tip = storage.get_tip_height().await; println!("Tip height after storing one header: {:?}", tip); assert_eq!(tip, Some(0)); diff --git a/dash-spv/tests/storage_consistency_test.rs b/dash-spv/tests/storage_consistency_test.rs index 8bdd682b7..a5640bf74 100644 --- a/dash-spv/tests/storage_consistency_test.rs +++ b/dash-spv/tests/storage_consistency_test.rs @@ -36,7 +36,7 @@ async fn test_tip_height_header_consistency_basic() { storage.store_headers(&headers).await.unwrap(); // Check consistency immediately - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; println!("Tip height: {:?}", tip_height); if let Some(height) = tip_height { @@ -72,7 +72,7 @@ async fn test_tip_height_header_consistency_after_save() { // Wait for background save to complete sleep(Duration::from_secs(1)).await; - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; println!("Phase 1 - Tip height: {:?}", tip_height); if let Some(height) = tip_height { @@ -87,7 +87,7 @@ async fn test_tip_height_header_consistency_after_save() { { let storage = DiskStorageManager::new(storage_path.clone()).await.unwrap(); - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; println!("Phase 2 - Tip height after reload: {:?}", tip_height); if let Some(height) = tip_height { @@ -129,7 +129,7 @@ async fn test_tip_height_header_consistency_large_dataset() { storage.store_headers(&headers).await.unwrap(); // Check consistency after each batch - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; if let Some(height) = tip_height { let header = storage.get_header(height).await.unwrap(); if header.is_none() { @@ -155,7 +155,7 @@ async fn test_tip_height_header_consistency_large_dataset() { } // Final consistency check - let final_tip = storage.get_tip_height().await.unwrap(); + let final_tip = storage.get_tip_height().await; println!("Final tip height: {:?}", final_tip); if let Some(height) = final_tip { @@ -206,7 +206,7 @@ async fn test_concurrent_tip_header_access() { let handle = tokio::spawn(async move { // Repeatedly check consistency for iteration in 0..100 { - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; if let Some(height) = tip_height { let header = storage.get_header(height).await.unwrap(); @@ -278,7 +278,7 @@ async fn test_reproduce_filter_sync_bug() { storage.store_headers(&tip_header).await.unwrap(); // Now check what get_tip_height() returns - let reported_tip = storage.get_tip_height().await.unwrap(); + let reported_tip = storage.get_tip_height().await; println!("Storage reports tip height: {:?}", reported_tip); if let Some(tip_height) = reported_tip { @@ -346,7 +346,7 @@ async fn test_reproduce_filter_sync_bug_small() { storage.store_headers(&tip_header).await.unwrap(); // Now check what get_tip_height() returns - let reported_tip = storage.get_tip_height().await.unwrap(); + let reported_tip = storage.get_tip_height().await; println!("Storage reports tip height: {:?}", reported_tip); if let Some(tip_height) = reported_tip { @@ -406,7 +406,7 @@ async fn test_segment_boundary_consistency() { segment_size + 1, // Second in second segment ]; - let tip_height = storage.get_tip_height().await.unwrap().unwrap(); + let tip_height = storage.get_tip_height().await.unwrap(); println!("Tip height: {}", tip_height); for height in boundary_heights { @@ -461,7 +461,7 @@ async fn test_reproduce_tip_height_segment_eviction_race() { storage.store_headers(&headers).await.unwrap(); // Immediately check for race condition - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; if let Some(height) = tip_height { // Try to access the tip header multiple times to catch race condition @@ -542,7 +542,7 @@ async fn test_concurrent_tip_height_access_with_eviction() { // Reduced from 50 to 20 iterations for iteration in 0..20 { // Get tip height - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; if let Some(height) = tip_height { // Immediately try to access the tip header @@ -606,7 +606,7 @@ async fn test_concurrent_tip_height_access_with_eviction_heavy() { let handle = tokio::spawn(async move { for iteration in 0..50 { // Get tip height - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; if let Some(height) = tip_height { // Immediately try to access the tip header @@ -659,7 +659,7 @@ async fn test_tip_height_segment_boundary_race() { storage.store_headers(&headers).await.unwrap(); // Verify tip is at segment boundary - let tip_height = storage.get_tip_height().await.unwrap(); + let tip_height = storage.get_tip_height().await; assert_eq!(tip_height, Some(segment_size - 1)); storage.shutdown().await; @@ -678,7 +678,7 @@ async fn test_tip_height_segment_boundary_race() { storage.store_headers(&headers).await.unwrap(); // After storing each segment, verify tip consistency - let reported_tip = storage.get_tip_height().await.unwrap(); + let reported_tip = storage.get_tip_height().await; if let Some(tip) = reported_tip { let header = storage.get_header(tip).await.unwrap(); if header.is_none() { @@ -698,7 +698,7 @@ async fn test_tip_height_segment_boundary_race() { } // But the current tip should always be accessible - let current_tip = storage.get_tip_height().await.unwrap(); + let current_tip = storage.get_tip_height().await; if let Some(tip) = current_tip { let header = storage.get_header(tip).await.unwrap(); assert!(header.is_some(), "Current tip header must always be accessible"); diff --git a/dash-spv/tests/storage_test.rs b/dash-spv/tests/storage_test.rs index 254a5162e..89db5da24 100644 --- a/dash-spv/tests/storage_test.rs +++ b/dash-spv/tests/storage_test.rs @@ -1,7 +1,9 @@ //! Integration tests for storage layer functionality. -use dash_spv::error::StorageError; -use dash_spv::storage::{DiskStorageManager, StorageManager}; +use dash_spv::{ + storage::{DiskStorageManager, StorageManager}, + StorageError, +}; use dashcore::{block::Header as BlockHeader, block::Version}; use dashcore_hashes::Hash; use tempfile::TempDir; @@ -57,7 +59,7 @@ async fn test_disk_storage_reopen_after_clean_shutdown() { assert!(storage.is_ok(), "Should reopen after clean shutdown"); let storage = storage.unwrap(); - let tip = storage.get_tip_height().await.unwrap(); + let tip = storage.get_tip_height().await; assert_eq!(tip, Some(4), "Data should persist across reopen"); } @@ -80,9 +82,6 @@ async fn test_disk_storage_concurrent_access_blocked() { } other => panic!("Expected DirectoryLocked error, got: {:?}", other), } - - // First storage manager should still be usable - assert!(_storage1.get_tip_height().await.is_ok()); } #[tokio::test]