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
69 changes: 67 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**: 57
**Total Functions**: 61
Copy link
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

Function count updated: please verify it’s auto-generated, not hand-edited.

Ensure the generator reflects 61 functions and that the header and doc stay in lockstep.


🏁 Script executed:

#!/bin/bash
# Count public FFI functions in header vs documented count
DOC_CNT=$(rg -n '^\\*\\*Total Functions\\*\\*:\\s*(\\d+)$' -or '$1' dash-spv-ffi/FFI_API.md)
HDR_CNT=$(rg -nP '^\s*(?:int32_t|bool|void|struct\s+FFIArray|FFIClientConfig\s*\*|FFINetwork\b|const\s+char\s*\*)\s+dash_spv_ffi_' dash-spv-ffi/include/dash_spv_ffi.h | wc -l | tr -d ' ')
echo "Doc: $DOC_CNT, Header: $HDR_CNT"
test "$DOC_CNT" = "$HDR_CNT" || { echo "Mismatch"; exit 1; }

Length of output: 384


🏁 Script executed:

#!/bin/bash
# Correctly extract documented total functions
DOC_CNT=$(rg -P -o '^\*\*Total Functions\*\*:\s*(\d+)$' -r '$1' dash-spv-ffi/FFI_API.md)
# Count public FFI functions in header
HDR_CNT=$(rg -P '^\s*(?:int32_t|bool|void|struct\s+FFIArray|FFIClientConfig\s*\*|FFINetwork\b|const\s+char\s*\*)\s+dash_spv_ffi_' -c dash-spv-ffi/include/dash_spv_ffi.h)
echo "Doc: $DOC_CNT, Header: $HDR_CNT"
if [ "$DOC_CNT" = "$HDR_CNT" ]; then
  echo "Match"
else
  echo "Mismatch"
fi

Length of output: 360


Sync FFI_API.md function count with header: Documented total (61) doesn’t match actual public FFI functions in dash-spv-ffi/include/dash_spv_ffi.h (46). Re-run the API doc generator (not manual edit) and commit the updated FFI_API.md so counts stay in lockstep.

🤖 Prompt for AI Agents
In dash-spv-ffi/FFI_API.md around line 7, the documented total functions (61) is
out of sync with the actual public FFI functions in
dash-spv-ffi/include/dash_spv_ffi.h (46); re-run the API documentation generator
(do not manually edit the count) pointing it at
dash-spv-ffi/include/dash_spv_ffi.h so the generator recomputes the total and
updates FFI_API.md, verify the generated total matches the header’s exported
functions, and commit the regenerated FFI_API.md to the branch.


## Table of Contents

Expand Down Expand Up @@ -132,11 +132,15 @@ Functions: 2

### Utility Functions

Functions: 11
Functions: 15

| Function | Description | Module |
|----------|-------------|--------|
| `dash_spv_ffi_array_destroy` | No description | types |
| `dash_spv_ffi_checkpoint_before_height` | Get the last checkpoint at or before a given height | checkpoints |
| `dash_spv_ffi_checkpoint_before_timestamp` | Get the last checkpoint at or before a given UNIX timestamp (seconds) | checkpoints |
| `dash_spv_ffi_checkpoint_latest` | Get the latest checkpoint for the given network | checkpoints |
| `dash_spv_ffi_checkpoints_between_heights` | Get all checkpoints between two heights (inclusive) | checkpoints |
| `dash_spv_ffi_client_get_stats` | No description | client |
| `dash_spv_ffi_client_get_wallet_manager` | Get the wallet manager from the SPV client Returns an opaque pointer to FFIW... | client |
| `dash_spv_ffi_client_record_send` | No description | client |
Expand Down Expand Up @@ -826,6 +830,67 @@ dash_spv_ffi_array_destroy(arr: *mut FFIArray) -> ()

---

#### `dash_spv_ffi_checkpoint_before_height`

```c
dash_spv_ffi_checkpoint_before_height(network: FFINetwork, height: u32, out_height: *mut u32, out_hash: *mut u8, // expects at least 32 bytes) -> i32
```

**Description:**
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.

**Safety:**
- `out_height` must be a valid pointer to a `u32`. - `out_hash` must point to at least 32 writable bytes.

**Module:** `checkpoints`

---

#### `dash_spv_ffi_checkpoint_before_timestamp`

```c
dash_spv_ffi_checkpoint_before_timestamp(network: FFINetwork, timestamp: u32, out_height: *mut u32, out_hash: *mut u8, // expects at least 32 bytes) -> i32
```

**Description:**
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.

**Safety:**
- `out_height` must be a valid pointer to a `u32`. - `out_hash` must point to at least 32 writable bytes.

**Module:** `checkpoints`

---

#### `dash_spv_ffi_checkpoint_latest`

```c
dash_spv_ffi_checkpoint_latest(network: FFINetwork, out_height: *mut u32, out_hash: *mut u8, // expects at least 32 bytes) -> i32
```

**Description:**
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.

**Safety:**
- `out_height` must be a valid pointer to a `u32`. - `out_hash` must point to at least 32 writable bytes.

**Module:** `checkpoints`

---

#### `dash_spv_ffi_checkpoints_between_heights`

```c
dash_spv_ffi_checkpoints_between_heights(network: FFINetwork, start_height: u32, end_height: u32,) -> FFIArray
```

**Description:**
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.

**Module:** `checkpoints`

---

#### `dash_spv_ffi_client_get_stats`

```c
Expand Down
46 changes: 31 additions & 15 deletions dash-spv-ffi/include/dash_spv_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,6 @@ typedef struct FFIEventCallbacks {
void *user_data;
} FFIEventCallbacks;

/**
* Handle for Core SDK that can be passed to Platform SDK
*/
typedef struct CoreSDKHandle {
struct FFIDashSpvClient *client;
} CoreSDKHandle;

/**
* FFIResult type for error handling
*/
typedef struct FFIResult {
int32_t error_code;
const char *error_message;
} FFIResult;

/**
* FFI-safe array that transfers ownership of memory to the C caller.
*
Expand All @@ -163,6 +148,21 @@ typedef struct FFIArray {
uintptr_t elem_align;
} FFIArray;

/**
* Handle for Core SDK that can be passed to Platform SDK
*/
typedef struct CoreSDKHandle {
struct FFIDashSpvClient *client;
} CoreSDKHandle;

/**
* FFIResult type for error handling
*/
typedef struct FFIResult {
int32_t error_code;
const char *error_message;
} FFIResult;

/**
* FFI-safe representation of an unconfirmed transaction
*
Expand Down Expand Up @@ -541,6 +541,22 @@ 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);

Comment on lines +556 to +559
Copy link
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

Header lacks FFICheckpoint definition used by the returned FFIArray.

C consumers cannot interpret elements without a typedef struct FFICheckpoint. Ensure cbindgen exports it.

Apply in Rust (so cbindgen emits the type):

-/// FFI representation of a checkpoint (height + block hash)
+/// FFI representation of a checkpoint (height + block hash)
+/// cbindgen:export
 #[repr(C)]
 pub struct FFICheckpoint {
     pub height: u32,
     pub block_hash: [u8; 32],
 }

Run to confirm the struct appears in the generated header:


🏁 Script executed:

#!/bin/bash
rg -nP 'typedef\s+struct\s+FFICheckpoint' dash-spv-ffi/include/dash_spv_ffi.h -C2

Length of output: 83


🏁 Script executed:

#!/bin/bash
# Check for FFICheckpoint in the generated C header
echo "Header occurrences:"
rg -n 'FFICheckpoint' dash-spv-ffi/include/dash_spv_ffi.h -C2

# Check Rust source for the FFICheckpoint definition and cbindgen annotations
echo "Rust source occurrences:"
rg -nP 'struct\s+FFICheckpoint' -C2 dash-spv-ffi/src/**/*.rs

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Search entire repository for any mentions of FFICheckpoint
echo "Occurrences of 'FFICheckpoint' in repo:"
rg -n 'FFICheckpoint' -C2 .

# Search for cbindgen annotations in Rust source
echo "Occurrences of 'cbindgen' annotations in Rust files:"
rg -n 'cbindgen' -C2 --type=rs

Length of output: 2722


Add cbindgen export for FFICheckpoint in Rust
Header lacks FFICheckpoint typedef; annotate struct so cbindgen generates it:

/// FFI representation of a checkpoint (height + block hash)
+/// cbindgen:export
#[repr(C)]
pub struct FFICheckpoint {
    pub height: u32,
    pub block_hash: [u8; 32],
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 556 to 559: the header lacks
a typedef for FFICheckpoint because the corresponding Rust struct wasn't
exported for cbindgen; update the Rust definition to be a public, C-compatible
struct (add pub, #[repr(C)], and derive Copy/Clone) and ensure it is visible to
cbindgen (keep it in a module cbindgen scans or add a cfg_attr if you use
conditional export) so cbindgen will generate the FFICheckpoint typedef in the C
header.

const char *dash_spv_ffi_get_last_error(void);

void dash_spv_ffi_clear_error(void);
Expand Down
168 changes: 168 additions & 0 deletions dash-spv-ffi/src/checkpoints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
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 key_wallet_ffi::FFINetwork;

/// FFI representation of a checkpoint (height + block hash)
#[repr(C)]
pub struct FFICheckpoint {
pub height: u32,
pub block_hash: [u8; 32],
}
Comment on lines +7 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Export FFICheckpoint to C headers via cbindgen.

Without explicit export, cbindgen may omit this struct; C consumers won’t know the element layout of FFIArray.

Apply:

-/// FFI representation of a checkpoint (height + block hash)
+/// FFI representation of a checkpoint (height + block hash)
+/// cbindgen:export
 #[repr(C)]
 pub struct FFICheckpoint {
     pub height: u32,
     pub block_hash: [u8; 32],
 }
📝 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
/// FFI representation of a checkpoint (height + block hash)
#[repr(C)]
pub struct FFICheckpoint {
pub height: u32,
pub block_hash: [u8; 32],
}
/// FFI representation of a checkpoint (height + block hash)
/// cbindgen:export
#[repr(C)]
pub struct FFICheckpoint {
pub height: u32,
pub block_hash: [u8; 32],
}
🤖 Prompt for AI Agents
In dash-spv-ffi/src/checkpoints.rs around lines 7–12, the FFICheckpoint struct
is public and repr(C) but cbindgen may still omit it unless it is explicitly
exported in the crate public API; update the file to make the type
cbindgen-visible by deriving Copy and Clone for safe FFI value semantics, add a
conditional export attribute for header generation (e.g. #[cfg_attr(feature =
"cbindgen", export_name = "FFICheckpoint")]) and ensure the type is re-exported
from the crate root (pub use crate::checkpoints::FFICheckpoint;) so cbindgen
sees and emits the struct layout in the generated C headers.


fn manager_for_network(network: FFINetwork) -> Result<CheckpointManager, String> {
let net: Network = network.into();
match net {
Network::Dash => Ok(CheckpointManager::new(mainnet_checkpoints())),
Network::Testnet => Ok(CheckpointManager::new(testnet_checkpoints())),
_ => Err("Checkpoints are only available for Dash and Testnet".to_string()),
}
}
Comment on lines +1 to +21
Copy link
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

Add unit and C tests for new FFI endpoints.

Per repo guidelines, add Rust unit tests and C tests covering success/edge cases for all four functions.

I can scaffold tests (Rust + C) for mainnet/testnet networks and error paths—want me to open a follow-up patch?


🏁 Script executed:

#!/bin/bash
# Quick audit for tests referencing new symbols
rg -nP 'checkpoint_(latest|before_height|before_timestamp)|checkpoints_between_heights' dash-spv-ffi/tests

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -eux

# List all test files in dash-spv-ffi/tests
echo "=== Test files ==="
find dash-spv-ffi/tests -type f

# Search for any occurrences of 'checkpoint' in those test files
echo "=== Occurrences of 'checkpoint' in tests ==="
rg -n 'checkpoint' dash-spv-ffi/tests

Length of output: 1490


Add tests for new checkpoint FFI endpoints. Add Rust unit tests in dash-spv-ffi/tests/unit covering all four functions (checkpoint_latest, checkpoint_before_height, checkpoint_before_timestamp, checkpoints_between_heights) with success and edge cases, and add corresponding C tests in dash-spv-ffi/tests/c_tests.

🤖 Prompt for AI Agents
In dash-spv-ffi/src/checkpoints.rs around lines 1 to 21, add comprehensive tests
for the new checkpoint FFI: create Rust unit tests in dash-spv-ffi/tests/unit
that exercise checkpoint_latest, checkpoint_before_height,
checkpoint_before_timestamp, and checkpoints_between_heights for both Dash and
Testnet covering normal success cases and edge cases (no checkpoint found,
boundary heights/timestamps, empty range), validate returned FFICheckpoint data,
and assert correct error handling/FFI error codes and set_last_error behavior;
also add corresponding C integration tests in dash-spv-ffi/tests/c_tests that
call the FFI functions from C, mirror the same success and edge cases, validate
returned structures and error codes/messages, and ensure tests run under cargo
test (and the project’s C test harness) so the new endpoints are fully exercised
across both Rust unit and C integration layers.


/// 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.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_latest(
network: FFINetwork,
out_height: *mut u32,
out_hash: *mut u8, // expects at least 32 bytes
) -> i32 {
if out_height.is_null() || out_hash.is_null() {
set_last_error("Null output pointer provided");
return FFIErrorCode::NullPointer as i32;
}
let mgr = match manager_for_network(network) {
Ok(m) => m,
Err(e) => {
set_last_error(&e);
return FFIErrorCode::InvalidArgument as i32;
}
};
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);
}
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoints available for network");
FFIErrorCode::NotImplemented as i32
}
}

/// 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.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_before_height(
network: FFINetwork,
height: u32,
out_height: *mut u32,
out_hash: *mut u8, // expects at least 32 bytes
) -> i32 {
if out_height.is_null() || out_hash.is_null() {
set_last_error("Null output pointer provided");
return FFIErrorCode::NullPointer as i32;
}
let mgr = match manager_for_network(network) {
Ok(m) => m,
Err(e) => {
set_last_error(&e);
return FFIErrorCode::InvalidArgument as i32;
}
};
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);
}
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoint at or before given height");
FFIErrorCode::ValidationError as i32
}
}

/// 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.
#[no_mangle]
pub extern "C" fn dash_spv_ffi_checkpoint_before_timestamp(
network: FFINetwork,
timestamp: u32,
out_height: *mut u32,
out_hash: *mut u8, // expects at least 32 bytes
) -> i32 {
if out_height.is_null() || out_hash.is_null() {
set_last_error("Null output pointer provided");
return FFIErrorCode::NullPointer as i32;
}
let mgr = match manager_for_network(network) {
Ok(m) => m,
Err(e) => {
set_last_error(&e);
return FFIErrorCode::InvalidArgument as i32;
}
};
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);
}
FFIErrorCode::Success as i32
} else {
set_last_error("No checkpoint at or before given 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>(),
}
}
}
}
2 changes: 2 additions & 0 deletions dash-spv-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod callbacks;
pub mod client;
pub mod config;
pub mod checkpoints;
pub mod error;
pub mod platform_integration;
pub mod types;
Expand All @@ -9,6 +10,7 @@ pub mod utils;
pub use callbacks::*;
pub use client::*;
pub use config::*;
pub use checkpoints::*;
pub use error::*;
pub use platform_integration::*;
pub use types::*;
Expand Down
Loading