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
38 changes: 36 additions & 2 deletions dash-spv-ffi/FFI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I

**Auto-generated**: This documentation is automatically generated from the source code. Do not edit manually.

**Total Functions**: 61
**Total Functions**: 63

## Table of Contents

Expand Down Expand Up @@ -34,10 +34,11 @@ Functions: 4

### Configuration

Functions: 23
Functions: 25

| Function | Description | Module |
|----------|-------------|--------|
| `dash_spv_ffi_client_update_config` | Update the running client's configuration | client |
| `dash_spv_ffi_config_add_peer` | Adds a peer address to the configuration # Safety - `config` must be a valid... | config |
| `dash_spv_ffi_config_destroy` | Destroys an FFIClientConfig and frees its memory # Safety - `config` must be... | config |
| `dash_spv_ffi_config_get_data_dir` | Gets the data directory path from the configuration # Safety - `config` must... | config |
Expand All @@ -49,6 +50,7 @@ Functions: 23
| `dash_spv_ffi_config_set_data_dir` | Sets the data directory for storing blockchain data # Safety - `config` must... | config |
| `dash_spv_ffi_config_set_fetch_mempool_transactions` | Sets whether to fetch full mempool transaction data # Safety - `config` must... | config |
| `dash_spv_ffi_config_set_filter_load` | Sets whether to load bloom filters # Safety - `config` must be a valid point... | config |
| `dash_spv_ffi_config_set_masternode_sync_enabled` | Enables or disables masternode synchronization # Safety - `config` must be a... | config |
| `dash_spv_ffi_config_set_max_mempool_transactions` | Sets the maximum number of mempool transactions to track # Safety - `config`... | config |
| `dash_spv_ffi_config_set_max_peers` | Sets the maximum number of peers to connect to # Safety - `config` must be a... | config |
| `dash_spv_ffi_config_set_mempool_strategy` | Sets the mempool synchronization strategy # Safety - `config` must be a vali... | config |
Expand Down Expand Up @@ -198,6 +200,22 @@ dash_spv_ffi_client_stop(client: *mut FFIDashSpvClient) -> i32

### Configuration - Detailed

#### `dash_spv_ffi_client_update_config`

```c
dash_spv_ffi_client_update_config(client: *mut FFIDashSpvClient, config: *const FFIClientConfig,) -> i32
```

**Description:**
Update the running client's configuration. # Safety - `client` must be a valid pointer to an `FFIDashSpvClient`. - `config` must be a valid pointer to an `FFIClientConfig`. - The network in `config` must match the client's network; changing networks at runtime is not supported.

**Safety:**
- `client` must be a valid pointer to an `FFIDashSpvClient`. - `config` must be a valid pointer to an `FFIClientConfig`. - The network in `config` must match the client's network; changing networks at runtime is not supported.

**Module:** `client`

---

#### `dash_spv_ffi_config_add_peer`

```c
Expand Down Expand Up @@ -362,6 +380,22 @@ Sets whether to load bloom filters # Safety - `config` must be a valid pointer

---

#### `dash_spv_ffi_config_set_masternode_sync_enabled`

```c
dash_spv_ffi_config_set_masternode_sync_enabled(config: *mut FFIClientConfig, enable: bool,) -> i32
```

**Description:**
Enables or disables masternode synchronization # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - The caller must ensure the config pointer remains valid for the duration of this call

**Safety:**
- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - The caller must ensure the config pointer remains valid for the duration of this call

**Module:** `config`

---

#### `dash_spv_ffi_config_set_max_mempool_transactions`

```c
Expand Down
118 changes: 83 additions & 35 deletions dash-spv-ffi/include/dash_spv_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ typedef enum FFIValidationMode {
*/
typedef struct FFIDashSpvClient FFIDashSpvClient;

/**
* FFI-safe array that transfers ownership of memory to the C caller.
*
* # Safety
*
* This struct represents memory that has been allocated by Rust but ownership
* has been transferred to the C caller. The caller is responsible for:
* - Not accessing the memory after it has been freed
* - Calling `dash_spv_ffi_array_destroy` to properly deallocate the memory
* - Ensuring the data, len, and capacity fields remain consistent
*/
typedef struct FFIArray {
void *data;
uintptr_t len;
uintptr_t capacity;
uintptr_t elem_size;
uintptr_t elem_align;
} FFIArray;

typedef ClientConfig FFIClientConfig;

typedef struct FFIString {
Expand Down Expand Up @@ -129,25 +148,6 @@ typedef struct FFIEventCallbacks {
void *user_data;
} FFIEventCallbacks;

/**
* FFI-safe array that transfers ownership of memory to the C caller.
*
* # Safety
*
* This struct represents memory that has been allocated by Rust but ownership
* has been transferred to the C caller. The caller is responsible for:
* - Not accessing the memory after it has been freed
* - Calling `dash_spv_ffi_array_destroy` to properly deallocate the memory
* - Ensuring the data, len, and capacity fields remain consistent
*/
typedef struct FFIArray {
void *data;
uintptr_t len;
uintptr_t capacity;
uintptr_t elem_size;
uintptr_t elem_align;
} FFIArray;

/**
* Handle for Core SDK that can be passed to Platform SDK
*/
Expand Down Expand Up @@ -196,8 +196,62 @@ typedef struct FFIUnconfirmedTransaction {
uintptr_t addresses_len;
} FFIUnconfirmedTransaction;

/**
* Get the latest checkpoint for the given network.
*
* # Safety
* - `out_height` must be a valid pointer to a `u32`.
* - `out_hash` must point to at least 32 writable bytes.
*/
int32_t dash_spv_ffi_checkpoint_latest(FFINetwork network, uint32_t *out_height, uint8_t *out_hash);

/**
* Get the last checkpoint at or before a given height.
*
* # Safety
* - `out_height` must be a valid pointer to a `u32`.
* - `out_hash` must point to at least 32 writable bytes.
*/
int32_t dash_spv_ffi_checkpoint_before_height(FFINetwork network,
uint32_t height,
uint32_t *out_height,
uint8_t *out_hash);

/**
* Get the last checkpoint at or before a given UNIX timestamp (seconds).
*
* # Safety
* - `out_height` must be a valid pointer to a `u32`.
* - `out_hash` must point to at least 32 writable bytes.
*/
int32_t dash_spv_ffi_checkpoint_before_timestamp(FFINetwork network,
uint32_t timestamp,
uint32_t *out_height,
uint8_t *out_hash);

/**
* 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.
*/
struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network,
uint32_t start_height,
uint32_t end_height);

struct FFIDashSpvClient *dash_spv_ffi_client_new(const FFIClientConfig *config);

/**
* Update the running client's configuration.
*
* # Safety
* - `client` must be a valid pointer to an `FFIDashSpvClient`.
* - `config` must be a valid pointer to an `FFIClientConfig`.
* - The network in `config` must match the client's network; changing networks at runtime is not supported.
*/
int32_t dash_spv_ffi_client_update_config(struct FFIDashSpvClient *client,
const FFIClientConfig *config);

Comment on lines +244 to +254

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Update-config API: document blocking behavior and enforce network invariants

Callers need to know this is synchronous (blocks on runtime). Also please verify a hard check rejects network changes at this boundary.

Doc tweak:

  * - `client` must be a valid pointer to an `FFIDashSpvClient`.
  * - `config` must be a valid pointer to an `FFIClientConfig`.
- * - The network in `config` must match the client's network; changing networks at runtime is not supported.
+ * - The network in `config` must match the client's network; changing networks at runtime is not supported.
+ * - This call is synchronous and blocks until the reconfiguration is applied; avoid calling on UI threads.

To verify the invariant is enforced, run:

Also confirm unit tests exist for both new FFI functions under dash-spv-ffi/tests.

Finally, regenerate this header and sync to swift-dash-core-sdk (./sync-headers.sh).


🏁 Script executed:

#!/bin/bash
# Ensure update_config rejects network changes (search implementations)
rg -n -C3 'update_config\s*\(' dash-spv*/**/*.rs
rg -n -C3 'network' dash-spv*/**/client*.rs

Length of output: 5280


🏁 Script executed:

rg -n 'dash_spv_ffi_client_update_config|dash_spv_ffi_config_set_masternode_sync_enabled' dash-spv-ffi/tests

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Locate DashSpvClient::update_config implementation in dash-spv crate
rg -n 'update_config(' -C5 dash-spv/src

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Search for the DashSpvClient::update_config method definition in dash-spv crate
rg -n 'fn update_config' -C5 dash-spv/src

Length of output: 897


Update header docs and add missing FFI tests

  • In dash-spv-ffi/include/dash_spv_ffi.h, extend the doc comment on dash_spv_ffi_client_update_config to note that it blocks synchronously on the runtime and should not be called on UI threads:
 * - The network in `config` must match the client's network; changing networks at runtime is not supported.
+ * - This call is synchronous and blocks until the reconfiguration is applied; avoid calling on UI threads.
  • Add unit tests in dash-spv-ffi/tests to cover both dash_spv_ffi_client_update_config (and verify it rejects network changes) and dash_spv_ffi_config_set_masternode_sync_enabled.
  • Regenerate the header and sync to swift-dash-core-sdk via ./sync-headers.sh.
📝 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
/**
* Update the running client's configuration.
*
* # Safety
* - `client` must be a valid pointer to an `FFIDashSpvClient`.
* - `config` must be a valid pointer to an `FFIClientConfig`.
* - The network in `config` must match the client's network; changing networks at runtime is not supported.
*/
int32_t dash_spv_ffi_client_update_config(struct FFIDashSpvClient *client,
const FFIClientConfig *config);
/**
* Update the running client's configuration.
*
* # Safety
* - `client` must be a valid pointer to an `FFIDashSpvClient`.
* - `config` must be a valid pointer to an `FFIClientConfig`.
* - The network in `config` must match the client's network; changing networks at runtime is not supported.
* - This call is synchronous and blocks until the reconfiguration is applied; avoid calling on UI threads.
*/
int32_t dash_spv_ffi_client_update_config(struct FFIDashSpvClient *client,
const FFIClientConfig *config);
🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 244 to 254, the doc comment
for dash_spv_ffi_client_update_config needs to be extended to state that the
call blocks synchronously on the runtime and must not be invoked from UI
threads; update the comment accordingly to warn callers about blocking behavior
and the restriction on changing networks at runtime. Then add unit tests under
dash-spv-ffi/tests that (1) exercise dash_spv_ffi_client_update_config to ensure
it accepts valid config updates but rejects network changes, and (2) test
dash_spv_ffi_config_set_masternode_sync_enabled for correct behavior; ensure
tests cover error paths and return codes. Finally regenerate headers and
propagate them to swift-dash-core-sdk by running ./sync-headers.sh and commit
the updated header and generated artifacts.

int32_t dash_spv_ffi_client_start(struct FFIDashSpvClient *client);

int32_t dash_spv_ffi_client_stop(struct FFIDashSpvClient *client);
Expand Down Expand Up @@ -414,6 +468,16 @@ int32_t dash_spv_ffi_config_set_relay_transactions(FFIClientConfig *config,
int32_t dash_spv_ffi_config_set_filter_load(FFIClientConfig *config,
bool load_filters);

/**
* Enables or disables masternode synchronization
*
* # Safety
* - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet
* - The caller must ensure the config pointer remains valid for the duration of this call
*/
int32_t dash_spv_ffi_config_set_masternode_sync_enabled(FFIClientConfig *config,
bool enable);

/**
* Gets the network type from the configuration
*
Expand Down Expand Up @@ -541,22 +605,6 @@ int32_t dash_spv_ffi_config_set_start_from_height(FFIClientConfig *config,
int32_t dash_spv_ffi_config_set_wallet_creation_time(FFIClientConfig *config,
uint32_t timestamp);

int32_t dash_spv_ffi_checkpoint_latest(FFINetwork network, uint32_t *out_height, uint8_t *out_hash);

int32_t dash_spv_ffi_checkpoint_before_height(FFINetwork network,
uint32_t height,
uint32_t *out_height,
uint8_t *out_hash);

int32_t dash_spv_ffi_checkpoint_before_timestamp(FFINetwork network,
uint32_t timestamp,
uint32_t *out_height,
uint8_t *out_hash);

struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network,
uint32_t start_height,
uint32_t end_height);

const char *dash_spv_ffi_get_last_error(void);

void dash_spv_ffi_clear_error(void);
Expand Down
32 changes: 13 additions & 19 deletions dash-spv-ffi/src/checkpoints.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{set_last_error, FFIArray, FFIErrorCode};
use dash_spv::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, CheckpointManager};
use dashcore::Network;
use dashcore::hashes::Hash;
use dashcore::Network;
use key_wallet_ffi::FFINetwork;

/// FFI representation of a checkpoint (height + block hash)
Expand All @@ -26,7 +26,7 @@ fn manager_for_network(network: FFINetwork) -> Result<CheckpointManager, String>
/// - `out_height` must be a valid pointer to a `u32`.
/// - `out_hash` must point to at least 32 writable bytes.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_latest(
pub unsafe extern "C" fn dash_spv_ffi_checkpoint_latest(
network: FFINetwork,
out_height: *mut u32,
out_hash: *mut u8, // expects at least 32 bytes
Expand All @@ -43,11 +43,9 @@ pub extern "C" fn dash_spv_ffi_checkpoint_latest(
}
};
if let Some(cp) = mgr.last_checkpoint() {
unsafe {
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
}
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoints available for network");
Expand All @@ -61,7 +59,7 @@ pub extern "C" fn dash_spv_ffi_checkpoint_latest(
/// - `out_height` must be a valid pointer to a `u32`.
/// - `out_hash` must point to at least 32 writable bytes.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_before_height(
pub unsafe extern "C" fn dash_spv_ffi_checkpoint_before_height(
network: FFINetwork,
height: u32,
out_height: *mut u32,
Expand All @@ -79,11 +77,9 @@ pub extern "C" fn dash_spv_ffi_checkpoint_before_height(
}
};
if let Some(cp) = mgr.last_checkpoint_before_height(height) {
unsafe {
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
}
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoint at or before given height");
Expand All @@ -97,7 +93,7 @@ pub extern "C" fn dash_spv_ffi_checkpoint_before_height(
/// - `out_height` must be a valid pointer to a `u32`.
/// - `out_hash` must point to at least 32 writable bytes.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_before_timestamp(
pub unsafe extern "C" fn dash_spv_ffi_checkpoint_before_timestamp(
network: FFINetwork,
timestamp: u32,
out_height: *mut u32,
Expand All @@ -115,11 +111,9 @@ pub extern "C" fn dash_spv_ffi_checkpoint_before_timestamp(
}
};
if let Some(cp) = mgr.last_checkpoint_before_timestamp(timestamp) {
unsafe {
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
}
*out_height = cp.height;
let hash = cp.block_hash.to_byte_array();
std::ptr::copy_nonoverlapping(hash.as_ptr(), out_hash, 32);
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoint at or before given timestamp");
Expand Down
35 changes: 35 additions & 0 deletions dash-spv-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,41 @@ impl FFIDashSpvClient {
}
}

/// Update the running client's configuration.
///
/// # Safety
/// - `client` must be a valid pointer to an `FFIDashSpvClient`.
/// - `config` must be a valid pointer to an `FFIClientConfig`.
/// - The network in `config` must match the client's network; changing networks at runtime is not supported.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_update_config(
client: *mut FFIDashSpvClient,
config: *const FFIClientConfig,
) -> i32 {
null_check!(client);
null_check!(config);

let client = &(*client);
let new_config = (&*config).clone_inner();

let result = client.runtime.block_on(async {
let mut guard = client.inner.lock().unwrap();
if let Some(ref mut spv_client) = *guard {
spv_client.update_config(new_config).await.map_err(|e| e)
} else {
Err(dash_spv::SpvError::Config("Client not initialized".to_string()))
}
});

match result {
Ok(()) => FFIErrorCode::Success as i32,
Err(e) => {
set_last_error(&e.to_string());
FFIErrorCode::from(e) as i32
}
}
}
Comment on lines +342 to +375

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Add a unit test for dash_spv_ffi_client_update_config.

Per prior guidance, each new FFI should have a unit test under dash-spv-ffi/tests. Validate success path and “client not initialized”.

Run to check for tests:


🏁 Script executed:

#!/bin/bash
rg -nP --glob 'dash-spv-ffi/tests/**' '(client_update_config|dash_spv_ffi_client_update_config)'

Length of output: 98


Add Rust and C tests for dash_spv_ffi_client_update_config
Create a Rust unit test in dash-spv-ffi/tests/unit and a C test in dash-spv-ffi/tests/c_tests, each covering the success path and the “client not initialized” error.

🤖 Prompt for AI Agents
In dash-spv-ffi/src/client.rs around lines 342 to 375, add tests: create a Rust
unit test file at dash-spv-ffi/tests/unit/test_update_config.rs that covers (1)
success path — construct an FFIDashSpvClient with a running runtime and an inner
spv_client that will accept update_config, call
dash_spv_ffi_client_update_config and assert it returns FFIErrorCode::Success
and does not set last error, and (2) “client not initialized” — construct an
FFIDashSpvClient whose inner is None, call the function and assert it returns
the mapped error code and that last error contains "Client not initialized".
Also add a C test at dash-spv-ffi/tests/c_tests/test_update_config.c that links
against the built library, exercises the same two scenarios by calling
dash_spv_ffi_client_update_config (one with a properly initialized client
wrapper created via existing FFI helpers or setup helper you add, and one with a
null/empty inner), checks the returned int equals the expected FFIErrorCode
values, and verifies the last error string via the FFI getter; ensure to clean
up resources and make tests deterministic (use mocks or simple stub spv client
that returns Ok or error).


#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_start(client: *mut FFIDashSpvClient) -> i32 {
null_check!(client);
Expand Down
17 changes: 17 additions & 0 deletions dash-spv-ffi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,23 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_filter_load(
FFIErrorCode::Success as i32
}

/// Enables or disables masternode synchronization
///
/// # Safety
/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet
/// - The caller must ensure the config pointer remains valid for the duration of this call
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_config_set_masternode_sync_enabled(
config: *mut FFIClientConfig,
enable: bool,
) -> i32 {
null_check!(config);

let config = &mut (*config).inner;
config.enable_masternodes = enable;
FFIErrorCode::Success as i32
}

/// Gets the network type from the configuration
///
/// # Safety
Expand Down
4 changes: 2 additions & 2 deletions dash-spv-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
pub mod callbacks;
pub mod checkpoints;
pub mod client;
pub mod config;
pub mod checkpoints;
pub mod error;
pub mod platform_integration;
pub mod types;
pub mod utils;

pub use callbacks::*;
pub use checkpoints::*;
pub use client::*;
pub use config::*;
pub use checkpoints::*;
pub use error::*;
pub use platform_integration::*;
pub use types::*;
Expand Down
Loading
Loading