-
Notifications
You must be signed in to change notification settings - Fork 10
feat: add functions for checkpoint retrieval in FFI API #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
| * | ||
|
|
@@ -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 | ||
| * | ||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainHeader lacks FFICheckpoint definition used by the returned FFIArray. C consumers cannot interpret elements without a 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 -C2Length 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/**/*.rsLength 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=rsLength of output: 2722 Add cbindgen export for FFICheckpoint in Rust /// 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 |
||
| const char *dash_spv_ffi_get_last_error(void); | ||
|
|
||
| void dash_spv_ffi_clear_error(void); | ||
|
|
||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdd 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/testsLength 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/testsLength 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 |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// 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>(), | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
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:
Length of output: 384
🏁 Script executed:
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