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
1 change: 0 additions & 1 deletion dash-spv-ffi/FFI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,6 @@ Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
- `FFINetwork` - Network type (Dash, Testnet, Regtest, Devnet)
- `FFIValidationMode` - Validation mode (None, Basic, Full)
- `FFIMempoolStrategy` - Mempool strategy (FetchAll, BloomFilter, Selective)
- `FFISyncStage` - Synchronization stage

## Memory Management

Expand Down
1 change: 0 additions & 1 deletion dash-spv-ffi/scripts/generate_ffi_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ def generate_markdown(functions: List[FFIFunction]) -> str:
md.append("- `FFINetwork` - Network type (Dash, Testnet, Regtest, Devnet)")
md.append("- `FFIValidationMode` - Validation mode (None, Basic, Full)")
md.append("- `FFIMempoolStrategy` - Mempool strategy (FetchAll, BloomFilter, Selective)")
md.append("- `FFISyncStage` - Synchronization stage")
md.append("")

# Memory Management
Expand Down
65 changes: 0 additions & 65 deletions dash-spv-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use dash_spv::Hash;
use futures::future::{AbortHandle, Abortable};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::runtime::Runtime;
use tokio::sync::{broadcast, watch};
Expand Down Expand Up @@ -355,70 +354,6 @@ pub unsafe extern "C" fn dash_spv_ffi_client_stop(client: *mut FFIDashSpvClient)
}
}

pub fn client_test_sync(client: &FFIDashSpvClient) -> i32 {
let result = client.runtime.block_on(async {
let spv_client = {
let mut guard = client.inner.lock().unwrap();
match guard.take() {
Some(client) => client,
None => {
return Err(dash_spv::SpvError::Config("Client not initialized".to_string()))
}
}
};
tracing::info!("Starting test sync...");

// Get initial height
let progress = spv_client.sync_progress();
let start_height = match progress.headers() {
Ok(progress) => progress.current_height(),
Err(e) => {
tracing::error!("Failed to get initial height: {}", e);
return Err(e.into());
}
};
tracing::info!("Initial height: {}", start_height);

// Wait a bit for headers to download
tokio::time::sleep(Duration::from_secs(10)).await;

// Check if headers increased
let progress = spv_client.sync_progress();
let end_height = match progress.headers() {
Ok(progress) => progress.current_height(),
Err(e) => {
tracing::error!("Failed to get final height: {}", e);
let mut guard = client.inner.lock().unwrap();
*guard = Some(spv_client);
return Err(e.into());
}
};
tracing::info!("Final height: {}", end_height);

let result = if end_height > start_height {
tracing::info!("✅ Sync working! Downloaded {} headers", end_height - start_height);
Ok(())
} else {
let msg = "No headers downloaded".to_string();
tracing::error!("❌ {}", msg);
Err(dash_spv::SpvError::Sync(dash_spv::SyncError::Network(msg)))
};

// put client back
let mut guard = client.inner.lock().unwrap();
*guard = Some(spv_client);
result
});

match result {
Ok(_) => FFIErrorCode::Success as i32,
Err(e) => {
set_last_error(&e.to_string());
FFIErrorCode::from(e) as i32
}
}
}

/// Start the SPV client and begin syncing in the background.
///
/// This is the streamlined entry point that combines `start()` and continuous monitoring
Expand Down
140 changes: 1 addition & 139 deletions dash-spv-ffi/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use dash_spv::sync::{
BlockHeadersProgress, BlocksProgress, ChainLockProgress, FilterHeadersProgress,
FiltersProgress, InstantSendProgress, MasternodesProgress, SyncProgress, SyncState,
};
use dash_spv::types::{DetailedSyncProgress, MempoolRemovalReason, SyncStage};
use dash_spv::SyncProgress as LegacySyncProgress;
use dash_spv::types::MempoolRemovalReason;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

Expand Down Expand Up @@ -46,75 +45,6 @@ impl FFIString {
}
}

#[repr(C)]
pub struct FFILegacySyncProgress {
pub header_height: u32,
pub filter_header_height: u32,
pub masternode_height: u32,
pub peer_count: u32,
pub filter_sync_available: bool,
pub filters_downloaded: u32,
pub last_synced_filter_height: u32,
}

impl From<LegacySyncProgress> for FFILegacySyncProgress {
fn from(progress: LegacySyncProgress) -> Self {
FFILegacySyncProgress {
header_height: progress.header_height,
filter_header_height: progress.filter_header_height,
masternode_height: progress.masternode_height,
peer_count: progress.peer_count,
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),
}
}
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum FFISyncStage {
Connecting = 0,
QueryingHeight = 1,
Downloading = 2,
Validating = 3,
Storing = 4,
DownloadingFilterHeaders = 5,
DownloadingFilters = 6,
DownloadingBlocks = 7,
Complete = 8,
Failed = 9,
}

impl From<SyncStage> for FFISyncStage {
fn from(stage: SyncStage) -> Self {
match stage {
SyncStage::Connecting => FFISyncStage::Connecting,
SyncStage::QueryingPeerHeight => FFISyncStage::QueryingHeight,
SyncStage::DownloadingHeaders {
..
} => FFISyncStage::Downloading,
SyncStage::ValidatingHeaders {
..
} => FFISyncStage::Validating,
SyncStage::StoringHeaders {
..
} => FFISyncStage::Storing,
SyncStage::DownloadingFilterHeaders {
..
} => FFISyncStage::DownloadingFilterHeaders,
SyncStage::DownloadingFilters {
..
} => FFISyncStage::DownloadingFilters,
SyncStage::DownloadingBlocks {
..
} => FFISyncStage::DownloadingBlocks,
SyncStage::Complete => FFISyncStage::Complete,
SyncStage::Failed(_) => FFISyncStage::Failed,
}
}
}

/// SyncState exposed by the FFI as FFISyncState.
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -404,74 +334,6 @@ impl From<SyncProgress> for FFISyncProgress {
}
}

#[repr(C)]
pub struct FFIDetailedSyncProgress {
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 overview: FFILegacySyncProgress,
pub total_headers: u64,
pub sync_start_timestamp: i64,
}

impl From<DetailedSyncProgress> for FFIDetailedSyncProgress {
fn from(progress: DetailedSyncProgress) -> Self {
use std::time::UNIX_EPOCH;

let stage_message = match &progress.sync_stage {
SyncStage::Connecting => "Connecting to peers".to_string(),
SyncStage::QueryingPeerHeight => "Querying blockchain height".to_string(),
SyncStage::DownloadingHeaders {
start,
end,
} => format!("Downloading headers {} to {}", start, end),
SyncStage::ValidatingHeaders {
batch_size,
} => format!("Validating {} headers", batch_size),
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::DownloadingBlocks {
pending,
} => format!("Downloading blocks ({} pending)", pending),
SyncStage::Complete => "Synchronization complete".to_string(),
SyncStage::Failed(err) => err.clone(),
};

let overview = FFILegacySyncProgress::from(progress.sync_progress.clone());

FFIDetailedSyncProgress {
total_height: progress.peer_best_height,
percentage: progress.percentage,
headers_per_second: progress.headers_per_second,
estimated_seconds_remaining: progress
.estimated_time_remaining
.map(|d| d.as_secs() as i64)
.unwrap_or(-1),
stage: progress.sync_stage.into(),
stage_message: FFIString::new(&stage_message),
overview,
total_headers: progress.total_headers_processed,
sync_start_timestamp: progress
.sync_start_time
.duration_since(UNIX_EPOCH)
.unwrap_or(std::time::Duration::from_secs(0))
.as_secs() as i64,
}
}
}

/// # Safety
/// - `s.ptr` must be a pointer previously returned by `FFIString::new` or compatible.
/// - It must not be used after this call.
Expand Down
107 changes: 0 additions & 107 deletions dash-spv-ffi/tests/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ mod tests {
use key_wallet_ffi::FFINetwork;
use serial_test::serial;
use std::ffi::CString;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex};
use tempfile::TempDir;

Expand All @@ -14,25 +13,6 @@ mod tests {
last_progress: Arc<Mutex<f64>>,
}

extern "C" fn _test_progress_callback(
progress: f64,
_message: *const std::os::raw::c_char,
user_data: *mut c_void,
) {
let data = unsafe { &*(user_data as *const _TestCallbackData) };
*data.progress_called.lock().unwrap() = true;
*data.last_progress.lock().unwrap() = progress;
}

extern "C" fn _test_completion_callback(
_success: bool,
_error: *const std::os::raw::c_char,
user_data: *mut c_void,
) {
let data = unsafe { &*(user_data as *const _TestCallbackData) };
*data.completion_called.lock().unwrap() = true;
}

fn create_test_config() -> (*mut FFIClientConfig, TempDir) {
let temp_dir = TempDir::new().unwrap();
let config = dash_spv_ffi_config_new(FFINetwork::Regtest);
Expand Down Expand Up @@ -98,91 +78,4 @@ mod tests {
assert!(progress.is_null());
}
}

#[test]
#[serial]
fn test_sync_progress() {
unsafe {
let (config, _temp_dir) = create_test_config();
let client = dash_spv_ffi_client_new(config);

let progress = dash_spv_ffi_client_get_sync_progress(client);
if !progress.is_null() {
let _progress_ref = &*progress;
// header_height and filter_header_height are u32, always >= 0
dash_spv_ffi_sync_progress_destroy(progress);
}

dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);
}
}

#[test]
#[serial]
fn test_client_stats() {
unsafe {
let (config, _temp_dir) = create_test_config();
let client = dash_spv_ffi_client_new(config);

dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);
}
}

#[test]
#[serial]
#[ignore]
fn test_sync_diagnostic() {
unsafe {
// Allow running this test only when explicitly enabled
if std::env::var("RUST_DASH_FFI_RUN_NETWORK_TESTS").unwrap_or_default() != "1" {
println!(
"Skipping test_sync_diagnostic (set RUST_DASH_FFI_RUN_NETWORK_TESTS=1 to run)"
);
return;
}

// Create testnet config for the diagnostic test
let config = dash_spv_ffi_config_testnet();
let temp_dir = TempDir::new().unwrap();
let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap();
dash_spv_ffi_config_set_data_dir(config, path.as_ptr());

// Create client
let client = dash_spv_ffi_client_new(config);
assert!(!client.is_null(), "Failed to create client");

// Start the client
let start_result = dash_spv_ffi_client_start(client);
if start_result != FFIErrorCode::Success as i32 {
println!("Warning: Failed to start client, error code: {}", start_result);
let error = dash_spv_ffi_get_last_error();
if !error.is_null() {
let error_str = std::ffi::CStr::from_ptr(error);
println!("Error message: {:?}", error_str);
}
}

// Run the diagnostic sync test
println!("Running sync diagnostic test...");
let test_result = client_test_sync(&*client);

if test_result == FFIErrorCode::Success as i32 {
println!("✅ Sync test passed!");
} else {
println!("❌ Sync test failed with error code: {}", test_result);
let error = dash_spv_ffi_get_last_error();
if !error.is_null() {
let error_str = std::ffi::CStr::from_ptr(error);
println!("Error message: {:?}", error_str);
}
}

// Stop and cleanup
let _stop_result = dash_spv_ffi_client_stop(client);
dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);
}
}
}
Loading
Loading