Skip to content

Commit 0937743

Browse files
Merge pull request #102 from dashpay/feat/mempool-bloom-filters-chain-management
Feat/mempool bloom filters chain management
2 parents a725e4d + 26ad93c commit 0937743

File tree

104 files changed

+10579
-40150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+10579
-40150
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi"]
2+
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi", "test-utils"]
33
resolver = "2"
44

55
[workspace.package]

dash-spv-ffi/CLAUDE.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
dash-spv-ffi provides C-compatible FFI bindings for the Dash SPV (Simplified Payment Verification) client. It wraps the Rust dash-spv library to enable usage from C, Swift, and other languages via a stable ABI.
8+
9+
## Build Commands
10+
11+
### Rust Library Build
12+
```bash
13+
# Debug build
14+
cargo build
15+
16+
# Release build (recommended for production)
17+
cargo build --release
18+
19+
# Build for specific iOS targets
20+
cargo build --release --target aarch64-apple-ios
21+
cargo build --release --target aarch64-apple-ios-sim
22+
```
23+
24+
### Header Generation
25+
The C header is auto-generated by the build script. To regenerate manually:
26+
```bash
27+
cbindgen --config cbindgen.toml --crate dash-spv-ffi --output include/dash_spv_ffi.h
28+
```
29+
30+
### Unified SDK Build
31+
For iOS integration with platform-ios:
32+
```bash
33+
# First build dash-spv-ffi for iOS targets (REQUIRED!)
34+
cargo build --release --target aarch64-apple-ios
35+
cargo build --release --target aarch64-apple-ios-sim
36+
37+
# Then build the unified SDK
38+
cd ../../platform-ios/packages/rs-sdk-ffi
39+
./build_ios.sh
40+
41+
# Copy to iOS project
42+
cp -R build/DashUnifiedSDK.xcframework ../../../dashpay-ios/DashPayiOS/Libraries/
43+
```
44+
45+
**Important**: The unified SDK build process (`build_ios.sh`) merges dash-spv-ffi with platform SDK. You MUST build dash-spv-ffi first or changes won't be included!
46+
47+
## Testing
48+
49+
### Rust Tests
50+
```bash
51+
# Run all tests
52+
cargo test
53+
54+
# Run specific test
55+
cargo test test_client_lifecycle
56+
57+
# Run with output
58+
cargo test -- --nocapture
59+
60+
# Run tests with real Dash node (requires DASH_SPV_IP env var)
61+
DASH_SPV_IP=192.168.1.100 cargo test -- --ignored
62+
```
63+
64+
### C Tests
65+
```bash
66+
cd tests/c_tests
67+
68+
# Build and run all tests
69+
make test
70+
71+
# Run specific test
72+
make test_basic && ./test_basic
73+
74+
# Clean build artifacts
75+
make clean
76+
```
77+
78+
## Architecture
79+
80+
### Core Components
81+
82+
**FFI Wrapper Layer** (`src/`):
83+
- `client.rs` - SPV client operations (connect, sync, broadcast)
84+
- `config.rs` - Client configuration (network, peers, validation)
85+
- `wallet.rs` - Wallet operations (addresses, balances, UTXOs)
86+
- `callbacks.rs` - Async callback system for progress/events
87+
- `types.rs` - FFI-safe type conversions
88+
- `error.rs` - Thread-local error handling
89+
- `platform_integration.rs` - Platform SDK integration support
90+
91+
**Key Design Patterns**:
92+
1. **Opaque Pointers**: Complex Rust types are exposed as opaque pointers (`FFIDashSpvClient*`)
93+
2. **Explicit Memory Management**: All FFI types have corresponding `_destroy()` functions
94+
3. **Error Handling**: Uses thread-local storage for error propagation
95+
4. **Callbacks**: Async operations use C function pointers for progress/completion
96+
97+
### FFI Safety Rules
98+
99+
1. **String Handling**:
100+
- Rust strings are returned as `*const c_char` (caller must free with `dash_string_free`)
101+
- Input strings are `*const c_char` (borrowed, not freed)
102+
103+
2. **Memory Ownership**:
104+
- Functions returning pointers transfer ownership (caller must destroy)
105+
- Functions taking pointers borrow (caller retains ownership)
106+
107+
3. **Thread Safety**:
108+
- Client operations are thread-safe
109+
- Callbacks may be invoked from any thread
110+
111+
### Integration with Unified SDK
112+
113+
This crate can be used standalone or as part of the unified SDK:
114+
- **Standalone**: Produces `libdash_spv_ffi.a` with `dash_spv_ffi.h`
115+
- **Unified**: Combined with platform SDK in `DashUnifiedSDK.xcframework`
116+
117+
The unified SDK merges headers and resolves type conflicts between Core and Platform layers.
118+
119+
## Common Development Tasks
120+
121+
### Adding New FFI Functions
122+
1. Implement Rust function in appropriate module with `#[no_mangle] extern "C"`
123+
2. Add cbindgen annotations for complex types
124+
3. Run `cargo build` to regenerate header
125+
4. Add corresponding test in `tests/unit/`
126+
5. Add C test in `tests/c_tests/`
127+
128+
### Debugging FFI Issues
129+
- Check `dash_spv_ffi_get_last_error()` for error details
130+
- Use `RUST_LOG=debug` for verbose logging
131+
- Verify memory management (matching create/destroy calls)
132+
- Test with AddressSanitizer: `RUSTFLAGS="-Z sanitizer=address" cargo test`
133+
134+
### Platform-Specific Builds
135+
- iOS: Use `--target aarch64-apple-ios` or `aarch64-apple-ios-sim`
136+
- Android: Use appropriate NDK target
137+
- Linux/macOS: Default target works
138+
139+
## Dependencies
140+
141+
Key dependencies from Cargo.toml:
142+
- `dash-spv` - Core SPV implementation (local path)
143+
- `dashcore` - Dash protocol types (local path)
144+
- `tokio` - Async runtime
145+
- `cbindgen` - C header generation (build dependency)

dash-spv-ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ tracing = "0.1"
2828
tempfile = "3.8"
2929
serial_test = "3.0"
3030
env_logger = "0.10"
31+
dashcore-test-utils = { path = "../test-utils" }
3132

3233
[build-dependencies]
3334
cbindgen = "0.26"

dash-spv-ffi/include/dash_spv_ffi.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,9 @@ void ffi_dash_spv_release_core_handle(struct CoreSDKHandle *handle);
523523
* - out_pubkey_size must be at least 48 bytes
524524
*/
525525
struct FFIResult ffi_dash_spv_get_quorum_public_key(struct FFIDashSpvClient *client,
526-
uint32_t _quorum_type,
526+
uint32_t quorum_type,
527527
const uint8_t *quorum_hash,
528-
uint32_t _core_chain_locked_height,
528+
uint32_t core_chain_locked_height,
529529
uint8_t *out_pubkey,
530530
uintptr_t out_pubkey_size);
531531

dash-spv-ffi/src/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ struct SyncCallbackData {
100100

101101
/// FFIDashSpvClient structure
102102
pub struct FFIDashSpvClient {
103-
inner: Arc<Mutex<Option<DashSpvClient>>>,
103+
pub(crate) inner: Arc<Mutex<Option<DashSpvClient>>>,
104104
runtime: Arc<Runtime>,
105105
event_callbacks: Arc<Mutex<FFIEventCallbacks>>,
106106
active_threads: Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>,
@@ -157,7 +157,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new(
157157
let config = &(*config);
158158
let runtime = match tokio::runtime::Builder::new_multi_thread()
159159
.thread_name("dash-spv-worker")
160-
.worker_threads(1) // Reduce threads for mobile
160+
.worker_threads(4) // Use 4 threads for better performance on iOS
161161
.enable_all()
162162
.build()
163163
{

dash-spv-ffi/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::sync::Mutex;
77
static LAST_ERROR: Mutex<Option<CString>> = Mutex::new(None);
88

99
#[repr(C)]
10+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1011
pub enum FFIErrorCode {
1112
Success = 0,
1213
NullPointer = 1,

dash-spv-ffi/src/platform_integration.rs

Lines changed: 133 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use crate::{set_last_error, FFIDashSpvClient, FFIErrorCode};
2+
use dashcore::hashes::Hash;
3+
use dashcore::sml::llmq_type::LLMQType;
4+
use dashcore::QuorumHash;
25
use std::os::raw::c_char;
36
use std::ptr;
47

@@ -72,9 +75,9 @@ pub unsafe extern "C" fn ffi_dash_spv_release_core_handle(handle: *mut CoreSDKHa
7275
#[no_mangle]
7376
pub unsafe extern "C" fn ffi_dash_spv_get_quorum_public_key(
7477
client: *mut FFIDashSpvClient,
75-
_quorum_type: u32,
78+
quorum_type: u32,
7679
quorum_hash: *const u8,
77-
_core_chain_locked_height: u32,
80+
core_chain_locked_height: u32,
7881
out_pubkey: *mut u8,
7982
out_pubkey_size: usize,
8083
) -> FFIResult {
@@ -105,12 +108,99 @@ pub unsafe extern "C" fn ffi_dash_spv_get_quorum_public_key(
105108
);
106109
}
107110

108-
// TODO: Implement actual quorum public key retrieval
109-
// For now, return a placeholder error
110-
FFIResult::error(
111-
FFIErrorCode::NotImplemented,
112-
"Quorum public key retrieval not yet implemented",
113-
)
111+
// Get the client reference
112+
let client = &*client;
113+
114+
// Access the inner client through the mutex
115+
let inner_guard = match client.inner.lock() {
116+
Ok(guard) => guard,
117+
Err(_) => {
118+
return FFIResult::error(FFIErrorCode::RuntimeError, "Failed to lock client mutex");
119+
}
120+
};
121+
122+
// Get the SPV client
123+
let spv_client = match inner_guard.as_ref() {
124+
Some(client) => client,
125+
None => {
126+
return FFIResult::error(FFIErrorCode::RuntimeError, "Client not initialized");
127+
}
128+
};
129+
130+
// Read the quorum hash from the input pointer
131+
let quorum_hash_bytes = std::slice::from_raw_parts(quorum_hash, 32);
132+
let mut hash_array = [0u8; 32];
133+
hash_array.copy_from_slice(quorum_hash_bytes);
134+
135+
// Convert quorum type and hash for engine lookup
136+
let llmq_type = match LLMQType::try_from(quorum_type as u8) {
137+
Ok(t) => t,
138+
Err(_) => {
139+
return FFIResult::error(
140+
FFIErrorCode::InvalidArgument,
141+
&format!("Invalid quorum type: {}", quorum_type),
142+
);
143+
}
144+
};
145+
let quorum_hash = QuorumHash::from_byte_array(hash_array);
146+
147+
// Get the masternode list engine directly for efficient access
148+
let engine = match spv_client.masternode_list_engine() {
149+
Some(engine) => engine,
150+
None => {
151+
return FFIResult::error(
152+
FFIErrorCode::RuntimeError,
153+
"Masternode list engine not initialized. Core SDK may still be syncing.",
154+
);
155+
}
156+
};
157+
158+
// Use the global quorum status index for efficient lookup
159+
match engine.quorum_statuses.get(&llmq_type).and_then(|type_map| type_map.get(&quorum_hash)) {
160+
Some((heights, public_key, _status)) => {
161+
// Check if the requested height is one of the heights where this quorum exists
162+
if !heights.contains(&core_chain_locked_height) {
163+
// Quorum exists but not at requested height - provide helpful info
164+
let height_list: Vec<u32> = heights.iter().copied().collect();
165+
return FFIResult::error(
166+
FFIErrorCode::ValidationError,
167+
&format!(
168+
"Quorum type {} with hash {:x} exists but not at height {}. Available at heights: {:?}",
169+
quorum_type, quorum_hash, core_chain_locked_height, height_list
170+
),
171+
);
172+
}
173+
174+
// Copy the public key directly from the global index
175+
let pubkey_ptr = public_key as *const _ as *const u8;
176+
std::ptr::copy_nonoverlapping(pubkey_ptr, out_pubkey, QUORUM_PUBKEY_SIZE);
177+
178+
// Return success
179+
FFIResult {
180+
error_code: 0,
181+
error_message: ptr::null(),
182+
}
183+
}
184+
None => {
185+
// Quorum not found in global index - provide diagnostic info
186+
let total_lists = engine.masternode_lists.len();
187+
let (min_height, max_height) = if total_lists > 0 {
188+
let min = engine.masternode_lists.keys().min().copied().unwrap_or(0);
189+
let max = engine.masternode_lists.keys().max().copied().unwrap_or(0);
190+
(min, max)
191+
} else {
192+
(0, 0)
193+
};
194+
195+
FFIResult::error(
196+
FFIErrorCode::ValidationError,
197+
&format!(
198+
"Quorum not found: type={}, hash={:x}. Core SDK has {} masternode lists ranging from height {} to {}. The quorum may not exist or the Core SDK may still be syncing.",
199+
quorum_type, quorum_hash, total_lists, min_height, max_height
200+
),
201+
)
202+
}
203+
}
114204
}
115205

116206
/// Gets the platform activation height from the Core chain
@@ -135,10 +225,39 @@ pub unsafe extern "C" fn ffi_dash_spv_get_platform_activation_height(
135225
return FFIResult::error(FFIErrorCode::NullPointer, "Null out_height pointer");
136226
}
137227

138-
// TODO: Implement actual platform activation height retrieval
139-
// For now, return a placeholder error
140-
FFIResult::error(
141-
FFIErrorCode::NotImplemented,
142-
"Platform activation height retrieval not yet implemented",
143-
)
228+
// Get the client reference
229+
let client = &*client;
230+
231+
// Access the inner client through the mutex
232+
let inner_guard = match client.inner.lock() {
233+
Ok(guard) => guard,
234+
Err(_) => {
235+
return FFIResult::error(FFIErrorCode::RuntimeError, "Failed to lock client mutex");
236+
}
237+
};
238+
239+
// Get the network from the client config
240+
let height = match inner_guard.as_ref() {
241+
Some(spv_client) => {
242+
// Platform activation heights per network
243+
match spv_client.network() {
244+
dashcore::Network::Dash => 1_888_888, // Mainnet (placeholder - needs verification)
245+
dashcore::Network::Testnet => 1_289_520, // Testnet confirmed height
246+
dashcore::Network::Devnet => 1, // Devnet starts immediately
247+
_ => 0, // Unknown network
248+
}
249+
}
250+
None => {
251+
return FFIResult::error(FFIErrorCode::RuntimeError, "Client not initialized");
252+
}
253+
};
254+
255+
// Set the output value
256+
*out_height = height;
257+
258+
// Return success
259+
FFIResult {
260+
error_code: 0,
261+
error_message: ptr::null(),
262+
}
144263
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Minimal platform integration test to verify FFI functions
2+
3+
use dash_spv_ffi::*;
4+
use std::ptr;
5+
6+
#[test]
7+
fn test_basic_null_checks() {
8+
unsafe {
9+
// Test null pointer handling
10+
let handle = ffi_dash_spv_get_core_handle(ptr::null_mut());
11+
assert!(handle.is_null());
12+
13+
// Test error code
14+
let mut height: u32 = 0;
15+
let result =
16+
ffi_dash_spv_get_platform_activation_height(ptr::null_mut(), &mut height as *mut u32);
17+
assert_eq!(result.error_code, FFIErrorCode::NullPointer as i32);
18+
}
19+
}

0 commit comments

Comments
 (0)