From e8b54f4ecd868264665bcb3e434a1f1eaf4c80c7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 26 Sep 2025 17:34:38 +0700 Subject: [PATCH 01/10] fix: sync progress --- dash-spv-ffi/dash_spv_ffi.h | 25 +++++----- dash-spv-ffi/include/dash_spv_ffi.h | 25 +++++----- dash-spv-ffi/src/bin/ffi_cli.rs | 4 +- dash-spv-ffi/src/types.rs | 8 ++-- dash-spv/src/client/mod.rs | 16 ++++++- dash-spv/src/types.rs | 11 +++-- .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 48 ++++++++++++------- .../SwiftDashCoreSDK/Core/SPVClient.swift | 20 ++++---- .../Models/SyncProgress.swift | 36 +++++++++++++- 9 files changed, 126 insertions(+), 67 deletions(-) diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 644ce1094..190f458cc 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -70,19 +70,6 @@ typedef struct FFIString { uintptr_t length; } FFIString; -typedef struct FFIDetailedSyncProgress { - uint32_t current_height; - uint32_t total_height; - double percentage; - double headers_per_second; - int64_t estimated_seconds_remaining; - enum FFISyncStage stage; - struct FFIString stage_message; - uint32_t connected_peers; - uint64_t total_headers; - int64_t sync_start_timestamp; -} FFIDetailedSyncProgress; - typedef struct FFISyncProgress { uint32_t header_height; uint32_t filter_header_height; @@ -96,6 +83,18 @@ typedef struct FFISyncProgress { uint32_t last_synced_filter_height; } FFISyncProgress; +typedef struct FFIDetailedSyncProgress { + uint32_t total_height; + double percentage; + double headers_per_second; + int64_t estimated_seconds_remaining; + enum FFISyncStage stage; + struct FFIString stage_message; + struct FFISyncProgress overview; + uint64_t total_headers; + int64_t sync_start_timestamp; +} FFIDetailedSyncProgress; + typedef struct FFISpvStats { uint32_t connected_peers; uint32_t total_peers; diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 644ce1094..190f458cc 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -70,19 +70,6 @@ typedef struct FFIString { uintptr_t length; } FFIString; -typedef struct FFIDetailedSyncProgress { - uint32_t current_height; - uint32_t total_height; - double percentage; - double headers_per_second; - int64_t estimated_seconds_remaining; - enum FFISyncStage stage; - struct FFIString stage_message; - uint32_t connected_peers; - uint64_t total_headers; - int64_t sync_start_timestamp; -} FFIDetailedSyncProgress; - typedef struct FFISyncProgress { uint32_t header_height; uint32_t filter_header_height; @@ -96,6 +83,18 @@ typedef struct FFISyncProgress { uint32_t last_synced_filter_height; } FFISyncProgress; +typedef struct FFIDetailedSyncProgress { + uint32_t total_height; + double percentage; + double headers_per_second; + int64_t estimated_seconds_remaining; + enum FFISyncStage stage; + struct FFIString stage_message; + struct FFISyncProgress overview; + uint64_t total_headers; + int64_t sync_start_timestamp; +} FFIDetailedSyncProgress; + typedef struct FFISpvStats { uint32_t connected_peers; uint32_t total_peers; diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index 21ccdb71d..999946c22 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -35,10 +35,10 @@ extern "C" fn on_detailed_progress(progress: *const FFIDetailedSyncProgress, _ud let p = &*progress; println!( "height {}/{} {:.2}% peers {} hps {:.1}", - p.current_height, + p.overview.header_height, p.total_height, p.percentage * 100.0, - p.connected_peers, + p.overview.peer_count, p.headers_per_second ); } diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index d419b9a59..b18f50088 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -97,14 +97,13 @@ impl From for FFISyncStage { #[repr(C)] pub struct FFIDetailedSyncProgress { - pub current_height: u32, pub total_height: u32, pub percentage: f64, pub headers_per_second: f64, pub estimated_seconds_remaining: i64, // -1 if unknown pub stage: FFISyncStage, pub stage_message: FFIString, - pub connected_peers: u32, + pub overview: FFISyncProgress, pub total_headers: u64, pub sync_start_timestamp: i64, } @@ -130,8 +129,9 @@ impl From for FFIDetailedSyncProgress { SyncStage::Failed(err) => err.clone(), }; + let overview = FFISyncProgress::from(progress.sync_progress.clone()); + FFIDetailedSyncProgress { - current_height: progress.current_height, total_height: progress.peer_best_height, percentage: progress.percentage, headers_per_second: progress.headers_per_second, @@ -141,7 +141,7 @@ impl From for FFIDetailedSyncProgress { .unwrap_or(-1), stage: progress.sync_stage.into(), stage_message: FFIString::new(&stage_message), - connected_peers: progress.connected_peers as u32, + overview, total_headers: progress.total_headers_processed, sync_start_timestamp: progress .sync_start_time diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 0791b96a8..66be86bb7 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -918,8 +918,21 @@ impl< crate::types::SyncStage::Complete }; + let status_display = self.create_status_display().await; + let mut sync_progress = match status_display.sync_progress().await { + Ok(p) => p, + Err(e) => { + tracing::warn!("Failed to compute sync progress snapshot: {}", e); + SyncProgress::default() + } + }; + + // Update peer count with the latest network information. + sync_progress.peer_count = self.network.peer_count() as u32; + sync_progress.header_height = current_height; + let progress = DetailedSyncProgress { - current_height, + sync_progress, peer_best_height: peer_best, percentage: if peer_best > 0 { (current_height as f64 / peer_best as f64 * 100.0).min(100.0) @@ -937,7 +950,6 @@ impl< None }, sync_stage, - connected_peers: self.network.peer_count(), total_headers_processed: current_height as u64, total_bytes_downloaded, sync_start_time, diff --git a/dash-spv/src/types.rs b/dash-spv/src/types.rs index d621cda94..ebb7e96ee 100644 --- a/dash-spv/src/types.rs +++ b/dash-spv/src/types.rs @@ -85,8 +85,8 @@ impl Default for SyncProgress { /// Detailed sync progress with performance metrics. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DetailedSyncProgress { - /// Current state - pub current_height: u32, + /// Snapshot of the core sync metrics for quick consumption. + pub sync_progress: SyncProgress, pub peer_best_height: u32, pub percentage: f64, @@ -97,7 +97,6 @@ pub struct DetailedSyncProgress { /// Detailed status pub sync_stage: SyncStage, - pub connected_peers: usize, pub total_headers_processed: u64, pub total_bytes_downloaded: u64, @@ -130,7 +129,8 @@ impl DetailedSyncProgress { if self.peer_best_height == 0 { return 0.0; } - ((self.current_height as f64 / self.peer_best_height as f64) * 100.0).min(100.0) + let current_height = self.sync_progress.header_height; + ((current_height as f64 / self.peer_best_height as f64) * 100.0).min(100.0) } pub fn calculate_eta(&self) -> Option { @@ -138,7 +138,8 @@ impl DetailedSyncProgress { return None; } - let remaining = self.peer_best_height.saturating_sub(self.current_height); + let current_height = self.sync_progress.header_height; + let remaining = self.peer_best_height.saturating_sub(current_height); if remaining == 0 { return Some(Duration::from_secs(0)); } diff --git a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h index 5ae81d3c3..190f458cc 100644 --- a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h +++ b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h @@ -61,6 +61,7 @@ typedef struct FFIArray { typedef struct FFIClientConfig { void *inner; + uint32_t worker_threads; } FFIClientConfig; @@ -69,19 +70,6 @@ typedef struct FFIString { uintptr_t length; } FFIString; -typedef struct FFIDetailedSyncProgress { - uint32_t current_height; - uint32_t total_height; - double percentage; - double headers_per_second; - int64_t estimated_seconds_remaining; - enum FFISyncStage stage; - struct FFIString stage_message; - uint32_t connected_peers; - uint64_t total_headers; - int64_t sync_start_timestamp; -} FFIDetailedSyncProgress; - typedef struct FFISyncProgress { uint32_t header_height; uint32_t filter_header_height; @@ -95,6 +83,18 @@ typedef struct FFISyncProgress { uint32_t last_synced_filter_height; } FFISyncProgress; +typedef struct FFIDetailedSyncProgress { + uint32_t total_height; + double percentage; + double headers_per_second; + int64_t estimated_seconds_remaining; + enum FFISyncStage stage; + struct FFIString stage_message; + struct FFISyncProgress overview; + uint64_t total_headers; + int64_t sync_start_timestamp; +} FFIDetailedSyncProgress; + typedef struct FFISpvStats { uint32_t connected_peers; uint32_t total_peers; @@ -281,6 +281,14 @@ struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network, */ struct FFIDashSpvClient *dash_spv_ffi_client_new(const struct FFIClientConfig *config) ; +/** + * Drain pending events and invoke configured callbacks (non-blocking). + * + * # Safety + * - `client` must be a valid, non-null pointer. + */ + int32_t dash_spv_ffi_client_drain_events(struct FFIDashSpvClient *client) ; + /** * Update the running client's configuration. * @@ -392,10 +400,8 @@ int32_t dash_spv_ffi_client_sync_to_tip_with_progress(struct FFIDashSpvClient *c /** * Cancels the sync operation. * - * **Note**: This function currently only stops the SPV client and clears sync callbacks, - * but does not fully abort the ongoing sync process. The sync operation may continue - * running in the background until it completes naturally. Full sync cancellation with - * proper task abortion is not yet implemented. + * This stops the SPV client, clears callbacks, and joins active threads so the sync + * operation halts immediately. * * # Safety * The client pointer must be valid and non-null. @@ -704,6 +710,14 @@ int32_t dash_spv_ffi_config_set_masternode_sync_enabled(struct FFIClientConfig * void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) ; +/** + * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) + * + * # Safety + * - `config` must be a valid pointer to an FFIClientConfig + */ + int32_t dash_spv_ffi_config_set_worker_threads(struct FFIClientConfig *config, uint32_t threads) ; + /** * Enables or disables mempool tracking * diff --git a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift index 187db798f..12175f57b 100644 --- a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift +++ b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift @@ -8,17 +8,19 @@ import Network /// Detailed sync progress information with real-time statistics public struct DetailedSyncProgress: Sendable, Equatable { - public let currentHeight: UInt32 + public let overview: SyncProgress public let totalHeight: UInt32 public let percentage: Double public let headersPerSecond: Double public let estimatedSecondsRemaining: Int64 public let stage: SyncStage public let stageMessage: String - public let connectedPeers: UInt32 public let totalHeadersProcessed: UInt64 public let syncStartTimestamp: Date + public var currentHeight: UInt32 { overview.currentHeight } + public var connectedPeers: UInt32 { overview.peerCount } + /// Calculated properties public var blocksRemaining: UInt32 { guard totalHeight > currentHeight else { return 0 } @@ -65,39 +67,36 @@ public struct DetailedSyncProgress: Sendable, Equatable { /// Public initializer for creating DetailedSyncProgress public init( - currentHeight: UInt32, + overview: SyncProgress, totalHeight: UInt32, percentage: Double, headersPerSecond: Double, estimatedSecondsRemaining: Int64, stage: SyncStage, stageMessage: String, - connectedPeers: UInt32, totalHeadersProcessed: UInt64, syncStartTimestamp: Date ) { - self.currentHeight = currentHeight + self.overview = overview self.totalHeight = totalHeight self.percentage = percentage self.headersPerSecond = headersPerSecond self.estimatedSecondsRemaining = estimatedSecondsRemaining self.stage = stage self.stageMessage = stageMessage - self.connectedPeers = connectedPeers self.totalHeadersProcessed = totalHeadersProcessed self.syncStartTimestamp = syncStartTimestamp } /// Initialize from FFI type internal init(ffiProgress: FFIDetailedSyncProgress) { - self.currentHeight = ffiProgress.current_height + self.overview = SyncProgress(ffiProgress: ffiProgress.overview) self.totalHeight = ffiProgress.total_height self.percentage = ffiProgress.percentage self.headersPerSecond = ffiProgress.headers_per_second self.estimatedSecondsRemaining = ffiProgress.estimated_seconds_remaining self.stage = SyncStage(ffiStage: ffiProgress.stage) self.stageMessage = String(cString: ffiProgress.stage_message.ptr) - self.connectedPeers = ffiProgress.connected_peers self.totalHeadersProcessed = ffiProgress.total_headers self.syncStartTimestamp = Date(timeIntervalSince1970: TimeInterval(ffiProgress.sync_start_timestamp)) } @@ -308,6 +307,9 @@ extension DetailedSyncProgress { "Time Remaining": formattedTimeRemaining, "Connected Peers": "\(connectedPeers)", "Headers Processed": "\(totalHeadersProcessed)", + "Filter Header Height": "\(overview.filterHeaderHeight)", + "Filters Downloaded": "\(overview.filtersDownloaded)", + "Peer Count": "\(overview.peerCount)", "Duration": formattedSyncDuration ] } @@ -1240,4 +1242,4 @@ extension SPVClient { public func syncProgressStream() -> SyncProgressStream { return SyncProgressStream(client: self) } -} \ No newline at end of file +} diff --git a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift index ba70221f9..ef2630996 100644 --- a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift +++ b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift @@ -11,6 +11,14 @@ public struct SyncProgress: Sendable, Equatable { public let estimatedTimeRemaining: TimeInterval? public let message: String? public let filterSyncAvailable: Bool + public let filterHeaderHeight: UInt32 + public let masternodeHeight: UInt32 + public let peerCount: UInt32 + public let headersSynced: Bool + public let filterHeadersSynced: Bool + public let masternodesSynced: Bool + public let filtersDownloaded: UInt32 + public let lastSyncedFilterHeight: UInt32 public init( currentHeight: UInt32, @@ -19,7 +27,15 @@ public struct SyncProgress: Sendable, Equatable { status: SyncStatus, estimatedTimeRemaining: TimeInterval? = nil, message: String? = nil, - filterSyncAvailable: Bool = false + filterSyncAvailable: Bool = false, + filterHeaderHeight: UInt32 = 0, + masternodeHeight: UInt32 = 0, + peerCount: UInt32 = 0, + headersSynced: Bool = false, + filterHeadersSynced: Bool = false, + masternodesSynced: Bool = false, + filtersDownloaded: UInt32 = 0, + lastSyncedFilterHeight: UInt32 = 0 ) { self.currentHeight = currentHeight self.totalHeight = totalHeight @@ -28,6 +44,14 @@ public struct SyncProgress: Sendable, Equatable { self.estimatedTimeRemaining = estimatedTimeRemaining self.message = message self.filterSyncAvailable = filterSyncAvailable + self.filterHeaderHeight = filterHeaderHeight + self.masternodeHeight = masternodeHeight + self.peerCount = peerCount + self.headersSynced = headersSynced + self.filterHeadersSynced = filterHeadersSynced + self.masternodesSynced = masternodesSynced + self.filtersDownloaded = filtersDownloaded + self.lastSyncedFilterHeight = lastSyncedFilterHeight } internal init(ffiProgress: FFISyncProgress) { @@ -38,6 +62,14 @@ public struct SyncProgress: Sendable, Equatable { self.estimatedTimeRemaining = nil self.message = nil self.filterSyncAvailable = ffiProgress.filter_sync_available + self.filterHeaderHeight = ffiProgress.filter_header_height + self.masternodeHeight = ffiProgress.masternode_height + self.peerCount = ffiProgress.peer_count + self.headersSynced = ffiProgress.headers_synced + self.filterHeadersSynced = ffiProgress.filter_headers_synced + self.masternodesSynced = ffiProgress.masternodes_synced + self.filtersDownloaded = ffiProgress.filters_downloaded + self.lastSyncedFilterHeight = ffiProgress.last_synced_filter_height } public var blocksRemaining: UInt32 { @@ -120,4 +152,4 @@ public enum SyncStatus: String, Codable, Sendable { return true } } -} \ No newline at end of file +} From 29735615b0dc08a1d02a656412c4da7866f0935c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 26 Sep 2025 19:49:11 +0700 Subject: [PATCH 02/10] more work --- dash-spv-ffi/dash_spv_ffi.h | 6 -- dash-spv-ffi/include/dash_spv_ffi.h | 6 -- dash-spv-ffi/src/bin/ffi_cli.rs | 7 +-- dash-spv-ffi/src/callbacks.rs | 27 +------- dash-spv-ffi/src/client.rs | 19 +----- .../tests/integration/test_full_workflow.rs | 1 - dash-spv-ffi/tests/test_event_callbacks.rs | 7 --- .../tests/unit/test_async_operations.rs | 1 - dash-spv/src/client/mod.rs | 62 +++++++++++++++---- dash-spv/src/types.rs | 17 ----- .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 6 -- 11 files changed, 56 insertions(+), 103 deletions(-) diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 190f458cc..2afebb22b 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -149,11 +149,6 @@ typedef void (*WalletTransactionCallback)(const char *wallet_id, bool is_ours, void *user_data); -typedef void (*FilterHeadersProgressCallback)(uint32_t filter_height, - uint32_t header_height, - double percentage, - void *user_data); - typedef struct FFIEventCallbacks { BlockCallback on_block; TransactionCallback on_transaction; @@ -163,7 +158,6 @@ typedef struct FFIEventCallbacks { MempoolRemovedCallback on_mempool_transaction_removed; CompactFilterMatchedCallback on_compact_filter_matched; WalletTransactionCallback on_wallet_transaction; - FilterHeadersProgressCallback on_filter_headers_progress; void *user_data; } FFIEventCallbacks; diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 190f458cc..2afebb22b 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -149,11 +149,6 @@ typedef void (*WalletTransactionCallback)(const char *wallet_id, bool is_ours, void *user_data); -typedef void (*FilterHeadersProgressCallback)(uint32_t filter_height, - uint32_t header_height, - double percentage, - void *user_data); - typedef struct FFIEventCallbacks { BlockCallback on_block; TransactionCallback on_transaction; @@ -163,7 +158,6 @@ typedef struct FFIEventCallbacks { MempoolRemovedCallback on_mempool_transaction_removed; CompactFilterMatchedCallback on_compact_filter_matched; WalletTransactionCallback on_wallet_transaction; - FilterHeadersProgressCallback on_filter_headers_progress; void *user_data; } FFIEventCallbacks; diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index 999946c22..5b11ef9b9 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -23,10 +23,6 @@ fn ffi_string_to_rust(s: *const c_char) -> String { unsafe { CStr::from_ptr(s) }.to_str().unwrap_or_default().to_owned() } -extern "C" fn on_filter_headers_progress(filter: u32, headers: u32, pct: f64, _ud: *mut c_void) { - println!("filters: {} headers: {} progress: {:.2}%", filter, headers, pct * 100.0); -} - extern "C" fn on_detailed_progress(progress: *const FFIDetailedSyncProgress, _ud: *mut c_void) { if progress.is_null() { return; @@ -171,7 +167,7 @@ fn main() { std::process::exit(1); } - // Set minimal event callbacks (progress via filter headers) + // Set minimal event callbacks let callbacks = FFIEventCallbacks { on_block: None, on_transaction: None, @@ -181,7 +177,6 @@ fn main() { on_mempool_transaction_removed: None, on_compact_filter_matched: None, on_wallet_transaction: None, - on_filter_headers_progress: Some(on_filter_headers_progress), user_data: ptr::null_mut(), }; let _ = dash_spv_ffi_client_set_event_callbacks(client, callbacks); diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 6e0bc6791..d02843e39 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -135,10 +135,6 @@ pub type WalletTransactionCallback = Option< ), >; -pub type FilterHeadersProgressCallback = Option< - extern "C" fn(filter_height: u32, header_height: u32, percentage: f64, user_data: *mut c_void), ->; - #[repr(C)] pub struct FFIEventCallbacks { pub on_block: BlockCallback, @@ -149,7 +145,7 @@ pub struct FFIEventCallbacks { pub on_mempool_transaction_removed: MempoolRemovedCallback, pub on_compact_filter_matched: CompactFilterMatchedCallback, pub on_wallet_transaction: WalletTransactionCallback, - pub on_filter_headers_progress: FilterHeadersProgressCallback, + // on_filter_headers_progress removed pub user_data: *mut c_void, } @@ -178,7 +174,6 @@ impl Default for FFIEventCallbacks { on_mempool_transaction_removed: None, on_compact_filter_matched: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data: std::ptr::null_mut(), } } @@ -390,22 +385,4 @@ impl FFIEventCallbacks { } } -impl FFIEventCallbacks { - pub fn call_filter_headers_progress( - &self, - filter_height: u32, - header_height: u32, - percentage: f64, - ) { - if let Some(callback) = self.on_filter_headers_progress { - tracing::info!( - "๐Ÿ“Š Calling filter headers progress callback: filter_height={}, header_height={}, pct={:.2}", - filter_height, header_height, percentage - ); - callback(filter_height, header_height, percentage, self.user_data); - tracing::info!("โœ… Filter headers progress callback completed"); - } else { - tracing::debug!("Filter headers progress callback not set"); - } - } -} +// Filter headers progress callback removed; detailed progress covers this data. diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 581a7f4d8..ae3ed9dde 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -249,17 +249,7 @@ impl FFIDashSpvClient { } => { callbacks.call_balance_update(confirmed, unconfirmed); } - dash_spv::types::SpvEvent::FilterHeadersProgress { - filter_header_height, - header_height, - percentage, - } => { - callbacks.call_filter_headers_progress( - filter_header_height, - header_height, - percentage, - ); - } + // FilterHeadersProgress removed; detailed progress reports cover this now. dash_spv::types::SpvEvent::TransactionDetected { ref txid, confirmed, @@ -945,7 +935,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_sync_to_tip_with_progress( FFIErrorCode::Success as i32 } -// Note: filter headers progress is forwarded via FFIEventCallbacks.on_filter_headers_progress +// Filter header progress updates are included in the detailed sync progress callback. /// Cancels the sync operation. /// @@ -1278,10 +1268,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_set_event_callbacks( tracing::debug!(" Block callback: {}", callbacks.on_block.is_some()); tracing::debug!(" Transaction callback: {}", callbacks.on_transaction.is_some()); tracing::debug!(" Balance update callback: {}", callbacks.on_balance_update.is_some()); - tracing::debug!( - " Filter headers progress callback: {}", - callbacks.on_filter_headers_progress.is_some() - ); + tracing::debug!(" Filter headers progress callback: {}", false); let mut event_callbacks = client.event_callbacks.lock().unwrap(); *event_callbacks = callbacks; diff --git a/dash-spv-ffi/tests/integration/test_full_workflow.rs b/dash-spv-ffi/tests/integration/test_full_workflow.rs index 9fea9492f..ccf0039f5 100644 --- a/dash-spv-ffi/tests/integration/test_full_workflow.rs +++ b/dash-spv-ffi/tests/integration/test_full_workflow.rs @@ -196,7 +196,6 @@ mod tests { on_mempool_transaction_removed: None, on_compact_filter_matched: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data: &ctx as *const _ as *mut c_void, }; diff --git a/dash-spv-ffi/tests/test_event_callbacks.rs b/dash-spv-ffi/tests/test_event_callbacks.rs index 0fcdddefd..71dfc38d3 100644 --- a/dash-spv-ffi/tests/test_event_callbacks.rs +++ b/dash-spv-ffi/tests/test_event_callbacks.rs @@ -174,7 +174,6 @@ fn test_event_callbacks_setup() { on_mempool_transaction_removed: None, on_compact_filter_matched: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data, }; @@ -268,7 +267,6 @@ fn test_enhanced_event_callbacks() { on_mempool_transaction_removed: None, on_compact_filter_matched: Some(test_compact_filter_matched_callback), on_wallet_transaction: Some(test_wallet_transaction_callback), - on_filter_headers_progress: None, user_data: Arc::as_ptr(&event_data) as *mut c_void, }; @@ -325,7 +323,6 @@ fn test_drain_events_integration() { on_mempool_transaction_confirmed: None, on_mempool_transaction_removed: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data, }; dash_spv_ffi_client_set_event_callbacks(client, callbacks); @@ -392,7 +389,6 @@ fn test_drain_events_concurrent_with_callbacks() { on_mempool_transaction_confirmed: None, on_mempool_transaction_removed: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data, }; dash_spv_ffi_client_set_event_callbacks(client, callbacks); @@ -472,7 +468,6 @@ fn test_drain_events_callback_lifecycle() { on_mempool_transaction_confirmed: None, on_mempool_transaction_removed: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data, }; dash_spv_ffi_client_set_event_callbacks(client, callbacks); @@ -491,7 +486,6 @@ fn test_drain_events_callback_lifecycle() { on_mempool_transaction_confirmed: None, on_mempool_transaction_removed: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data: std::ptr::null_mut(), }; dash_spv_ffi_client_set_event_callbacks(client, callbacks); @@ -510,7 +504,6 @@ fn test_drain_events_callback_lifecycle() { on_mempool_transaction_confirmed: None, on_mempool_transaction_removed: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data, }; dash_spv_ffi_client_set_event_callbacks(client, callbacks); diff --git a/dash-spv-ffi/tests/unit/test_async_operations.rs b/dash-spv-ffi/tests/unit/test_async_operations.rs index b093974db..aafc90856 100644 --- a/dash-spv-ffi/tests/unit/test_async_operations.rs +++ b/dash-spv-ffi/tests/unit/test_async_operations.rs @@ -599,7 +599,6 @@ mod tests { on_mempool_transaction_removed: None, on_compact_filter_matched: None, on_wallet_transaction: None, - on_filter_headers_progress: None, user_data: &event_data as *const _ as *mut c_void, }; diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 66be86bb7..b6fb4fa15 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -962,7 +962,7 @@ impl< last_rate_calc = Instant::now(); } - // Emit filter headers progress only when heights change + // Emit a detailed progress snapshot when filter/header 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); @@ -970,22 +970,60 @@ impl< storage.get_filter_tip_height().await.ok().flatten().unwrap_or(0); (self.state.read().await.sync_base_height + storage_tip, filter_tip) }; + if abs_header_height != last_emitted_header_height || filter_header_height != last_emitted_filter_header_height { - if abs_header_height > 0 { - let pct = if filter_header_height <= abs_header_height { - (filter_header_height as f64 / abs_header_height as f64 * 100.0) - .min(100.0) + // Build and emit a fresh DetailedSyncProgress snapshot reflecting current filter progress + let peer_best = self + .network + .get_peer_best_height() + .await + .ok() + .flatten() + .unwrap_or(abs_header_height); + + let status_display = self.create_status_display().await; + let mut sync_progress = match status_display.sync_progress().await { + Ok(p) => p, + Err(e) => { + tracing::warn!( + "Failed to compute sync progress snapshot (filter): {}", + e + ); + SyncProgress::default() + } + }; + // Ensure we include up-to-date header height and peer count + sync_progress.peer_count = self.network.peer_count() as u32; + sync_progress.header_height = abs_header_height; + + let progress = DetailedSyncProgress { + sync_progress, + peer_best_height: peer_best, + percentage: if peer_best > 0 { + (abs_header_height as f64 / peer_best as f64 * 100.0).min(100.0) } else { 0.0 - }; - self.emit_event(SpvEvent::FilterHeadersProgress { - filter_header_height, - header_height: abs_header_height, - percentage: pct, - }); - } + }, + headers_per_second: 0.0, + bytes_per_second: 0, + estimated_time_remaining: None, + sync_stage: if abs_header_height < peer_best { + crate::types::SyncStage::DownloadingHeaders { + start: abs_header_height, + end: peer_best, + } + } else { + crate::types::SyncStage::Complete + }, + total_headers_processed: abs_header_height as u64, + total_bytes_downloaded, + sync_start_time, + last_update_time: SystemTime::now(), + }; + self.emit_progress(progress); + last_emitted_header_height = abs_header_height; last_emitted_filter_header_height = filter_header_height; } diff --git a/dash-spv/src/types.rs b/dash-spv/src/types.rs index ebb7e96ee..422116ee4 100644 --- a/dash-spv/src/types.rs +++ b/dash-spv/src/types.rs @@ -789,23 +789,6 @@ pub enum SpvEvent { percentage: f64, }, - /// Filter headers progress update. - /// - /// Carries absolute blockchain heights for both the current filter header tip - /// and the current block header tip, along with a convenience percentage - /// (filter_header_height / header_height * 100), clamped to [0, 100]. - /// - /// Consumers who sync from a checkpoint may prefer to recompute a - /// checkpoint-relative percentage using their base height. - FilterHeadersProgress { - /// Current absolute height of synchronized filter headers. - filter_header_height: u32, - /// Current absolute height of synchronized block headers. - header_height: u32, - /// Convenience percentage in [0, 100]. - percentage: f64, - }, - /// ChainLock received and validated. ChainLockReceived { /// Block height of the ChainLock. diff --git a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h index 190f458cc..2afebb22b 100644 --- a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h +++ b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h @@ -149,11 +149,6 @@ typedef void (*WalletTransactionCallback)(const char *wallet_id, bool is_ours, void *user_data); -typedef void (*FilterHeadersProgressCallback)(uint32_t filter_height, - uint32_t header_height, - double percentage, - void *user_data); - typedef struct FFIEventCallbacks { BlockCallback on_block; TransactionCallback on_transaction; @@ -163,7 +158,6 @@ typedef struct FFIEventCallbacks { MempoolRemovedCallback on_mempool_transaction_removed; CompactFilterMatchedCallback on_compact_filter_matched; WalletTransactionCallback on_wallet_transaction; - FilterHeadersProgressCallback on_filter_headers_progress; void *user_data; } FFIEventCallbacks; From 5f29b988cb5a65a3427eebcad1b9baea719dff3c Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 27 Sep 2025 21:28:40 +0700 Subject: [PATCH 03/10] Update dash-spv-ffi/src/client.rs Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- dash-spv-ffi/src/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index ae3ed9dde..65b8728c7 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -1268,7 +1268,6 @@ pub unsafe extern "C" fn dash_spv_ffi_client_set_event_callbacks( tracing::debug!(" Block callback: {}", callbacks.on_block.is_some()); tracing::debug!(" Transaction callback: {}", callbacks.on_transaction.is_some()); tracing::debug!(" Balance update callback: {}", callbacks.on_balance_update.is_some()); - tracing::debug!(" Filter headers progress callback: {}", false); let mut event_callbacks = client.event_callbacks.lock().unwrap(); *event_callbacks = callbacks; From 7622e6db3027396698690b7f21b4386b860fdcf9 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 27 Sep 2025 21:28:49 +0700 Subject: [PATCH 04/10] Update dash-spv-ffi/src/client.rs Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- dash-spv-ffi/src/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 65b8728c7..a9a9db4a7 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -249,7 +249,6 @@ impl FFIDashSpvClient { } => { callbacks.call_balance_update(confirmed, unconfirmed); } - // FilterHeadersProgress removed; detailed progress reports cover this now. dash_spv::types::SpvEvent::TransactionDetected { ref txid, confirmed, From 9147aff7063605f2e010dfab53b6945b72dbaa42 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 27 Sep 2025 21:28:55 +0700 Subject: [PATCH 05/10] Update dash-spv-ffi/src/callbacks.rs Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- dash-spv-ffi/src/callbacks.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index d02843e39..74b96816c 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -385,4 +385,3 @@ impl FFIEventCallbacks { } } -// Filter headers progress callback removed; detailed progress covers this data. From 389f0a35204a6c9af002fd1fea9f60ee0e3bc8ee Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Sat, 27 Sep 2025 21:29:09 +0700 Subject: [PATCH 06/10] Update dash-spv-ffi/src/callbacks.rs Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- dash-spv-ffi/src/callbacks.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 74b96816c..06fbdc6a7 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -145,7 +145,6 @@ pub struct FFIEventCallbacks { pub on_mempool_transaction_removed: MempoolRemovedCallback, pub on_compact_filter_matched: CompactFilterMatchedCallback, pub on_wallet_transaction: WalletTransactionCallback, - // on_filter_headers_progress removed pub user_data: *mut c_void, } From 52cdb167ca7b98dd7accfab8f73e400309cdd5dd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 28 Sep 2025 04:09:01 +0700 Subject: [PATCH 07/10] fixes --- dash-spv-ffi/dash_spv_ffi.h | 9 +- dash-spv-ffi/include/dash_spv_ffi.h | 9 +- dash-spv-ffi/src/bin/ffi_cli.rs | 5 +- dash-spv-ffi/src/client.rs | 5 +- dash-spv-ffi/src/types.rs | 26 ++- .../tests/performance/test_benchmarks.rs | 6 +- dash-spv-ffi/tests/test_types.rs | 8 +- .../tests/unit/test_type_conversions.rs | 3 - dash-spv/src/client/mod.rs | 184 +++++++++++------- dash-spv/src/client/status_display.rs | 3 - dash-spv/src/storage/sync_state.rs | 8 +- dash-spv/src/sync/filters.rs | 3 + dash-spv/src/sync/sequential/mod.rs | 20 +- dash-spv/src/types.rs | 20 +- .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 9 +- .../SwiftDashCoreSDK/Core/SPVClient.swift | 20 +- .../Models/SyncProgress.swift | 13 +- 17 files changed, 194 insertions(+), 157 deletions(-) diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 2afebb22b..780251d54 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -28,8 +28,10 @@ typedef enum FFISyncStage { Downloading = 2, Validating = 3, Storing = 4, - Complete = 5, - Failed = 6, + DownloadingFilterHeaders = 5, + DownloadingFilters = 6, + Complete = 7, + Failed = 8, } FFISyncStage; typedef enum DashSpvValidationMode { @@ -75,9 +77,6 @@ typedef struct FFISyncProgress { uint32_t filter_header_height; uint32_t masternode_height; uint32_t peer_count; - bool headers_synced; - bool filter_headers_synced; - bool masternodes_synced; bool filter_sync_available; uint32_t filters_downloaded; uint32_t last_synced_filter_height; diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 2afebb22b..780251d54 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -28,8 +28,10 @@ typedef enum FFISyncStage { Downloading = 2, Validating = 3, Storing = 4, - Complete = 5, - Failed = 6, + DownloadingFilterHeaders = 5, + DownloadingFilters = 6, + Complete = 7, + Failed = 8, } FFISyncStage; typedef enum DashSpvValidationMode { @@ -75,9 +77,6 @@ typedef struct FFISyncProgress { uint32_t filter_header_height; uint32_t masternode_height; uint32_t peer_count; - bool headers_synced; - bool filter_headers_synced; - bool masternodes_synced; bool filter_sync_available; uint32_t filters_downloaded; uint32_t last_synced_filter_height; diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index 5b11ef9b9..1650fa671 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -206,10 +206,11 @@ fn main() { let prog_ptr = dash_spv_ffi_client_get_sync_progress(client); if !prog_ptr.is_null() { let prog = &*prog_ptr; - let filters_complete = prog.filter_headers_synced + let headers_done = prog.header_height >= prog.filter_header_height; + let filters_complete = prog.filter_header_height >= prog.header_height || !prog.filter_sync_available || disable_filter_sync; - if prog.headers_synced && filters_complete { + if headers_done && filters_complete { dash_spv_ffi_sync_progress_destroy(prog_ptr); break; } diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index ae3ed9dde..f1b8d170d 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -794,7 +794,10 @@ pub unsafe extern "C" fn dash_spv_ffi_client_sync_to_tip_with_progress( match maybe_progress { Some(progress) => { // Handle callback in a thread-safe way - let should_stop = matches!(progress.sync_stage, SyncStage::Complete); + let should_stop = matches!( + progress.sync_stage, + SyncStage::Complete | SyncStage::Failed(_) + ); // Create FFI progress let ffi_progress = Box::new(FFIDetailedSyncProgress::from(progress)); diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index b18f50088..e1450430d 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -38,9 +38,6 @@ pub struct FFISyncProgress { pub filter_header_height: u32, pub masternode_height: u32, pub peer_count: u32, - pub headers_synced: bool, - pub filter_headers_synced: bool, - pub masternodes_synced: bool, pub filter_sync_available: bool, pub filters_downloaded: u32, pub last_synced_filter_height: u32, @@ -53,9 +50,6 @@ impl From for FFISyncProgress { filter_header_height: progress.filter_header_height, masternode_height: progress.masternode_height, peer_count: progress.peer_count, - headers_synced: progress.headers_synced, - filter_headers_synced: progress.filter_headers_synced, - masternodes_synced: progress.masternodes_synced, filter_sync_available: progress.filter_sync_available, filters_downloaded: progress.filters_downloaded as u32, last_synced_filter_height: progress.last_synced_filter_height.unwrap_or(0), @@ -71,8 +65,10 @@ pub enum FFISyncStage { Downloading = 2, Validating = 3, Storing = 4, - Complete = 5, - Failed = 6, + DownloadingFilterHeaders = 5, + DownloadingFilters = 6, + Complete = 7, + Failed = 8, } impl From for FFISyncStage { @@ -89,6 +85,12 @@ impl From for FFISyncStage { SyncStage::StoringHeaders { .. } => FFISyncStage::Storing, + SyncStage::DownloadingFilterHeaders { + .. + } => FFISyncStage::DownloadingFilterHeaders, + SyncStage::DownloadingFilters { + .. + } => FFISyncStage::DownloadingFilters, SyncStage::Complete => FFISyncStage::Complete, SyncStage::Failed(_) => FFISyncStage::Failed, } @@ -125,6 +127,14 @@ impl From for FFIDetailedSyncProgress { SyncStage::StoringHeaders { batch_size, } => format!("Storing {} headers", batch_size), + SyncStage::DownloadingFilterHeaders { + current, + target, + } => format!("Downloading filter headers {} / {}", current, target), + SyncStage::DownloadingFilters { + completed, + total, + } => format!("Downloading filters {} / {}", completed, total), SyncStage::Complete => "Synchronization complete".to_string(), SyncStage::Failed(err) => err.clone(), }; diff --git a/dash-spv-ffi/tests/performance/test_benchmarks.rs b/dash-spv-ffi/tests/performance/test_benchmarks.rs index 423a71899..4096b9def 100644 --- a/dash-spv-ffi/tests/performance/test_benchmarks.rs +++ b/dash-spv-ffi/tests/performance/test_benchmarks.rs @@ -382,9 +382,7 @@ mod tests { filter_header_height: 12340, masternode_height: 12300, peer_count: 8, - headers_synced: true, - filter_headers_synced: true, - masternodes_synced: false, + filter_sync_available: true, filters_downloaded: 1000, last_synced_filter_height: Some(12000), sync_start: std::time::SystemTime::now(), @@ -448,4 +446,4 @@ mod tests { } } } -} \ No newline at end of file +} diff --git a/dash-spv-ffi/tests/test_types.rs b/dash-spv-ffi/tests/test_types.rs index 23af9b212..da43b6295 100644 --- a/dash-spv-ffi/tests/test_types.rs +++ b/dash-spv-ffi/tests/test_types.rs @@ -84,11 +84,8 @@ mod tests { filter_header_height: 90, masternode_height: 80, peer_count: 5, - headers_synced: true, - filter_headers_synced: false, - masternodes_synced: false, - filters_downloaded: 50, filter_sync_available: true, + filters_downloaded: 50, last_synced_filter_height: Some(45), sync_start: std::time::SystemTime::now(), last_update: std::time::SystemTime::now(), @@ -100,9 +97,6 @@ mod tests { assert_eq!(ffi_progress.filter_header_height, 90); assert_eq!(ffi_progress.masternode_height, 80); assert_eq!(ffi_progress.peer_count, 5); - assert!(ffi_progress.headers_synced); - assert!(!ffi_progress.filter_headers_synced); - assert!(!ffi_progress.masternodes_synced); assert_eq!(ffi_progress.filters_downloaded, 50); assert_eq!(ffi_progress.last_synced_filter_height, 45); } diff --git a/dash-spv-ffi/tests/unit/test_type_conversions.rs b/dash-spv-ffi/tests/unit/test_type_conversions.rs index 3aa61e4a9..ee6586caa 100644 --- a/dash-spv-ffi/tests/unit/test_type_conversions.rs +++ b/dash-spv-ffi/tests/unit/test_type_conversions.rs @@ -144,9 +144,6 @@ mod tests { filter_header_height: u32::MAX, masternode_height: u32::MAX, peer_count: u32::MAX, - headers_synced: true, - filter_headers_synced: true, - masternodes_synced: true, filter_sync_available: true, filters_downloaded: u64::MAX, last_synced_filter_height: Some(u32::MAX), diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index b6fb4fa15..dbe8178ca 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -19,10 +19,11 @@ use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::sync::filters::FilterNotificationSender; +use crate::sync::sequential::phases::SyncPhase; use crate::sync::sequential::SequentialSyncManager; use crate::types::{ AddressBalance, ChainState, DetailedSyncProgress, MempoolState, SpvEvent, SpvStats, - SyncProgress, + SyncProgress, SyncStage, }; use crate::validation::ValidationManager; use dashcore::network::constants::NetworkExt; @@ -148,6 +149,61 @@ impl< let _ = self.event_tx.send(event); } + fn map_phase_to_stage( + phase: &SyncPhase, + sync_progress: &SyncProgress, + peer_best_height: u32, + ) -> SyncStage { + match phase { + SyncPhase::Idle => { + if sync_progress.peer_count == 0 { + SyncStage::Connecting + } else { + SyncStage::QueryingPeerHeight + } + } + SyncPhase::DownloadingHeaders { + start_height, + target_height, + .. + } => SyncStage::DownloadingHeaders { + start: *start_height, + end: target_height.unwrap_or(peer_best_height), + }, + SyncPhase::DownloadingMnList { + diffs_processed, + .. + } => SyncStage::ValidatingHeaders { + batch_size: *diffs_processed as usize, + }, + SyncPhase::DownloadingCFHeaders { + current_height, + target_height, + .. + } => SyncStage::DownloadingFilterHeaders { + current: *current_height, + target: *target_height, + }, + SyncPhase::DownloadingFilters { + completed_heights, + total_filters, + .. + } => SyncStage::DownloadingFilters { + completed: completed_heights.len() as u32, + total: *total_filters, + }, + SyncPhase::DownloadingBlocks { + pending_blocks, + .. + } => SyncStage::StoringHeaders { + batch_size: pending_blocks.len(), + }, + SyncPhase::FullySynced { + .. + } => SyncStage::Complete, + } + } + /// Helper to create a StatusDisplay instance. async fn create_status_display(&self) -> StatusDisplay<'_, S> { StatusDisplay::new( @@ -674,8 +730,6 @@ impl< let storage = self.storage.lock().await; storage.get_filter_tip_height().await.map_err(SpvError::Storage)?.unwrap_or(0) }, - headers_synced: false, // Will be synced by monitoring loop - filter_headers_synced: false, ..SyncProgress::default() }; @@ -745,6 +799,7 @@ impl< // Last emitted heights for filter headers progress to avoid duplicate events let mut last_emitted_header_height: u32 = 0; let mut last_emitted_filter_header_height: u32 = 0; + let mut last_emitted_filters_downloaded: u64 = 0; loop { // Check if we should stop @@ -903,20 +958,8 @@ impl< } let headers_per_second = headers_this_second as f64; - - // Determine sync stage - let sync_stage = if self.network.peer_count() == 0 { - crate::types::SyncStage::Connecting - } else if current_height == 0 { - crate::types::SyncStage::QueryingPeerHeight - } else if current_height < peer_best { - crate::types::SyncStage::DownloadingHeaders { - start: current_height, - end: peer_best, - } - } else { - crate::types::SyncStage::Complete - }; + let peer_count = self.network.peer_count() as u32; + let phase_snapshot = self.sync_manager.current_phase().clone(); let status_display = self.create_status_display().await; let mut sync_progress = match status_display.sync_progress().await { @@ -928,8 +971,13 @@ impl< }; // Update peer count with the latest network information. - sync_progress.peer_count = self.network.peer_count() as u32; + sync_progress.peer_count = peer_count; sync_progress.header_height = current_height; + sync_progress.filter_sync_available = self.config.enable_filters; + + let sync_stage = + Self::map_phase_to_stage(&phase_snapshot, &sync_progress, peer_best); + let filters_downloaded = sync_progress.filters_downloaded; let progress = DetailedSyncProgress { sync_progress, @@ -956,6 +1004,7 @@ impl< last_update_time: SystemTime::now(), }; + last_emitted_filters_downloaded = filters_downloaded; self.emit_progress(progress); headers_this_second = 0; @@ -963,16 +1012,19 @@ impl< } // Emit a detailed progress snapshot when filter/header 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 filter_tip = - storage.get_filter_tip_height().await.ok().flatten().unwrap_or(0); - (self.state.read().await.sync_base_height + storage_tip, filter_tip) + let (_sync_base_height, abs_header_height, filter_header_height) = { + let (storage_tip, filter_tip) = { + let storage = self.storage.lock().await; + let storage_tip = + storage.get_tip_height().await.ok().flatten().unwrap_or(0); + let filter_tip = + storage.get_filter_tip_height().await.ok().flatten().unwrap_or(0); + (storage_tip, filter_tip) + }; + let base = { self.state.read().await.sync_base_height }; + (base, base + storage_tip, filter_tip) }; - if abs_header_height != last_emitted_header_height - || filter_header_height != last_emitted_filter_header_height { // Build and emit a fresh DetailedSyncProgress snapshot reflecting current filter progress let peer_best = self @@ -983,6 +1035,7 @@ impl< .flatten() .unwrap_or(abs_header_height); + let phase_snapshot = self.sync_manager.current_phase().clone(); let status_display = self.create_status_display().await; let mut sync_progress = match status_display.sync_progress().await { Ok(p) => p, @@ -995,37 +1048,43 @@ impl< } }; // Ensure we include up-to-date header height and peer count - sync_progress.peer_count = self.network.peer_count() as u32; + let peer_count = self.network.peer_count() as u32; + sync_progress.peer_count = peer_count; sync_progress.header_height = abs_header_height; + sync_progress.filter_sync_available = self.config.enable_filters; - let progress = DetailedSyncProgress { - sync_progress, - peer_best_height: peer_best, - percentage: if peer_best > 0 { - (abs_header_height as f64 / peer_best as f64 * 100.0).min(100.0) - } else { - 0.0 - }, - headers_per_second: 0.0, - bytes_per_second: 0, - estimated_time_remaining: None, - sync_stage: if abs_header_height < peer_best { - crate::types::SyncStage::DownloadingHeaders { - start: abs_header_height, - end: peer_best, - } - } else { - crate::types::SyncStage::Complete - }, - total_headers_processed: abs_header_height as u64, - total_bytes_downloaded, - sync_start_time, - last_update_time: SystemTime::now(), - }; - self.emit_progress(progress); + let filters_downloaded = sync_progress.filters_downloaded; - last_emitted_header_height = abs_header_height; - last_emitted_filter_header_height = filter_header_height; + if abs_header_height != last_emitted_header_height + || filter_header_height != last_emitted_filter_header_height + || filters_downloaded != last_emitted_filters_downloaded + { + let sync_stage = + Self::map_phase_to_stage(&phase_snapshot, &sync_progress, peer_best); + + let progress = DetailedSyncProgress { + sync_progress, + peer_best_height: peer_best, + percentage: if peer_best > 0 { + (abs_header_height as f64 / peer_best as f64 * 100.0).min(100.0) + } else { + 0.0 + }, + headers_per_second: 0.0, + bytes_per_second: 0, + estimated_time_remaining: None, + sync_stage, + total_headers_processed: abs_header_height as u64, + total_bytes_downloaded, + sync_start_time, + last_update_time: SystemTime::now(), + }; + last_emitted_header_height = abs_header_height; + last_emitted_filter_header_height = filter_header_height; + last_emitted_filters_downloaded = filters_downloaded; + + self.emit_progress(progress); + } } last_status_update = Instant::now(); @@ -1970,18 +2029,11 @@ impl< tracing::debug!("Sequential sync manager will resume from stored state"); // Determine phase based on sync progress - if saved_state.sync_progress.headers_synced { - if saved_state.sync_progress.filter_headers_synced { - // Headers and filter headers done, we're in filter download phase - tracing::info!("Resuming sequential sync in filter download phase"); - } else { - // Headers done, need filter headers - tracing::info!("Resuming sequential sync in filter header download phase"); - } - } else { - // Still downloading headers - tracing::info!("Resuming sequential sync in header download phase"); - } + tracing::info!( + "Resuming sequential sync; saved header height {} filter header height {}", + saved_state.sync_progress.header_height, + saved_state.sync_progress.filter_header_height + ); // Reset any in-flight requests self.sync_manager.reset_pending_requests(); diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index b8537274d..9b5c5aef9 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -99,9 +99,6 @@ impl<'a, S: StorageManager + Send + Sync + 'static> StatusDisplay<'a, S> { filter_header_height, masternode_height: state.last_masternode_diff_height.unwrap_or(0), peer_count: 1, // TODO: Get from network manager - headers_synced: false, // TODO: Implement - filter_headers_synced: false, // TODO: Implement - masternodes_synced: false, // TODO: Implement filter_sync_available: false, // TODO: Get from network manager filters_downloaded: filters_received, last_synced_filter_height, diff --git a/dash-spv/src/storage/sync_state.rs b/dash-spv/src/storage/sync_state.rs index 379a0b55d..27318a44b 100644 --- a/dash-spv/src/storage/sync_state.rs +++ b/dash-spv/src/storage/sync_state.rs @@ -172,12 +172,8 @@ impl PersistentSyncState { sync_progress: sync_progress.clone(), checkpoints: Self::create_checkpoints(chain_state), masternode_sync: MasternodeSyncState { - last_synced_height: if sync_progress.masternodes_synced { - Some(sync_progress.masternode_height) - } else { - None - }, - is_synced: sync_progress.masternodes_synced, + last_synced_height: None, + is_synced: false, masternode_count: chain_state .masternode_engine .as_ref() diff --git a/dash-spv/src/sync/filters.rs b/dash-spv/src/sync/filters.rs index 3ff4ae24b..58d11f5ab 100644 --- a/dash-spv/src/sync/filters.rs +++ b/dash-spv/src/sync/filters.rs @@ -2879,6 +2879,9 @@ impl Date: Sun, 28 Sep 2025 04:37:33 +0700 Subject: [PATCH 08/10] fmt --- dash-spv-ffi/src/callbacks.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 06fbdc6a7..5506b3ddf 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -383,4 +383,3 @@ impl FFIEventCallbacks { } } } - From 3e7ea3e23aca0a42aec6e0bc493dd00a186c9917 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 28 Sep 2025 04:41:18 +0700 Subject: [PATCH 09/10] fixes --- dash-spv/tests/header_sync_test.rs | 1 - dash-spv/tests/integration_real_node_test.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dash-spv/tests/header_sync_test.rs b/dash-spv/tests/header_sync_test.rs index 032cf80f1..f254181bc 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -319,7 +319,6 @@ async fn test_header_sync_with_client_integration() { let stats = stats.unwrap(); assert_eq!(stats.header_height, 0); - assert!(!stats.headers_synced); info!("Header sync client integration test completed"); } diff --git a/dash-spv/tests/integration_real_node_test.rs b/dash-spv/tests/integration_real_node_test.rs index 163bd8ccd..596012f40 100644 --- a/dash-spv/tests/integration_real_node_test.rs +++ b/dash-spv/tests/integration_real_node_test.rs @@ -132,8 +132,8 @@ async fn test_real_header_sync_genesis_to_1000() { client.sync_progress().await.expect("Failed to get initial sync progress"); info!( - "Initial sync state: height={}, synced={}", - initial_progress.header_height, initial_progress.headers_synced + "Initial sync state: header_height={} filter_header_height={}", + initial_progress.header_height, initial_progress.filter_header_height ); // Perform header sync @@ -241,7 +241,7 @@ async fn test_real_header_sync_up_to_10k() { } // Check if we've reached our target or sync is complete - if progress.header_height >= MAX_TEST_HEADERS || progress.headers_synced { + if progress.header_height >= MAX_TEST_HEADERS { return Ok::<_, dash_spv::error::SpvError>(progress); } From f3256e3a9d4311cfcba6bcf632664117bec369bf Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 28 Sep 2025 13:09:32 +0700 Subject: [PATCH 10/10] more fixes --- dash-spv-ffi/dash_spv_ffi.h | 5 +++-- dash-spv-ffi/include/dash_spv_ffi.h | 5 +++-- dash-spv-ffi/src/bin/ffi_cli.rs | 20 ++++++++++++++----- dash-spv-ffi/src/types.rs | 11 ++++++++-- dash-spv/src/client/mod.rs | 4 ++-- dash-spv/src/types.rs | 3 +++ .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 5 +++-- .../SwiftDashCoreSDK/Core/SPVClient.swift | 11 ++++++++-- .../Models/SyncProgress.swift | 3 --- 9 files changed, 47 insertions(+), 20 deletions(-) diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 780251d54..b8169ea5c 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -30,8 +30,9 @@ typedef enum FFISyncStage { Storing = 4, DownloadingFilterHeaders = 5, DownloadingFilters = 6, - Complete = 7, - Failed = 8, + DownloadingBlocks = 7, + Complete = 8, + Failed = 9, } FFISyncStage; typedef enum DashSpvValidationMode { diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 780251d54..b8169ea5c 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -30,8 +30,9 @@ typedef enum FFISyncStage { Storing = 4, DownloadingFilterHeaders = 5, DownloadingFilters = 6, - Complete = 7, - Failed = 8, + DownloadingBlocks = 7, + Complete = 8, + Failed = 9, } FFISyncStage; typedef enum DashSpvValidationMode { diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index ded4ccac9..38611856b 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -1,6 +1,7 @@ use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use std::time::Duration; @@ -16,6 +17,8 @@ enum NetworkOpt { Regtest, } +static SYNC_COMPLETED: AtomicBool = AtomicBool::new(false); + fn ffi_string_to_rust(s: *const c_char) -> String { if s.is_null() { return String::new(); @@ -44,6 +47,7 @@ extern "C" fn on_completion(success: bool, msg: *const c_char, _ud: *mut c_void) let m = ffi_string_to_rust(msg); if success { println!("Completed: {}", m); + SYNC_COMPLETED.store(true, Ordering::SeqCst); } else { eprintln!("Failed: {}", m); } @@ -188,6 +192,9 @@ fn main() { std::process::exit(1); } + // Ensure completion flag is reset before starting sync + SYNC_COMPLETED.store(false, Ordering::SeqCst); + // Run sync on this thread; detailed progress will print via callback let rc = dash_spv_ffi_client_sync_to_tip_with_progress( client, @@ -206,11 +213,14 @@ fn main() { let prog_ptr = dash_spv_ffi_client_get_sync_progress(client); if !prog_ptr.is_null() { let prog = &*prog_ptr; - let headers_done = prog.header_height >= prog.filter_header_height; - let filters_complete = prog.filter_header_height >= prog.header_height - || !prog.filter_sync_available - || disable_filter_sync; - if headers_done && filters_complete { + let headers_done = SYNC_COMPLETED.load(Ordering::SeqCst); + let filters_complete = if disable_filter_sync || !prog.filter_sync_available { + false + } else { + prog.filter_header_height >= prog.header_height + && prog.last_synced_filter_height >= prog.filter_header_height + }; + if headers_done && (filters_complete || disable_filter_sync) { dash_spv_ffi_sync_progress_destroy(prog_ptr); break; } diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index e1450430d..d703e376a 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -67,8 +67,9 @@ pub enum FFISyncStage { Storing = 4, DownloadingFilterHeaders = 5, DownloadingFilters = 6, - Complete = 7, - Failed = 8, + DownloadingBlocks = 7, + Complete = 8, + Failed = 9, } impl From for FFISyncStage { @@ -91,6 +92,9 @@ impl From for FFISyncStage { SyncStage::DownloadingFilters { .. } => FFISyncStage::DownloadingFilters, + SyncStage::DownloadingBlocks { + .. + } => FFISyncStage::DownloadingBlocks, SyncStage::Complete => FFISyncStage::Complete, SyncStage::Failed(_) => FFISyncStage::Failed, } @@ -135,6 +139,9 @@ impl From for FFIDetailedSyncProgress { completed, total, } => format!("Downloading filters {} / {}", completed, total), + SyncStage::DownloadingBlocks { + pending, + } => format!("Downloading blocks ({} pending)", pending), SyncStage::Complete => "Synchronization complete".to_string(), SyncStage::Failed(err) => err.clone(), }; diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 837c3bbfb..ada0831d0 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -195,8 +195,8 @@ impl< SyncPhase::DownloadingBlocks { pending_blocks, .. - } => SyncStage::StoringHeaders { - batch_size: pending_blocks.len(), + } => SyncStage::DownloadingBlocks { + pending: pending_blocks.len(), }, SyncPhase::FullySynced { .. diff --git a/dash-spv/src/types.rs b/dash-spv/src/types.rs index a3f7ffb9b..cde711a8e 100644 --- a/dash-spv/src/types.rs +++ b/dash-spv/src/types.rs @@ -116,6 +116,9 @@ pub enum SyncStage { completed: u32, total: u32, }, + DownloadingBlocks { + pending: usize, + }, Complete, Failed(String), } diff --git a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h index 780251d54..b8169ea5c 100644 --- a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h +++ b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h @@ -30,8 +30,9 @@ typedef enum FFISyncStage { Storing = 4, DownloadingFilterHeaders = 5, DownloadingFilters = 6, - Complete = 7, - Failed = 8, + DownloadingBlocks = 7, + Complete = 8, + Failed = 9, } FFISyncStage; typedef enum DashSpvValidationMode { diff --git a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift index c443da353..110e6b55f 100644 --- a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift +++ b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Core/SPVClient.swift @@ -109,6 +109,7 @@ public enum SyncStage: Equatable, Sendable { case downloading case downloadingFilterHeaders case downloadingFilters + case downloadingBlocks case validating case storing case complete @@ -131,9 +132,11 @@ public enum SyncStage: Equatable, Sendable { self = .downloadingFilterHeaders case 6: // Downloading filters self = .downloadingFilters - case 7: // Complete + case 7: // Downloading blocks + self = .downloadingBlocks + case 8: // Complete self = .complete - case 8: // Failed + case 9: // Failed self = .failed default: self = .failed @@ -152,6 +155,8 @@ public enum SyncStage: Equatable, Sendable { return "Downloading filter headers" case .downloadingFilters: return "Downloading filters" + case .downloadingBlocks: + return "Downloading blocks" case .validating: return "Validating headers" case .storing: @@ -184,6 +189,8 @@ public enum SyncStage: Equatable, Sendable { return "๐Ÿงพ" case .downloadingFilters: return "๐Ÿช„" + case .downloadingBlocks: + return "๐Ÿ“ฆ" case .validating: return "โœ…" case .storing: diff --git a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift index cbd25d9a2..e4795c1da 100644 --- a/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift +++ b/swift-dash-core-sdk/Sources/SwiftDashCoreSDK/Models/SyncProgress.swift @@ -28,9 +28,6 @@ public struct SyncProgress: Sendable, Equatable { filterHeaderHeight: UInt32 = 0, masternodeHeight: UInt32 = 0, peerCount: UInt32 = 0, - headersSynced: Bool = false, - filterHeadersSynced: Bool = false, - masternodesSynced: Bool = false, filtersDownloaded: UInt32 = 0, lastSyncedFilterHeight: UInt32 = 0 ) {