Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dash-spv/src/client/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion dash-spv/src/client/filter_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion dash-spv/src/client/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion dash-spv/src/client/message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion dash-spv/src/client/message_handler_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion dash-spv/src/client/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -611,10 +611,8 @@ impl<S: StorageManager + Send + Sync + 'static, N: NetworkManager + Send + Sync

// If we failed due to missing previous header, and we're at genesis,
// this might be a protocol issue where peer expects us to have genesis in compression state
if matches!(
e,
crate::sync::headers2_state::ProcessError::DecompressionError(0, _)
) && self.chain_state.read().await.tip_height() == 0
if matches!(e, crate::sync::headers2::ProcessError::DecompressionError(0, _))
&& self.chain_state.read().await.tip_height() == 0
{
tracing::warn!(
"Headers2 decompression failed at genesis. Peer may be sending compressed headers that reference genesis. Consider falling back to regular headers."
Expand Down
5 changes: 5 additions & 0 deletions dash-spv/src/sync/headers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Header synchronization with fork detection and reorganization handling.

mod manager;

pub use manager::{HeaderSyncManager, ReorgConfig};
5 changes: 5 additions & 0 deletions dash-spv/src/sync/headers2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Headers2 state management for compressed header synchronization.

mod manager;

pub use manager::{Headers2StateManager, Headers2Stats, ProcessError};
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
//! Core SyncManager struct and simple accessor methods.

use std::time::{Duration, Instant};

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};
use crate::types::SyncProgress;
use key_wallet_manager::wallet_interface::WalletInterface;

use super::phases::{PhaseTransition, SyncPhase};
use super::transitions::TransitionManager;
use crate::sync::{FilterSyncManager, HeaderSyncManager, MasternodeSyncManager, ReorgConfig};
use crate::types::{SharedFilterHeights, SyncProgress};
use crate::{SpvStats, SyncError};
use dashcore::BlockHash;
use key_wallet_manager::{wallet_interface::WalletInterface, Network as WalletNetwork};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;

/// Number of blocks back from a ChainLock's block height where we need the masternode list
/// for validation. ChainLock signatures are created by the masternode quorum that existed
Expand Down Expand Up @@ -103,6 +105,185 @@ impl<
W: WalletInterface,
> SyncManager<S, N, W>
{
/// Create a new sequential sync manager
pub fn new(
config: &ClientConfig,
received_filter_heights: SharedFilterHeights,
wallet: Arc<RwLock<W>>,
chain_state: Arc<RwLock<crate::types::ChainState>>,
stats: Arc<RwLock<SpvStats>>,
) -> SyncResult<Self> {
// 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<u32> {
// 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<u32> {
// 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<u32> {
self.config.start_from_height
}

/// Start the sequential sync process
pub async fn start_sync(&mut self, network: &mut N, storage: &mut S) -> SyncResult<bool> {
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<Option<BlockHash>> {
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions dash-spv/src/sync/masternodes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Masternode synchronization and embedded data.

pub mod embedded_data;
mod manager;

pub use manager::MasternodeSyncManager;
47 changes: 42 additions & 5 deletions dash-spv/src/sync/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
File renamed without changes.
Loading