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
427 changes: 6 additions & 421 deletions dash-spv-ffi/FFI_API.md

Large diffs are not rendered by default.

328 changes: 0 additions & 328 deletions dash-spv-ffi/include/dash_spv_ffi.h

Large diffs are not rendered by default.

30 changes: 3 additions & 27 deletions dash-spv-ffi/src/bin/ffi_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ fn main() {
.action(ArgAction::Append)
.help("Peer address host:port (repeatable)"),
)
.arg(
Arg::new("workers")
.long("workers")
.value_parser(clap::value_parser!(u32))
.help("Tokio worker threads (0=auto)"),
)
.arg(
Arg::new("log-level")
.long("log-level")
Expand All @@ -99,12 +93,6 @@ fn main() {
.action(ArgAction::SetTrue)
.help("Disable masternode list synchronization"),
)
.arg(
Arg::new("no-filters")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want to remove this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the ffi function that enables/disables the filters since its not being used in iOS. removed this bcs it is something you can no longer configure using ffi

.long("no-filters")
.action(ArgAction::SetTrue)
.help("Disable compact filter synchronization"),
)
.get_matches();

// Map network
Expand All @@ -115,8 +103,6 @@ fn main() {
_ => FFINetwork::Dash,
};

let disable_filter_sync = matches.get_flag("no-filters");

unsafe {
// Initialize tracing/logging via FFI so `tracing::info!` emits output
let level = matches.get_one::<String>("log-level").map(String::as_str).unwrap_or("info");
Expand All @@ -133,12 +119,6 @@ fn main() {
std::process::exit(1);
}

let _ = dash_spv_ffi_config_set_filter_load(cfg, !disable_filter_sync);

if let Some(workers) = matches.get_one::<u32>("workers") {
let _ = dash_spv_ffi_config_set_worker_threads(cfg, *workers);
}

if let Some(height) = matches.get_one::<u32>("start-height") {
let _ = dash_spv_ffi_config_set_start_from_height(cfg, *height);
}
Expand Down Expand Up @@ -214,13 +194,9 @@ fn main() {
if !prog_ptr.is_null() {
let prog = &*prog_ptr;
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) {
let filters_complete = prog.filter_header_height >= prog.header_height
&& prog.last_synced_filter_height >= prog.filter_header_height;
if headers_done && filters_complete {
Comment on lines 196 to +199
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent a potential hang when filter sync is unavailable.

If filter_sync_available is false, filter heights can stay at 0 while header_height advances, so filters_complete never becomes true and the polling loop can run forever. Gate the condition on filter_sync_available (or otherwise allow completion without filters).

🐛 Proposed fix
-                let filters_complete = prog.filter_header_height >= prog.header_height
-                    && prog.last_synced_filter_height >= prog.filter_header_height;
+                let filters_complete = !prog.filter_sync_available
+                    || (prog.filter_header_height >= prog.header_height
+                        && prog.last_synced_filter_height >= prog.filter_header_height);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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) {
let filters_complete = prog.filter_header_height >= prog.header_height
&& prog.last_synced_filter_height >= prog.filter_header_height;
if headers_done && filters_complete {
let headers_done = SYNC_COMPLETED.load(Ordering::SeqCst);
let filters_complete = !prog.filter_sync_available
|| (prog.filter_header_height >= prog.header_height
&& prog.last_synced_filter_height >= prog.filter_header_height);
if headers_done && filters_complete {
🤖 Prompt for AI Agents
In `@dash-spv-ffi/src/bin/ffi_cli.rs` around lines 196 - 199, The loop's
completion check can hang if filter sync is disabled because filters_complete is
computed unconditionally; update the condition that sets filters_complete to
only require filter heights when filter_sync_available is true (e.g., compute
filters_complete as filter_sync_available && prog.filter_header_height >=
prog.header_height && prog.last_synced_filter_height >=
prog.filter_header_height), and then use that guarded filters_complete together
with SYNC_COMPLETED.load(Ordering::SeqCst) in the if check so the loop can
complete when filters are not available.

dash_spv_ffi_sync_progress_destroy(prog_ptr);
break;
}
Expand Down
77 changes: 0 additions & 77 deletions dash-spv-ffi/src/broadcast.rs

This file was deleted.

42 changes: 1 addition & 41 deletions dash-spv-ffi/src/checkpoints.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{set_last_error, FFIArray, FFIErrorCode};
use crate::{set_last_error, FFIErrorCode};
use dash_spv::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, CheckpointManager};
use dashcore::hashes::Hash;
use dashcore::Network;
Expand Down Expand Up @@ -120,43 +120,3 @@ pub unsafe extern "C" fn dash_spv_ffi_checkpoint_before_timestamp(
FFIErrorCode::ValidationError as i32
}
}

/// Get all checkpoints between two heights (inclusive).
///
/// Returns an `FFIArray` of `FFICheckpoint` items. The caller owns the memory and
/// must free the array buffer using `dash_spv_ffi_array_destroy` when done.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoints_between_heights(
network: FFINetwork,
start_height: u32,
end_height: u32,
) -> FFIArray {
match manager_for_network(network) {
Ok(mgr) => {
// Collect checkpoints within inclusive range
let mut out: Vec<FFICheckpoint> = Vec::new();
for &h in mgr.checkpoint_heights() {
if h >= start_height && h <= end_height {
if let Some(cp) = mgr.get_checkpoint(h) {
out.push(FFICheckpoint {
height: cp.height,
block_hash: cp.block_hash.to_byte_array(),
});
}
}
}
FFIArray::new(out)
}
Err(e) => {
set_last_error(&e);
// Return empty array on error
FFIArray {
data: std::ptr::null_mut(),
len: 0,
capacity: 0,
elem_size: std::mem::size_of::<FFICheckpoint>(),
elem_align: std::mem::align_of::<FFICheckpoint>(),
}
}
}
}
143 changes: 2 additions & 141 deletions dash-spv-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ use dash_spv::storage::DiskStorageManager;
use dash_spv::types::SyncStage;
use dash_spv::DashSpvClient;
use dash_spv::Hash;
use dashcore::Txid;

use futures::future::{AbortHandle, Abortable};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::ffi::CString;
use std::os::raw::{c_char, c_void};
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
Expand Down Expand Up @@ -513,23 +511,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_stop(client: *mut FFIDashSpvClient)
}
}

/// Performs a test synchronization of the SPV client
///
/// # Parameters
/// - `client`: Pointer to an FFIDashSpvClient instance
///
/// # Returns
/// - `0` on success
/// - Negative error code on failure
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer.
/// The caller must ensure that the client pointer is valid.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_test_sync(client: *mut FFIDashSpvClient) -> i32 {
null_check!(client);

let client = &(*client);
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();
Expand Down Expand Up @@ -1033,34 +1015,6 @@ pub unsafe extern "C" fn dash_spv_ffi_client_clear_storage(client: *mut FFIDashS
}
}

/// Check if compact filter sync is currently available.
///
/// # Safety
/// - `client` must be a valid, non-null pointer.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_is_filter_sync_available(
client: *mut FFIDashSpvClient,
) -> bool {
null_check!(client, false);

let client = &(*client);
let inner = client.inner.clone();

client.runtime.block_on(async {
let spv_client = {
let mut guard = inner.lock().unwrap();
match guard.take() {
Some(client) => client,
None => return false,
}
};
let res = spv_client.is_filter_sync_available().await;
let mut guard = inner.lock().unwrap();
*guard = Some(spv_client);
res
})
}

/// Set event callbacks for the client.
///
/// # Safety
Expand Down Expand Up @@ -1144,99 +1098,6 @@ pub unsafe extern "C" fn dash_spv_ffi_sync_progress_destroy(progress: *mut FFISy

// Wallet operations

/// Request a rescan of the blockchain from a given height (not yet implemented).
///
/// # Safety
/// - `client` must be a valid, non-null pointer.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_rescan_blockchain(
client: *mut FFIDashSpvClient,
_from_height: u32,
) -> i32 {
null_check!(client);

let client = &(*client);
let inner = client.inner.clone();

let result: Result<(), dash_spv::SpvError> = client.runtime.block_on(async {
let mut guard = inner.lock().unwrap();
if let Some(ref mut _spv_client) = *guard {
// TODO: rescan_from_height not yet implemented in dash-spv
Err(dash_spv::SpvError::Config("Not implemented".to_string()))
} else {
Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound(
"Client not initialized".to_string(),
)))
}
});

match result {
Ok(_) => FFIErrorCode::Success as i32,
Err(e) => {
set_last_error(&format!("Failed to rescan blockchain: {}", e));
FFIErrorCode::from(e) as i32
}
}
}

/// Record that we attempted to send a transaction by its txid.
///
/// # Safety
/// - `client` and `txid` must be valid, non-null pointers.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_record_send(
client: *mut FFIDashSpvClient,
txid: *const c_char,
) -> i32 {
null_check!(client);
null_check!(txid);

let txid_str = match CStr::from_ptr(txid).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(&format!("Invalid UTF-8 in txid: {}", e));
return FFIErrorCode::InvalidArgument as i32;
}
};

let txid = match Txid::from_str(txid_str) {
Ok(t) => t,
Err(e) => {
set_last_error(&format!("Invalid txid: {}", e));
return FFIErrorCode::InvalidArgument as i32;
}
};

let client = &(*client);
let inner = client.inner.clone();

let result = client.runtime.block_on(async {
let spv_client = {
let mut guard = inner.lock().unwrap();
match guard.take() {
Some(client) => client,
None => {
return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound(
"Client not initialized".to_string(),
)))
}
}
};
let res = spv_client.record_send(txid).await;
let mut guard = inner.lock().unwrap();
*guard = Some(spv_client);
res
});

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

/// Get the wallet manager from the SPV client
///
/// Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying
Expand Down
Loading
Loading