diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index e25f8dd59..33c44c8c2 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -20,7 +20,7 @@ use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::sync::filters::FilterNotificationSender; -use crate::sync::sequential::SyncManager; +use crate::sync::SyncManager; use crate::types::{ChainState, DetailedSyncProgress, MempoolState, SpvEvent, SpvStats}; use crate::validation::ValidationManager; use key_wallet_manager::wallet_interface::WalletInterface; diff --git a/dash-spv/src/client/filter_sync.rs b/dash-spv/src/client/filter_sync.rs index fb6b35fcb..a1d996610 100644 --- a/dash-spv/src/client/filter_sync.rs +++ b/dash-spv/src/client/filter_sync.rs @@ -3,7 +3,7 @@ use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::sequential::SyncManager; +use crate::sync::SyncManager; use crate::types::FilterMatch; use crate::types::SpvStats; use key_wallet_manager::wallet_interface::WalletInterface; diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index 838cef5b6..5528ac6e7 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -17,7 +17,7 @@ use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::sequential::SyncManager; +use crate::sync::SyncManager; use crate::types::{ChainState, MempoolState, SpvStats}; use crate::validation::ValidationManager; use dashcore::network::constants::NetworkExt; diff --git a/dash-spv/src/client/message_handler.rs b/dash-spv/src/client/message_handler.rs index 523988c10..d5e394ebb 100644 --- a/dash-spv/src/client/message_handler.rs +++ b/dash-spv/src/client/message_handler.rs @@ -5,7 +5,7 @@ use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::sequential::SyncManager; +use crate::sync::SyncManager; use crate::types::{MempoolState, SpvEvent, SpvStats}; // Removed local ad-hoc compact filter construction in favor of always processing full blocks use key_wallet_manager::wallet_interface::WalletInterface; diff --git a/dash-spv/src/client/message_handler_test.rs b/dash-spv/src/client/message_handler_test.rs index 04b4d10be..ec52488a3 100644 --- a/dash-spv/src/client/message_handler_test.rs +++ b/dash-spv/src/client/message_handler_test.rs @@ -12,7 +12,7 @@ mod tests { use crate::storage::memory::MemoryStorageManager; use crate::storage::StorageManager; use crate::sync::filters::FilterNotificationSender; - use crate::sync::sequential::SyncManager; + use crate::sync::SyncManager; use crate::types::{ChainState, MempoolState, SpvEvent, SpvStats}; use crate::validation::ValidationManager; use crate::wallet::Wallet; diff --git a/dash-spv/src/client/progress.rs b/dash-spv/src/client/progress.rs index d7b8f50d5..7998560a6 100644 --- a/dash-spv/src/client/progress.rs +++ b/dash-spv/src/client/progress.rs @@ -8,7 +8,7 @@ use crate::error::Result; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::sequential::phases::SyncPhase; +use crate::sync::SyncPhase; use crate::types::{SpvStats, SyncProgress, SyncStage}; use key_wallet_manager::wallet_interface::WalletInterface; diff --git a/dash-spv/src/sync/headers.rs b/dash-spv/src/sync/headers/manager.rs similarity index 99% rename from dash-spv/src/sync/headers.rs rename to dash-spv/src/sync/headers/manager.rs index b96e6004c..0ca81c93d 100644 --- a/dash-spv/src/sync/headers.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -12,7 +12,7 @@ use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::headers2_state::Headers2StateManager; +use crate::sync::headers2::Headers2StateManager; use crate::types::{CachedHeader, ChainState}; use std::sync::Arc; use tokio::sync::RwLock; @@ -611,10 +611,8 @@ impl SyncManager { + /// Create a new sequential sync manager + pub fn new( + config: &ClientConfig, + received_filter_heights: SharedFilterHeights, + wallet: Arc>, + chain_state: Arc>, + stats: Arc>, + ) -> SyncResult { + // Create reorg config with sensible defaults + let reorg_config = ReorgConfig::default(); + + Ok(Self { + current_phase: SyncPhase::Idle, + transition_manager: TransitionManager::new(config), + header_sync: HeaderSyncManager::new(config, reorg_config, chain_state).map_err( + |e| SyncError::InvalidState(format!("Failed to create header sync manager: {}", e)), + )?, + filter_sync: FilterSyncManager::new(config, received_filter_heights), + masternode_sync: MasternodeSyncManager::new(config), + config: config.clone(), + phase_history: Vec::new(), + sync_start_time: None, + phase_timeout: Duration::from_secs(60), // 1 minute default timeout per phase + max_phase_retries: 3, + current_phase_retries: 0, + wallet, + stats, + _phantom_s: std::marker::PhantomData, + _phantom_n: std::marker::PhantomData, + }) + } + + /// Load headers from storage into the sync managers + pub async fn load_headers_from_storage(&mut self, storage: &S) -> SyncResult { + // 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) + } + + /// Get the earliest wallet birth height hint for the configured network, if available. + pub async fn wallet_birth_height_hint(&self) -> Option { + // Map the dashcore network to wallet network, returning None for unknown variants + let wallet_network = match self.config.network { + dashcore::Network::Dash => WalletNetwork::Dash, + dashcore::Network::Testnet => WalletNetwork::Testnet, + dashcore::Network::Devnet => WalletNetwork::Devnet, + dashcore::Network::Regtest => WalletNetwork::Regtest, + _ => return None, // Unknown network variant - return None instead of defaulting + }; + + // Only acquire the wallet lock if we have a valid network mapping + let wallet_guard = self.wallet.read().await; + let result = wallet_guard.earliest_required_height(wallet_network).await; + drop(wallet_guard); + result + } + + /// Get the configured start height hint, if any. + pub fn config_start_height(&self) -> Option { + self.config.start_from_height + } + + /// Start the sequential sync process + pub async fn start_sync(&mut self, network: &mut N, storage: &mut S) -> SyncResult { + if self.current_phase.is_syncing() { + return Err(SyncError::SyncInProgress); + } + + tracing::info!("🚀 Starting sequential sync process"); + tracing::info!("📊 Current phase: {}", self.current_phase.name()); + self.sync_start_time = Some(Instant::now()); + + // Transition from Idle to first phase + self.transition_to_next_phase(storage, network, "Starting sync").await?; + + // The actual header request will be sent when we have peers + match &self.current_phase { + SyncPhase::DownloadingHeaders { + .. + } => { + // Just prepare the sync, don't execute yet + tracing::info!( + "📋 Sequential sync prepared, waiting for peers to send initial requests" + ); + // Prepare the header sync without sending requests + let base_hash = self.header_sync.prepare_sync(storage).await?; + tracing::debug!("Starting from base hash: {:?}", base_hash); + } + _ => { + // If we're not in headers phase, something is wrong + return Err(SyncError::InvalidState( + "Expected to be in DownloadingHeaders phase".to_string(), + )); + } + } + + Ok(true) + } + + /// Send initial sync requests (called after peers are connected) + pub async fn send_initial_requests( + &mut self, + network: &mut N, + storage: &mut S, + ) -> SyncResult<()> { + match &self.current_phase { + SyncPhase::DownloadingHeaders { + .. + } => { + tracing::info!("📡 Sending initial header requests for sequential sync"); + // If header sync is already prepared, just send the request + if self.header_sync.is_syncing() { + // Get current tip from storage to determine base hash + 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?; + } else { + // Otherwise start sync normally + self.header_sync.start_sync(network, storage).await?; + } + } + _ => { + tracing::warn!("send_initial_requests called but not in DownloadingHeaders phase"); + } + } + Ok(()) + } + + /// Reset any pending requests after restart. + pub fn reset_pending_requests(&mut self) { + // Reset all sync manager states + let _ = self.header_sync.reset_pending_requests(); + self.filter_sync.reset_pending_requests(); + // Masternode sync doesn't have pending requests to reset + + // Reset phase tracking + self.current_phase_retries = 0; + + tracing::debug!("Reset sequential sync manager pending requests"); + } + + /// Helper method to get base hash from storage + pub(super) async fn get_base_hash_from_storage( + &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 base_hash = match current_tip_height { + None => None, + Some(height) => { + let tip_header = storage + .get_header(height) + .await + .map_err(|e| SyncError::Storage(format!("Failed to get tip header: {}", e)))?; + tip_header.map(|h| h.block_hash()) + } + }; + + 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() diff --git a/dash-spv/src/sync/embedded_data.rs b/dash-spv/src/sync/masternodes/embedded_data.rs similarity index 92% rename from dash-spv/src/sync/embedded_data.rs rename to dash-spv/src/sync/masternodes/embedded_data.rs index 2b1fda322..ffd5db89c 100644 --- a/dash-spv/src/sync/embedded_data.rs +++ b/dash-spv/src/sync/masternodes/embedded_data.rs @@ -7,11 +7,11 @@ use dashcore::{consensus::deserialize, network::message_sml::MnListDiff, Network // Embed the mainnet MNListDiff from height 0 to 2227096 const MAINNET_MNLIST_DIFF_0_2227096: &[u8] = - include_bytes!("../../../dash/artifacts/mn_list_diff_0_2227096.bin"); + include_bytes!("../../../../dash/artifacts/mn_list_diff_0_2227096.bin"); // Embed the testnet MNListDiff from height 0 to 1296600 const TESTNET_MNLIST_DIFF_0_1296600: &[u8] = - include_bytes!("../../../dash/artifacts/mn_list_diff_testnet_0_1296600.bin"); + include_bytes!("../../../../dash/artifacts/mn_list_diff_testnet_0_1296600.bin"); /// Information about an embedded MNListDiff pub struct EmbeddedDiff { diff --git a/dash-spv/src/sync/masternodes.rs b/dash-spv/src/sync/masternodes/manager.rs similarity index 100% rename from dash-spv/src/sync/masternodes.rs rename to dash-spv/src/sync/masternodes/manager.rs diff --git a/dash-spv/src/sync/masternodes/mod.rs b/dash-spv/src/sync/masternodes/mod.rs new file mode 100644 index 000000000..faef8d903 --- /dev/null +++ b/dash-spv/src/sync/masternodes/mod.rs @@ -0,0 +1,6 @@ +//! Masternode synchronization and embedded data. + +pub mod embedded_data; +mod manager; + +pub use manager::MasternodeSyncManager; diff --git a/dash-spv/src/sync/sequential/message_handlers.rs b/dash-spv/src/sync/message_handlers.rs similarity index 100% rename from dash-spv/src/sync/sequential/message_handlers.rs rename to dash-spv/src/sync/message_handlers.rs diff --git a/dash-spv/src/sync/mod.rs b/dash-spv/src/sync/mod.rs index 2ffb7aa2d..383405205 100644 --- a/dash-spv/src/sync/mod.rs +++ b/dash-spv/src/sync/mod.rs @@ -1,14 +1,51 @@ //! Synchronization management for the Dash SPV client. //! -//! This module provides sequential sync strategy: -//! Headers first, then filter headers, then filters on-demand +//! This module implements a strict sequential sync pipeline where each phase +//! must complete 100% before the next phase begins. +//! +//! # Sequential Sync Benefits: +//! - Simpler state management (one active phase) +//! - Easier error recovery (restart current phase) +//! - Matches dependencies (need headers before filters) +//! - More reliable than concurrent sync +//! +//! # CRITICAL: Lock Ordering +//! To prevent deadlocks, acquire locks in this order: +//! 1. state (via read/write methods) +//! 2. storage (via async methods) +//! 3. network (via send_message) +//! +//! # Module Structure +//! - `manager` - Core SyncManager struct +//! - `phase_execution` - Phase execution, transitions, and timeout handling +//! - `message_handlers` - Handlers for sync phase messages +//! - `post_sync` - Handlers for post-sync messages (after initial sync complete) +//! - `phases` - SyncPhase enum and phase-related types +//! - `transitions` - Phase transition management +//! - `filters` - BIP157 Compact Block Filter synchronization +//! - `headers` - Header synchronization with fork detection +//! - `headers2` - Headers2 compressed header state management +//! - `masternodes` - Masternode synchronization -pub mod embedded_data; +// Core sync modules pub mod filters; pub mod headers; -pub mod headers2_state; +pub mod headers2; pub mod masternodes; -pub mod sequential; + +// Sequential sync pipeline modules +pub mod manager; +pub mod message_handlers; +pub mod phase_execution; +pub mod phases; +pub mod post_sync; +pub mod transitions; + +// Re-exports pub use filters::FilterSyncManager; pub use headers::{HeaderSyncManager, ReorgConfig}; +pub use headers2::{Headers2StateManager, Headers2Stats, ProcessError}; +pub use manager::SyncManager; pub use masternodes::MasternodeSyncManager; +pub use phases::{PhaseTransition, SyncPhase}; +pub use transitions::TransitionManager; diff --git a/dash-spv/src/sync/sequential/phase_execution.rs b/dash-spv/src/sync/phase_execution.rs similarity index 100% rename from dash-spv/src/sync/sequential/phase_execution.rs rename to dash-spv/src/sync/phase_execution.rs diff --git a/dash-spv/src/sync/sequential/phases.rs b/dash-spv/src/sync/phases.rs similarity index 100% rename from dash-spv/src/sync/sequential/phases.rs rename to dash-spv/src/sync/phases.rs diff --git a/dash-spv/src/sync/sequential/post_sync.rs b/dash-spv/src/sync/post_sync.rs similarity index 100% rename from dash-spv/src/sync/sequential/post_sync.rs rename to dash-spv/src/sync/post_sync.rs diff --git a/dash-spv/src/sync/sequential/lifecycle.rs b/dash-spv/src/sync/sequential/lifecycle.rs deleted file mode 100644 index ea43b3566..000000000 --- a/dash-spv/src/sync/sequential/lifecycle.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! Lifecycle management for SyncManager (initialization, startup, shutdown). - -use std::time::{Duration, Instant}; - -use dashcore::BlockHash; - -use crate::client::ClientConfig; -use crate::error::{SyncError, SyncResult}; -use crate::network::NetworkManager; -use crate::storage::StorageManager; -use crate::sync::{FilterSyncManager, HeaderSyncManager, MasternodeSyncManager, ReorgConfig}; -use crate::types::{SharedFilterHeights, SpvStats}; -use key_wallet_manager::{wallet_interface::WalletInterface, Network as WalletNetwork}; -use std::sync::Arc; -use tokio::sync::RwLock; - -use super::manager::SyncManager; -use super::phases::SyncPhase; -use super::transitions::TransitionManager; - -impl< - S: StorageManager + Send + Sync + 'static, - N: NetworkManager + Send + Sync + 'static, - W: WalletInterface, - > SyncManager -{ - /// Create a new sequential sync manager - pub fn new( - config: &ClientConfig, - received_filter_heights: SharedFilterHeights, - wallet: Arc>, - chain_state: Arc>, - stats: Arc>, - ) -> SyncResult { - // Create reorg config with sensible defaults - let reorg_config = ReorgConfig::default(); - - Ok(Self { - current_phase: SyncPhase::Idle, - transition_manager: TransitionManager::new(config), - header_sync: HeaderSyncManager::new(config, reorg_config, chain_state).map_err( - |e| SyncError::InvalidState(format!("Failed to create header sync manager: {}", e)), - )?, - filter_sync: FilterSyncManager::new(config, received_filter_heights), - masternode_sync: MasternodeSyncManager::new(config), - config: config.clone(), - phase_history: Vec::new(), - sync_start_time: None, - phase_timeout: Duration::from_secs(60), // 1 minute default timeout per phase - max_phase_retries: 3, - current_phase_retries: 0, - wallet, - stats, - _phantom_s: std::marker::PhantomData, - _phantom_n: std::marker::PhantomData, - }) - } - - /// Load headers from storage into the sync managers - pub async fn load_headers_from_storage(&mut self, storage: &S) -> SyncResult { - // 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) - } - - /// Get the earliest wallet birth height hint for the configured network, if available. - pub async fn wallet_birth_height_hint(&self) -> Option { - // Map the dashcore network to wallet network, returning None for unknown variants - let wallet_network = match self.config.network { - dashcore::Network::Dash => WalletNetwork::Dash, - dashcore::Network::Testnet => WalletNetwork::Testnet, - dashcore::Network::Devnet => WalletNetwork::Devnet, - dashcore::Network::Regtest => WalletNetwork::Regtest, - _ => return None, // Unknown network variant - return None instead of defaulting - }; - - // Only acquire the wallet lock if we have a valid network mapping - let wallet_guard = self.wallet.read().await; - let result = wallet_guard.earliest_required_height(wallet_network).await; - drop(wallet_guard); - result - } - - /// Get the configured start height hint, if any. - pub fn config_start_height(&self) -> Option { - self.config.start_from_height - } - - /// Start the sequential sync process - pub async fn start_sync(&mut self, network: &mut N, storage: &mut S) -> SyncResult { - if self.current_phase.is_syncing() { - return Err(SyncError::SyncInProgress); - } - - tracing::info!("🚀 Starting sequential sync process"); - tracing::info!("📊 Current phase: {}", self.current_phase.name()); - self.sync_start_time = Some(Instant::now()); - - // Transition from Idle to first phase - self.transition_to_next_phase(storage, network, "Starting sync").await?; - - // The actual header request will be sent when we have peers - match &self.current_phase { - SyncPhase::DownloadingHeaders { - .. - } => { - // Just prepare the sync, don't execute yet - tracing::info!( - "📋 Sequential sync prepared, waiting for peers to send initial requests" - ); - // Prepare the header sync without sending requests - let base_hash = self.header_sync.prepare_sync(storage).await?; - tracing::debug!("Starting from base hash: {:?}", base_hash); - } - _ => { - // If we're not in headers phase, something is wrong - return Err(SyncError::InvalidState( - "Expected to be in DownloadingHeaders phase".to_string(), - )); - } - } - - Ok(true) - } - - /// Send initial sync requests (called after peers are connected) - pub async fn send_initial_requests( - &mut self, - network: &mut N, - storage: &mut S, - ) -> SyncResult<()> { - match &self.current_phase { - SyncPhase::DownloadingHeaders { - .. - } => { - tracing::info!("📡 Sending initial header requests for sequential sync"); - // If header sync is already prepared, just send the request - if self.header_sync.is_syncing() { - // Get current tip from storage to determine base hash - 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?; - } else { - // Otherwise start sync normally - self.header_sync.start_sync(network, storage).await?; - } - } - _ => { - tracing::warn!("send_initial_requests called but not in DownloadingHeaders phase"); - } - } - Ok(()) - } - - /// Reset any pending requests after restart. - pub fn reset_pending_requests(&mut self) { - // Reset all sync manager states - let _ = self.header_sync.reset_pending_requests(); - self.filter_sync.reset_pending_requests(); - // Masternode sync doesn't have pending requests to reset - - // Reset phase tracking - self.current_phase_retries = 0; - - tracing::debug!("Reset sequential sync manager pending requests"); - } - - /// Helper method to get base hash from storage - pub(super) async fn get_base_hash_from_storage( - &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 base_hash = match current_tip_height { - None => None, - Some(height) => { - let tip_header = storage - .get_header(height) - .await - .map_err(|e| SyncError::Storage(format!("Failed to get tip header: {}", e)))?; - tip_header.map(|h| h.block_hash()) - } - }; - - Ok(base_hash) - } -} diff --git a/dash-spv/src/sync/sequential/mod.rs b/dash-spv/src/sync/sequential/mod.rs deleted file mode 100644 index 9b96fd6b1..000000000 --- a/dash-spv/src/sync/sequential/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Sequential synchronization manager for dash-spv -//! -//! This module implements a strict sequential sync pipeline where each phase -//! must complete 100% before the next phase begins. -//! -//! # Sequential Sync Benefits: -//! - Simpler state management (one active phase) -//! - Easier error recovery (restart current phase) -//! - Matches dependencies (need headers before filters) -//! - More reliable than concurrent sync -//! -//! # Tradeoff: -//! Slower total sync time, but significantly simpler code. -//! -//! # CRITICAL: Lock Ordering -//! To prevent deadlocks, acquire locks in this order: -//! 1. state (via read/write methods) -//! 2. storage (via async methods) -//! 3. network (via send_message) -//! -//! # Module Structure -//! This module has been refactored into focused sub-modules: -//! - `manager` - Core struct definition and simple accessors -//! - `lifecycle` - Initialization, startup, and shutdown -//! - `phase_execution` - Phase execution, transitions, and timeout handling -//! - `message_handlers` - Handlers for sync phase messages -//! - `post_sync` - Handlers for post-sync messages (after initial sync complete) -//! - `phases` - SyncPhase enum and phase-related types -//! - `progress` - Progress tracking utilities -//! - `recovery` - Recovery and error handling logic -//! - `transitions` - Phase transition management - -// Sub-modules (focused implementations) -pub mod lifecycle; -pub mod manager; -pub mod message_handlers; -pub mod phase_execution; -pub mod post_sync; - -// Existing sub-modules -pub mod phases; -pub mod transitions; - -// Re-exports -pub use manager::SyncManager; -pub use phases::{PhaseTransition, SyncPhase}; -pub use transitions::TransitionManager; diff --git a/dash-spv/src/sync/sequential/transitions.rs b/dash-spv/src/sync/transitions.rs similarity index 100% rename from dash-spv/src/sync/sequential/transitions.rs rename to dash-spv/src/sync/transitions.rs diff --git a/dash-spv/tests/chainlock_validation_test.rs b/dash-spv/tests/chainlock_validation_test.rs index cb5c3d223..2d63b9098 100644 --- a/dash-spv/tests/chainlock_validation_test.rs +++ b/dash-spv/tests/chainlock_validation_test.rs @@ -355,7 +355,7 @@ async fn test_client_chainlock_update_flow() { // Simulate masternode sync by manually setting sequential sync state // In real usage, this would happen automatically during sync // Note: sync_manager is private, can't access directly - // client.sync_manager.set_phase(dash_spv::sync::sequential::phases::SyncPhase::FullySynced { + // client.sync_manager.set_phase(dash_spv::sync::SyncPhase::FullySynced { // sync_completed_at: std::time::Instant::now(), // total_sync_time: Duration::from_secs(10), // headers_synced: 1000,