From 97ff4a6d89883e1fc3c3b7885556308d9af3420b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 28 Sep 2025 20:06:58 +0700 Subject: [PATCH 1/8] describe --- dash-spv/src/client/block_processor.rs | 7 ++-- dash-spv/src/client/block_processor_test.rs | 8 +++++ key-wallet-manager/src/wallet_interface.rs | 9 ++++++ .../src/wallet_manager/process_block.rs | 32 +++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/dash-spv/src/client/block_processor.rs b/dash-spv/src/client/block_processor.rs index fe7129905..9325a0a03 100644 --- a/dash-spv/src/client/block_processor.rs +++ b/dash-spv/src/client/block_processor.rs @@ -179,17 +179,18 @@ impl String { + "MockWallet (test implementation)".to_string() + } } fn create_test_block(network: Network) -> Block { @@ -236,6 +240,10 @@ mod tests { // Always return false - filter doesn't match false } + + async fn describe(&self, _network: Network) -> String { + "NonMatchingWallet (test implementation)".to_string() + } } let (task_tx, task_rx) = mpsc::unbounded_channel(); diff --git a/key-wallet-manager/src/wallet_interface.rs b/key-wallet-manager/src/wallet_interface.rs index 43094eaa3..9f68c038e 100644 --- a/key-wallet-manager/src/wallet_interface.rs +++ b/key-wallet-manager/src/wallet_interface.rs @@ -2,6 +2,7 @@ //! //! This module defines the trait that SPV clients use to interact with wallets. +use alloc::string::String; use async_trait::async_trait; use dashcore::bip158::BlockFilter; use dashcore::prelude::CoreBlockHeight; @@ -49,4 +50,12 @@ pub trait WalletInterface: Send + Sync { async fn earliest_required_height(&self, _network: Network) -> Option { None } + + /// Provide a human-readable description of the wallet implementation. + /// + /// Implementations are encouraged to include high-level state such as the + /// number of managed wallets, networks, or tracked scripts. + async fn describe(&self, _network: Network) -> String { + "Wallet interface description unavailable".to_string() + } } diff --git a/key-wallet-manager/src/wallet_manager/process_block.rs b/key-wallet-manager/src/wallet_manager/process_block.rs index 0f18892d4..b4f81626e 100644 --- a/key-wallet-manager/src/wallet_manager/process_block.rs +++ b/key-wallet-manager/src/wallet_manager/process_block.rs @@ -1,5 +1,8 @@ use crate::wallet_interface::WalletInterface; use crate::{Network, WalletManager}; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt::Write as _; use async_trait::async_trait; use dashcore::bip158::BlockFilter; use dashcore::prelude::CoreBlockHeight; @@ -136,4 +139,33 @@ impl WalletInterface for WalletM // Return None if no wallets with known birth heights were found for this network earliest } + + async fn describe(&self, network: Network) -> String { + let wallet_count = self.wallet_infos.len(); + if wallet_count == 0 { + return format!("WalletManager: 0 wallets (network {})", network); + } + + let mut details = Vec::with_capacity(wallet_count); + for (wallet_id, info) in &self.wallet_infos { + let name = info.name().unwrap_or("unnamed"); + + let mut wallet_id_hex = String::with_capacity(wallet_id.len() * 2); + for byte in wallet_id { + let _ = write!(&mut wallet_id_hex, "{:02x}", byte); + } + + let script_count = info.monitored_addresses(network).len(); + let summary = format!("{} scripts", script_count); + + details.push(format!("{} ({}): {}", name, wallet_id_hex, summary)); + } + + format!( + "WalletManager: {} wallet(s) on {}\n{}", + wallet_count, + network, + details.join("\n") + ) + } } From 165c57890cf4e01d28e3b923da76e515fe63f498 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 28 Sep 2025 20:48:09 +0700 Subject: [PATCH 2/8] clear filters --- dash-spv/src/client/block_processor.rs | 7 ++-- dash-spv/src/client/mod.rs | 36 +++++++++++++++++++ dash-spv/src/storage/disk.rs | 21 +++++++++++ dash-spv/src/storage/memory.rs | 6 ++++ dash-spv/src/storage/mod.rs | 3 ++ dash-spv/src/sync/filters.rs | 8 +++++ dash-spv/tests/error_handling_test.rs | 7 ++++ .../tests/error_recovery_integration_test.rs | 4 +++ .../src/wallet_manager/process_block.rs | 9 ++--- 9 files changed, 92 insertions(+), 9 deletions(-) diff --git a/dash-spv/src/client/block_processor.rs b/dash-spv/src/client/block_processor.rs index 9325a0a03..56b00d488 100644 --- a/dash-spv/src/client/block_processor.rs +++ b/dash-spv/src/client/block_processor.rs @@ -180,7 +180,6 @@ impl Result<()> { + { + let mut storage = self.storage.lock().await; + storage.clear_filters().await.map_err(SpvError::Storage)?; + } + + // Reset in-memory chain state for filters + { + let mut state = self.state.write().await; + state.filter_headers.clear(); + state.current_filter_tip = None; + } + + // Reset filter sync manager tracking + self.sync_manager.filter_sync_mut().clear_filter_state().await; + + // Reset filter-related statistics + let received_heights = { + let stats = self.stats.read().await; + stats.received_filter_heights.clone() + }; + + { + let mut stats = self.stats.write().await; + stats.filter_headers_downloaded = 0; + stats.filter_height = 0; + stats.filters_downloaded = 0; + stats.filters_received = 0; + } + + received_heights.lock().await.clear(); + + Ok(()) + } + /// Take the progress receiver for external consumption. pub fn take_progress_receiver( &mut self, diff --git a/dash-spv/src/storage/disk.rs b/dash-spv/src/storage/disk.rs index 9551bbca1..ac3fff3a5 100644 --- a/dash-spv/src/storage/disk.rs +++ b/dash-spv/src/storage/disk.rs @@ -1631,6 +1631,27 @@ impl StorageManager for DiskStorageManager { Ok(()) } + async fn clear_filters(&mut self) -> StorageResult<()> { + // Stop worker to prevent concurrent writes to filter directories + self.stop_worker().await; + + // Clear in-memory filter state + self.active_filter_segments.write().await.clear(); + *self.cached_filter_tip_height.write().await = None; + + // Remove filter headers and compact filter files + let filters_dir = self.base_path.join("filters"); + if filters_dir.exists() { + tokio::fs::remove_dir_all(&filters_dir).await?; + } + tokio::fs::create_dir_all(&filters_dir).await?; + + // Restart background worker for future operations + self.start_worker().await; + + Ok(()) + } + async fn stats(&self) -> StorageResult { let mut component_sizes = HashMap::new(); let mut total_size = 0u64; diff --git a/dash-spv/src/storage/memory.rs b/dash-spv/src/storage/memory.rs index bf3767034..dd0c8b946 100644 --- a/dash-spv/src/storage/memory.rs +++ b/dash-spv/src/storage/memory.rs @@ -307,6 +307,12 @@ impl StorageManager for MemoryStorageManager { Ok(()) } + async fn clear_filters(&mut self) -> StorageResult<()> { + self.filter_headers.clear(); + self.filters.clear(); + Ok(()) + } + async fn stats(&self) -> StorageResult { let mut component_sizes = HashMap::new(); diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index e4f144312..17a6ac7a1 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -154,6 +154,9 @@ pub trait StorageManager: Send + Sync { /// Clear all data. async fn clear(&mut self) -> StorageResult<()>; + /// Clear all filter headers and compact filters. + async fn clear_filters(&mut self) -> StorageResult<()>; + /// Get storage statistics. async fn stats(&self) -> StorageResult; diff --git a/dash-spv/src/sync/filters.rs b/dash-spv/src/sync/filters.rs index c8509028f..99f2ca876 100644 --- a/dash-spv/src/sync/filters.rs +++ b/dash-spv/src/sync/filters.rs @@ -3466,4 +3466,12 @@ impl StorageResult<()> { + if self.fail_on_write { + return Err(StorageError::WriteFailed("Mock write failure".to_string())); + } + Ok(()) + } + async fn stats(&self) -> StorageResult { Ok(dash_spv::storage::StorageStats { header_count: 0, diff --git a/dash-spv/tests/error_recovery_integration_test.rs b/dash-spv/tests/error_recovery_integration_test.rs index f7cd0dc98..218969ac3 100644 --- a/dash-spv/tests/error_recovery_integration_test.rs +++ b/dash-spv/tests/error_recovery_integration_test.rs @@ -655,6 +655,10 @@ impl StorageManager for MockStorageManager { Ok(()) } + async fn clear_filters(&mut self) -> dash_spv::error::StorageResult<()> { + Ok(()) + } + async fn stats(&self) -> dash_spv::error::StorageResult { Ok(dash_spv::storage::StorageStats { header_count: 0, diff --git a/key-wallet-manager/src/wallet_manager/process_block.rs b/key-wallet-manager/src/wallet_manager/process_block.rs index b4f81626e..402efb562 100644 --- a/key-wallet-manager/src/wallet_manager/process_block.rs +++ b/key-wallet-manager/src/wallet_manager/process_block.rs @@ -2,8 +2,8 @@ use crate::wallet_interface::WalletInterface; use crate::{Network, WalletManager}; use alloc::string::String; use alloc::vec::Vec; -use core::fmt::Write as _; use async_trait::async_trait; +use core::fmt::Write as _; use dashcore::bip158::BlockFilter; use dashcore::prelude::CoreBlockHeight; use dashcore::{Block, BlockHash, Transaction, Txid}; @@ -161,11 +161,6 @@ impl WalletInterface for WalletM details.push(format!("{} ({}): {}", name, wallet_id_hex, summary)); } - format!( - "WalletManager: {} wallet(s) on {}\n{}", - wallet_count, - network, - details.join("\n") - ) + format!("WalletManager: {} wallet(s) on {}\n{}", wallet_count, network, details.join("\n")) } } From af64bc40d22de861056c83d7e44fb105ace3a846 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 29 Sep 2025 23:01:41 +0700 Subject: [PATCH 3/8] fixes --- dash-spv-ffi/FFI_API.md | 35 +++++-- dash-spv-ffi/dash_spv_ffi.h | 28 ++++-- dash-spv-ffi/examples/wallet_manager_usage.rs | 20 ++-- dash-spv-ffi/include/dash_spv_ffi.h | 28 ++++-- dash-spv-ffi/scripts/generate_ffi_docs.py | 9 +- dash-spv-ffi/src/client.rs | 37 +++++--- dash-spv-ffi/tests/test_wallet_manager.rs | 92 ++++++++++++++++++- dash-spv/src/client/mod.rs | 64 ++++++++++++- key-wallet-ffi/FFI_API.md | 35 ++++++- key-wallet-ffi/include/key_wallet_ffi.h | 19 ++++ key-wallet-ffi/src/lib.rs | 7 +- key-wallet-ffi/src/wallet_manager.rs | 53 +++++++++++ .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 28 ++++-- .../KeyWalletFFI/include/key_wallet_ffi.h | 19 ++++ 14 files changed, 402 insertions(+), 72 deletions(-) diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index b3e2902bb..7f9121b23 100644 --- a/dash-spv-ffi/FFI_API.md +++ b/dash-spv-ffi/FFI_API.md @@ -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**: 70 +**Total Functions**: 71 ## Table of Contents @@ -138,7 +138,7 @@ Functions: 2 ### Utility Functions -Functions: 18 +Functions: 19 | Function | Description | Module | |----------|-------------|--------| @@ -151,7 +151,7 @@ Functions: 18 | `dash_spv_ffi_client_get_stats` | Get current runtime statistics for the SPV client | client | | `dash_spv_ffi_client_get_tip_hash` | Get the current chain tip hash (32 bytes) if available | client | | `dash_spv_ffi_client_get_tip_height` | Get the current chain tip height (absolute) | 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_get_wallet_manager` | Get the wallet manager from the SPV client Returns a pointer to an `FFIWalle... | client | | `dash_spv_ffi_client_record_send` | Record that we attempted to send a transaction by its txid | client | | `dash_spv_ffi_client_rescan_blockchain` | Request a rescan of the blockchain from a given height (not yet implemented) | client | | `dash_spv_ffi_enable_test_mode` | No description | utils | @@ -160,6 +160,7 @@ Functions: 18 | `dash_spv_ffi_string_array_destroy` | Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents | types | | `dash_spv_ffi_string_destroy` | No description | types | | `dash_spv_ffi_version` | No description | utils | +| `dash_spv_ffi_wallet_manager_free` | Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager` | client | ## Detailed Function Documentation @@ -1117,14 +1118,14 @@ Get the current chain tip height (absolute). # Safety - `client` must be a vali #### `dash_spv_ffi_client_get_wallet_manager` ```c -dash_spv_ffi_client_get_wallet_manager(client: *mut FFIDashSpvClient,) -> *mut c_void +dash_spv_ffi_client_get_wallet_manager(client: *mut FFIDashSpvClient,) -> *mut FFIWalletManager ``` **Description:** -Get the wallet manager from the SPV client Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. This allows direct interaction with the wallet manager without going through the client. # Safety The caller must ensure that: - The client pointer is valid - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi # Returns An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. Swift should treat this as an OpaquePointer. Get a handle to the wallet manager owned by this client. # Safety - `client` must be a valid, non-null pointer. +Get the wallet manager from the SPV client Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying `Arc>`. This allows direct interaction with the wallet manager without going back through the client for each call. # Safety The caller must ensure that: - The client pointer is valid - The returned pointer is released exactly once using `dash_spv_ffi_wallet_manager_free` # Returns A pointer to the wallet manager wrapper, or NULL if the client is not initialized. **Safety:** -The caller must ensure that: - The client pointer is valid - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi +The caller must ensure that: - The client pointer is valid - The returned pointer is released exactly once using `dash_spv_ffi_wallet_manager_free` **Module:** `client` @@ -1237,6 +1238,22 @@ dash_spv_ffi_version() -> *const c_char --- +#### `dash_spv_ffi_wallet_manager_free` + +```c +dash_spv_ffi_wallet_manager_free(manager: *mut FFIWalletManager) -> () +``` + +**Description:** +Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. This simply forwards to `wallet_manager_free` in key-wallet-ffi so that lifetime management is consistent between direct key-wallet usage and the SPV client pathway. # Safety - `manager` must either be null or a pointer previously returned by `dash_spv_ffi_client_get_wallet_manager`. + +**Safety:** +- `manager` must either be null or a pointer previously returned by `dash_spv_ffi_client_get_wallet_manager`. + +**Module:** `client` + +--- + ## Type Definitions ### Core Types @@ -1266,7 +1283,7 @@ dash_spv_ffi_version() -> *const c_char 2. **Cleanup Required**: All returned pointers must be freed using the appropriate `_destroy` function 3. **Thread Safety**: The SPV client is thread-safe 4. **Error Handling**: Check return codes and use `dash_spv_ffi_get_last_error()` for details -5. **Opaque Pointers**: `dash_spv_ffi_client_get_wallet_manager()` returns `void*` for Swift compatibility +5. **Shared Ownership**: `dash_spv_ffi_client_get_wallet_manager()` returns `FFIWalletManager*` that must be released with `dash_spv_ffi_wallet_manager_free()` ## Usage Examples @@ -1289,8 +1306,8 @@ if (result != 0) { // Sync to chain tip dash_spv_ffi_client_sync_to_tip(client, NULL, NULL); -// Get wallet manager (returns void* for Swift) -void* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client); +// Get wallet manager (shares ownership with the client) +FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client); // Clean up dash_spv_ffi_client_destroy(client); diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index b8169ea5c..bdbde3fbc 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -531,27 +531,35 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli /** * Get the wallet manager from the SPV client * - * Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. - * This allows direct interaction with the wallet manager without going through the client. + * Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying + * `Arc>`. This allows direct interaction with the wallet + * manager without going back through the client for each call. * * # Safety * * The caller must ensure that: * - The client pointer is valid - * - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi + * - The returned pointer is released exactly once using + * `dash_spv_ffi_wallet_manager_free` * * # Returns * - * An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. - * Swift should treat this as an OpaquePointer. - * Get a handle to the wallet manager owned by this client. + * A pointer to the wallet manager wrapper, or NULL if the client is not initialized. + */ + FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; + +/** + * Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. + * + * This simply forwards to `wallet_manager_free` in key-wallet-ffi so that + * lifetime management is consistent between direct key-wallet usage and the + * SPV client pathway. * * # Safety - * - `client` must be a valid, non-null pointer. + * - `manager` must either be null or a pointer previously returned by + * `dash_spv_ffi_client_get_wallet_manager`. */ - -void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) -; + void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ; struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; diff --git a/dash-spv-ffi/examples/wallet_manager_usage.rs b/dash-spv-ffi/examples/wallet_manager_usage.rs index baffeb79d..639396cf1 100644 --- a/dash-spv-ffi/examples/wallet_manager_usage.rs +++ b/dash-spv-ffi/examples/wallet_manager_usage.rs @@ -5,7 +5,7 @@ /// 2. No longer requires going through the client for each operation /// 3. Cleaner and more efficient access to wallet functionality use dash_spv_ffi::*; -use key_wallet_ffi::{wallet_manager_free, wallet_manager_wallet_count, FFIError}; +use key_wallet_ffi::{wallet_manager_wallet_count, FFIError}; fn main() { unsafe { @@ -21,22 +21,22 @@ fn main() { panic!("Failed to create client"); } - // Get the wallet manager - now returns void* for Swift compatibility - // This contains a cloned Arc to the wallet manager, allowing - // direct interaction without going through the client - let wallet_manager_ptr = dash_spv_ffi_client_get_wallet_manager(client); - if wallet_manager_ptr.is_null() { + // Get the wallet manager - this returns a strongly typed pointer that + // shares the Arc with the SPV client, allowing direct interaction + let wallet_manager = dash_spv_ffi_client_get_wallet_manager(client); + if wallet_manager.is_null() { panic!("Failed to get wallet manager"); } - // Cast back to FFIWalletManager for use - let wallet_manager = wallet_manager_ptr as *mut key_wallet_ffi::FFIWalletManager; // Now we can use the wallet manager directly // No need to go through client -> inner -> spv_client -> wallet() // Get the number of wallets (should be 0 initially) let mut error = std::mem::zeroed::(); - let wallet_count = wallet_manager_wallet_count(wallet_manager, &mut error); + let wallet_count = wallet_manager_wallet_count( + wallet_manager as *const key_wallet_ffi::FFIWalletManager, + &mut error, + ); println!("Number of wallets: {}", wallet_count); // Note: To get total balance, you would need to iterate through wallets @@ -66,7 +66,7 @@ fn main() { // Clean up // The wallet manager can now be independently destroyed // It maintains its own Arc reference to the underlying wallet - wallet_manager_free(wallet_manager); + dash_spv_ffi_wallet_manager_free(wallet_manager); dash_spv_ffi_client_destroy(client); dash_spv_ffi_config_destroy(config); diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index b8169ea5c..bdbde3fbc 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -531,27 +531,35 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli /** * Get the wallet manager from the SPV client * - * Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. - * This allows direct interaction with the wallet manager without going through the client. + * Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying + * `Arc>`. This allows direct interaction with the wallet + * manager without going back through the client for each call. * * # Safety * * The caller must ensure that: * - The client pointer is valid - * - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi + * - The returned pointer is released exactly once using + * `dash_spv_ffi_wallet_manager_free` * * # Returns * - * An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. - * Swift should treat this as an OpaquePointer. - * Get a handle to the wallet manager owned by this client. + * A pointer to the wallet manager wrapper, or NULL if the client is not initialized. + */ + FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; + +/** + * Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. + * + * This simply forwards to `wallet_manager_free` in key-wallet-ffi so that + * lifetime management is consistent between direct key-wallet usage and the + * SPV client pathway. * * # Safety - * - `client` must be a valid, non-null pointer. + * - `manager` must either be null or a pointer previously returned by + * `dash_spv_ffi_client_get_wallet_manager`. */ - -void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) -; + void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ; struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; diff --git a/dash-spv-ffi/scripts/generate_ffi_docs.py b/dash-spv-ffi/scripts/generate_ffi_docs.py index 1c5d2b719..517e52b47 100644 --- a/dash-spv-ffi/scripts/generate_ffi_docs.py +++ b/dash-spv-ffi/scripts/generate_ffi_docs.py @@ -287,7 +287,10 @@ def generate_markdown(functions: List[FFIFunction]) -> str: md.append("2. **Cleanup Required**: All returned pointers must be freed using the appropriate `_destroy` function") md.append("3. **Thread Safety**: The SPV client is thread-safe") md.append("4. **Error Handling**: Check return codes and use `dash_spv_ffi_get_last_error()` for details") - md.append("5. **Opaque Pointers**: `dash_spv_ffi_client_get_wallet_manager()` returns `void*` for Swift compatibility") + md.append( + "5. **Shared Ownership**: `dash_spv_ffi_client_get_wallet_manager()` returns `FFIWalletManager*` " + "that must be released with `dash_spv_ffi_wallet_manager_free()`" + ) md.append("") # Usage Examples @@ -312,8 +315,8 @@ def generate_markdown(functions: List[FFIFunction]) -> str: md.append("// Sync to chain tip") md.append("dash_spv_ffi_client_sync_to_tip(client, NULL, NULL);") md.append("") - md.append("// Get wallet manager (returns void* for Swift)") - md.append("void* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);") + md.append("// Get wallet manager (shares ownership with the client)") + md.append("FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);") md.append("") md.append("// Clean up") md.append("dash_spv_ffi_client_destroy(client);") diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 84b8a3979..b47d25acd 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -1485,27 +1485,24 @@ pub unsafe extern "C" fn dash_spv_ffi_client_record_send( /// Get the wallet manager from the SPV client /// -/// Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. -/// This allows direct interaction with the wallet manager without going through the client. +/// Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying +/// `Arc>`. This allows direct interaction with the wallet +/// manager without going back through the client for each call. /// /// # Safety /// /// The caller must ensure that: /// - The client pointer is valid -/// - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi +/// - The returned pointer is released exactly once using +/// `dash_spv_ffi_wallet_manager_free` /// /// # Returns /// -/// An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. -/// Swift should treat this as an OpaquePointer. -/// Get a handle to the wallet manager owned by this client. -/// -/// # Safety -/// - `client` must be a valid, non-null pointer. +/// A pointer to the wallet manager wrapper, or NULL if the client is not initialized. #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_client_get_wallet_manager( client: *mut FFIDashSpvClient, -) -> *mut c_void { +) -> *mut FFIWalletManager { null_check!(client, std::ptr::null_mut()); let client = &*client; @@ -1519,9 +1516,27 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_wallet_manager( // Create the FFIWalletManager with the cloned Arc let manager = FFIWalletManager::from_arc(wallet_arc, runtime); - Box::into_raw(Box::new(manager)) as *mut c_void + Box::into_raw(Box::new(manager)) } else { set_last_error("Client not initialized"); std::ptr::null_mut() } } + +/// Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. +/// +/// This simply forwards to `wallet_manager_free` in key-wallet-ffi so that +/// lifetime management is consistent between direct key-wallet usage and the +/// SPV client pathway. +/// +/// # Safety +/// - `manager` must either be null or a pointer previously returned by +/// `dash_spv_ffi_client_get_wallet_manager`. +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_wallet_manager_free(manager: *mut FFIWalletManager) { + if manager.is_null() { + return; + } + + key_wallet_ffi::wallet_manager::wallet_manager_free(manager); +} diff --git a/dash-spv-ffi/tests/test_wallet_manager.rs b/dash-spv-ffi/tests/test_wallet_manager.rs index 570ffefce..240c5e42e 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -1,10 +1,18 @@ #[cfg(test)] mod tests { use dash_spv_ffi::*; + use dashcore::Network; + use key_wallet::wallet::initialization::WalletAccountCreationOptions; + use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_ffi::{ - wallet_manager::{wallet_manager_free, wallet_manager_wallet_count}, - FFIError, FFIWalletManager, + wallet_manager::{ + wallet_manager_free_wallet_ids, wallet_manager_get_wallet_ids, + wallet_manager_import_wallet_from_bytes, wallet_manager_wallet_count, + }, + FFIError, FFINetwork, FFIWalletManager, }; + use key_wallet_manager::wallet_manager::WalletManager; + use std::ffi::CStr; #[test] fn test_get_wallet_manager() { @@ -30,7 +38,85 @@ mod tests { assert_eq!(count, 0); // Clean up - wallet_manager_free(wallet_manager as *mut FFIWalletManager); + dash_spv_ffi_wallet_manager_free(wallet_manager); + dash_spv_ffi_client_destroy(client); + dash_spv_ffi_config_destroy(config); + } + } + + #[test] + fn test_wallet_manager_shared_via_client_imports_wallet() { + unsafe { + let config = dash_spv_ffi_config_testnet(); + assert!(!config.is_null()); + + let client = dash_spv_ffi_client_new(config); + assert!(!client.is_null()); + + let wallet_manager = dash_spv_ffi_client_get_wallet_manager(client); + assert!(!wallet_manager.is_null()); + + // Prepare a serialized wallet using the native manager so we can import it + let mut native_manager = WalletManager::::new(); + let (serialized_wallet, expected_wallet_id) = native_manager + .create_wallet_from_mnemonic_return_serialized_bytes( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "", + &[Network::Dash], + None, + WalletAccountCreationOptions::Default, + false, + false, + ) + .expect("wallet serialization should succeed"); + + // Import the serialized wallet through the FFI pointer we retrieved from the client + let mut error = FFIError::success(); + let mut imported_wallet_id = [0u8; 32]; + let import_ok = wallet_manager_import_wallet_from_bytes( + wallet_manager, + serialized_wallet.as_ptr(), + serialized_wallet.len(), + imported_wallet_id.as_mut_ptr(), + &mut error as *mut FFIError, + ); + assert!(import_ok, "import should succeed: {:?}", error); + assert_eq!(imported_wallet_id, expected_wallet_id); + + // Fetch wallet IDs through FFI to confirm the manager sees the new wallet + let mut ids_ptr: *mut u8 = std::ptr::null_mut(); + let mut id_count: usize = 0; + let ids_ok = wallet_manager_get_wallet_ids( + wallet_manager as *const FFIWalletManager, + &mut ids_ptr, + &mut id_count, + &mut error as *mut FFIError, + ); + assert!(ids_ok, "get_wallet_ids should succeed: {:?}", error); + assert_eq!(id_count, 1); + assert!(!ids_ptr.is_null()); + + let ids_slice = std::slice::from_raw_parts(ids_ptr, id_count * 32); + assert_eq!(&ids_slice[..32], &expected_wallet_id); + wallet_manager_free_wallet_ids(ids_ptr, id_count); + + // Call the describe helper through FFI to ensure the shared instance reports correctly + let mut description_error = FFIError::success(); + let description_ptr = key_wallet_ffi::wallet_manager_describe( + wallet_manager as *const FFIWalletManager, + FFINetwork::Dash, + &mut description_error as *mut FFIError, + ); + assert!(!description_ptr.is_null(), "describe should succeed: {:?}", description_error); + let description = CStr::from_ptr(description_ptr).to_string_lossy().into_owned(); + key_wallet_ffi::wallet_manager_free_string(description_ptr); + assert!( + description.contains("WalletManager: 1 wallet"), + "description should mention the imported wallet, got: {}", + description + ); + + dash_spv_ffi_wallet_manager_free(wallet_manager); dash_spv_ffi_client_destroy(client); dash_spv_ffi_config_destroy(config); } diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 802d1e852..9ffa0d0f4 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -2546,8 +2546,15 @@ mod message_handler_test; #[cfg(test)] mod tests { + use super::{ClientConfig, DashSpvClient}; + use crate::network::mock::MockNetworkManager; + use crate::storage::MemoryStorageManager; use crate::types::{MempoolState, UnconfirmedTransaction}; - use dashcore::{Amount, Transaction, TxOut}; + use dashcore::{Amount, Network, Transaction, TxOut}; + use key_wallet::wallet::initialization::WalletAccountCreationOptions; + use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; + use key_wallet_manager::wallet_interface::WalletInterface; + use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; use tokio::sync::RwLock; @@ -2557,6 +2564,61 @@ mod tests { // 2. Validation of transaction effects on addresses // 3. Edge cases like zero amounts and conflicting signs + #[tokio::test] + async fn client_exposes_shared_wallet_manager() { + let mut config = ClientConfig::default(); + config.network = Network::Dash; + config.enable_filters = false; + config.enable_masternodes = false; + config.enable_mempool_tracking = false; + + let network_manager = MockNetworkManager::new(); + let storage = MemoryStorageManager::new().await.expect("memory storage should initialize"); + let wallet = Arc::new(RwLock::new(WalletManager::::new())); + + let client = DashSpvClient::new(config, network_manager, storage, wallet) + .await + .expect("client construction must succeed"); + + let shared_wallet = client.wallet().clone(); + + { + let guard = shared_wallet.read().await; + assert_eq!(guard.wallet_count(), 0, "new managers start empty"); + } + + let mut temp_manager = WalletManager::::new(); + let (serialized_wallet, _wallet_id) = temp_manager + .create_wallet_from_mnemonic_return_serialized_bytes( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "", + &[Network::Dash], + None, + WalletAccountCreationOptions::Default, + false, + false, + ) + .expect("wallet serialization should succeed"); + + { + let mut guard = shared_wallet.write().await; + guard + .import_wallet_from_bytes(&serialized_wallet) + .expect("importing serialized wallet should succeed"); + } + + let description = { + let guard = shared_wallet.read().await; + guard.describe(Network::Dash).await + }; + + assert!( + description.contains("WalletManager: 1 wallet"), + "description should capture imported wallet, got: {}", + description + ); + } + #[tokio::test] async fn test_get_mempool_balance_logic() { // Create a simple test scenario to validate the balance calculation logic diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index 8a4f9aba6..0492e26b6 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -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**: 232 +**Total Functions**: 234 ## Table of Contents @@ -42,7 +42,7 @@ Functions: 3 ### Wallet Manager -Functions: 17 +Functions: 19 | Function | Description | Module | |----------|-------------|--------| @@ -51,8 +51,10 @@ Functions: 17 | `wallet_manager_add_wallet_from_mnemonic_with_options` | Add a wallet from mnemonic to the manager with options # Safety - `manager`... | wallet_manager | | `wallet_manager_create` | Create a new wallet manager | wallet_manager | | `wallet_manager_current_height` | Get current height for a network # Safety - `manager` must be a valid point... | wallet_manager | +| `wallet_manager_describe` | Describe the wallet manager for a given network and return a newly allocated ... | wallet_manager | | `wallet_manager_free` | Free wallet manager # Safety - `manager` must be a valid pointer to an FFIW... | wallet_manager | | `wallet_manager_free_addresses` | Free address array # Safety - `addresses` must be a valid pointer to an arr... | wallet_manager | +| `wallet_manager_free_string` | Free a string previously returned by wallet manager APIs | wallet_manager | | `wallet_manager_free_wallet_bytes` | No description | wallet_manager | | `wallet_manager_free_wallet_ids` | Free wallet IDs buffer # Safety - `wallet_ids` must be a valid pointer to a... | wallet_manager | | `wallet_manager_get_managed_wallet_info` | Get managed wallet info from the manager Returns a reference to the managed ... | wallet_manager | @@ -476,6 +478,22 @@ Get current height for a network # Safety - `manager` must be a valid pointer --- +#### `wallet_manager_describe` + +```c +wallet_manager_describe(manager: *const FFIWalletManager, network: crate::FFINetwork, error: *mut FFIError,) -> *mut c_char +``` + +**Description:** +Describe the wallet manager for a given network and return a newly allocated C string. # Safety - `manager` must be a valid pointer to an `FFIWalletManager` - Callers must free the returned string with `wallet_manager_free_string` + +**Safety:** +- `manager` must be a valid pointer to an `FFIWalletManager` - Callers must free the returned string with `wallet_manager_free_string` + +**Module:** `wallet_manager` + +--- + #### `wallet_manager_free` ```c @@ -508,6 +526,19 @@ Free address array # Safety - `addresses` must be a valid pointer to an array --- +#### `wallet_manager_free_string` + +```c +wallet_manager_free_string(value: *mut c_char) -> () +``` + +**Description:** +Free a string previously returned by wallet manager APIs. + +**Module:** `wallet_manager` + +--- + #### `wallet_manager_free_wallet_bytes` ```c diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index ae0749d60..17527ca2a 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -3727,6 +3727,25 @@ FFIAccountResult wallet_add_account_with_string_xpub(FFIWallet *wallet, const char *xpub_string) ; +/* + Describe the wallet manager for a given network and return a newly + allocated C string. + + # Safety + - `manager` must be a valid pointer to an `FFIWalletManager` + - Callers must free the returned string with `wallet_manager_free_string` + */ + +char *wallet_manager_describe(const FFIWalletManager *manager, + FFINetwork network, + FFIError *error) +; + +/* + Free a string previously returned by wallet manager APIs. + */ + void wallet_manager_free_string(char *value) ; + /* Create a new wallet manager */ diff --git a/key-wallet-ffi/src/lib.rs b/key-wallet-ffi/src/lib.rs index 7fbb41b46..4488fda2c 100644 --- a/key-wallet-ffi/src/lib.rs +++ b/key-wallet-ffi/src/lib.rs @@ -34,9 +34,10 @@ pub use error::{FFIError, FFIErrorCode}; pub use types::{FFIBalance, FFINetwork, FFINetworks, FFIWallet}; pub use utxo::FFIUTXO; pub use wallet_manager::{ - wallet_manager_create, wallet_manager_free, wallet_manager_free_wallet_ids, - wallet_manager_get_wallet, wallet_manager_get_wallet_balance, wallet_manager_get_wallet_ids, - wallet_manager_wallet_count, FFIWalletManager, + wallet_manager_create, wallet_manager_describe, wallet_manager_free, + wallet_manager_free_string, wallet_manager_free_wallet_ids, wallet_manager_get_wallet, + wallet_manager_get_wallet_balance, wallet_manager_get_wallet_ids, wallet_manager_wallet_count, + FFIWalletManager, }; // ============================================================================ diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index cefeced72..8630c7849 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -19,6 +19,7 @@ use crate::types::FFINetworks; use crate::FFINetwork; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; +use key_wallet_manager::wallet_interface::WalletInterface; use key_wallet_manager::wallet_manager::WalletManager; /// FFI wrapper for WalletManager @@ -44,6 +45,58 @@ impl FFIWalletManager { } } +/// Describe the wallet manager for a given network and return a newly +/// allocated C string. +/// +/// # Safety +/// - `manager` must be a valid pointer to an `FFIWalletManager` +/// - Callers must free the returned string with `wallet_manager_free_string` +#[no_mangle] +pub unsafe extern "C" fn wallet_manager_describe( + manager: *const FFIWalletManager, + network: crate::FFINetwork, + error: *mut FFIError, +) -> *mut c_char { + if manager.is_null() { + FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); + return ptr::null_mut(); + } + + let manager_ref = &*manager; + let runtime = manager_ref.runtime.clone(); + let manager_arc = manager_ref.manager.clone(); + + let description = runtime.block_on(async { + let guard = manager_arc.read().await; + guard.describe(network.into()).await + }); + + match CString::new(description) { + Ok(c_string) => { + FFIError::set_success(error); + c_string.into_raw() + } + Err(e) => { + FFIError::set_error( + error, + FFIErrorCode::InvalidState, + format!("Failed to create description string: {}", e), + ); + ptr::null_mut() + } + } +} + +/// Free a string previously returned by wallet manager APIs. +#[no_mangle] +pub unsafe extern "C" fn wallet_manager_free_string(value: *mut c_char) { + if value.is_null() { + return; + } + + drop(CString::from_raw(value)); +} + /// Create a new wallet manager #[no_mangle] pub extern "C" fn wallet_manager_create(error: *mut FFIError) -> *mut FFIWalletManager { diff --git a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h index b8169ea5c..bdbde3fbc 100644 --- a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h +++ b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h @@ -531,27 +531,35 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli /** * Get the wallet manager from the SPV client * - * Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. - * This allows direct interaction with the wallet manager without going through the client. + * Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying + * `Arc>`. This allows direct interaction with the wallet + * manager without going back through the client for each call. * * # Safety * * The caller must ensure that: * - The client pointer is valid - * - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi + * - The returned pointer is released exactly once using + * `dash_spv_ffi_wallet_manager_free` * * # Returns * - * An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. - * Swift should treat this as an OpaquePointer. - * Get a handle to the wallet manager owned by this client. + * A pointer to the wallet manager wrapper, or NULL if the client is not initialized. + */ + FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; + +/** + * Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. + * + * This simply forwards to `wallet_manager_free` in key-wallet-ffi so that + * lifetime management is consistent between direct key-wallet usage and the + * SPV client pathway. * * # Safety - * - `client` must be a valid, non-null pointer. + * - `manager` must either be null or a pointer previously returned by + * `dash_spv_ffi_client_get_wallet_manager`. */ - -void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) -; + void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ; struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; diff --git a/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h b/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h index ae0749d60..17527ca2a 100644 --- a/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h +++ b/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h @@ -3727,6 +3727,25 @@ FFIAccountResult wallet_add_account_with_string_xpub(FFIWallet *wallet, const char *xpub_string) ; +/* + Describe the wallet manager for a given network and return a newly + allocated C string. + + # Safety + - `manager` must be a valid pointer to an `FFIWalletManager` + - Callers must free the returned string with `wallet_manager_free_string` + */ + +char *wallet_manager_describe(const FFIWalletManager *manager, + FFINetwork network, + FFIError *error) +; + +/* + Free a string previously returned by wallet manager APIs. + */ + void wallet_manager_free_string(char *value) ; + /* Create a new wallet manager */ From 8547d5ca85e651974cd8fa9d5c7f5638a4aeca01 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 11:16:31 +0700 Subject: [PATCH 4/8] fixes --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4fb5dc19d..2b164aa64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ version = "0.40.0" [patch.crates-io] dashcore_hashes = { path = "hashes" } # Use fixed version of elliptic-curve-tools with DefaultIsZeroes trait bound -elliptic-curve-tools = { git = "https://github.com/mikelodder7/elliptic-curve-tools", branch = "main" } +elliptic-curve-tools = { git = "https://github.com/mikelodder7/elliptic-curve-tools", rev = "8eaba09c9dc93ab367b3ad6fa85ed8e7ae8f0da5" } [profile.release] # Default to unwinding for most crates From 53a7cde38da634f276bdf57e0acbc5a80e18fcf6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 12:56:10 +0700 Subject: [PATCH 5/8] fixes --- Cargo.toml | 2 -- dash-spv/Cargo.toml | 2 +- dash/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b164aa64..33c2aaac8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,6 @@ version = "0.40.0" [patch.crates-io] dashcore_hashes = { path = "hashes" } -# Use fixed version of elliptic-curve-tools with DefaultIsZeroes trait bound -elliptic-curve-tools = { git = "https://github.com/mikelodder7/elliptic-curve-tools", rev = "8eaba09c9dc93ab367b3ad6fa85ed8e7ae8f0da5" } [profile.release] # Default to unwinding for most crates diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index 493dee200..38797e1d7 100644 --- a/dash-spv/Cargo.toml +++ b/dash-spv/Cargo.toml @@ -16,7 +16,7 @@ key-wallet = { path = "../key-wallet" } key-wallet-manager = { path = "../key-wallet-manager" } # BLS signatures -blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "be108b2cf6ac64eedbe04f91c63731533c8956bc" } +blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "0c34a7a488a0bd1c9a9a2196e793b303ad35c900" } # CLI clap = { version = "4.0", features = ["derive"] } diff --git a/dash/Cargo.toml b/dash/Cargo.toml index 30f357511..6dc4edc1b 100644 --- a/dash/Cargo.toml +++ b/dash/Cargo.toml @@ -63,7 +63,7 @@ anyhow = { version= "1.0" } hex = { version= "0.4" } bincode = { version= "=2.0.0-rc.3", optional = true } bincode_derive = { version= "=2.0.0-rc.3", optional = true } -blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "be108b2cf6ac64eedbe04f91c63731533c8956bc", optional = true } +blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "0c34a7a488a0bd1c9a9a2196e793b303ad35c900", optional = true } ed25519-dalek = { version = "2.1", features = ["rand_core"], optional = true } blake3 = "1.8.1" thiserror = "2" From 5df4609c3b3e5967bb523a4e16e73dcaaf79b3b7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 16:11:25 +0700 Subject: [PATCH 6/8] fix --- key-wallet-ffi/include/key_wallet_ffi.h | 6 ++++++ key-wallet-ffi/src/wallet_manager.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index 17527ca2a..344fa14f8 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -3743,6 +3743,12 @@ char *wallet_manager_describe(const FFIWalletManager *manager, /* Free a string previously returned by wallet manager APIs. + + # Safety + - `value` must be either null or a pointer obtained from + `wallet_manager_describe` (or other wallet manager FFI helpers that + specify this free function). + - The pointer must not be used after this call returns. */ void wallet_manager_free_string(char *value) ; diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index 8630c7849..4522083b8 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -88,6 +88,12 @@ pub unsafe extern "C" fn wallet_manager_describe( } /// Free a string previously returned by wallet manager APIs. +/// +/// # Safety +/// - `value` must be either null or a pointer obtained from +/// `wallet_manager_describe` (or other wallet manager FFI helpers that +/// specify this free function). +/// - The pointer must not be used after this call returns. #[no_mangle] pub unsafe extern "C" fn wallet_manager_free_string(value: *mut c_char) { if value.is_null() { From 40cea47c348872e526ee2bb9053f78aa41329067 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 16:18:38 +0700 Subject: [PATCH 7/8] fix --- key-wallet-ffi/FFI_API.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index 0492e26b6..b613851a6 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -533,7 +533,10 @@ wallet_manager_free_string(value: *mut c_char) -> () ``` **Description:** -Free a string previously returned by wallet manager APIs. +Free a string previously returned by wallet manager APIs. # Safety - `value` must be either null or a pointer obtained from `wallet_manager_describe` (or other wallet manager FFI helpers that specify this free function). - The pointer must not be used after this call returns. + +**Safety:** +- `value` must be either null or a pointer obtained from `wallet_manager_describe` (or other wallet manager FFI helpers that specify this free function). - The pointer must not be used after this call returns. **Module:** `wallet_manager` From 827ae97ffb25b7a2d635ea17574d836aaad6dedd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 16:33:56 +0700 Subject: [PATCH 8/8] fix --- dash-spv-ffi/include/dash_spv_ffi.h | 15 +++++++++++++-- dash-spv-ffi/src/client.rs | 10 +++++----- dash-spv-ffi/src/types.rs | 10 ++++++++++ dash-spv-ffi/tests/test_wallet_manager.rs | 7 ++++--- dash-spv/src/client/mod.rs | 12 +++++++----- .../Sources/DashSPVFFI/include/dash_spv_ffi.h | 15 +++++++++++++-- .../Sources/KeyWalletFFI/include/key_wallet_ffi.h | 6 ++++++ 7 files changed, 58 insertions(+), 17 deletions(-) diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index bdbde3fbc..dae86e8a6 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -161,6 +161,17 @@ typedef struct FFIEventCallbacks { void *user_data; } FFIEventCallbacks; +/** + * Opaque handle to the wallet manager owned by the SPV client. + * + * This is intentionally zero-sized so it can be used purely as an FFI handle + * while still allowing Rust to cast to the underlying key-wallet manager + * implementation when necessary. + */ +typedef struct FFIWalletManager { + uint8_t _private[0]; +} FFIWalletManager; + /** * Handle for Core SDK that can be passed to Platform SDK */ @@ -546,7 +557,7 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli * * A pointer to the wallet manager wrapper, or NULL if the client is not initialized. */ - FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; + struct FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; /** * Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. @@ -559,7 +570,7 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli * - `manager` must either be null or a pointer previously returned by * `dash_spv_ffi_client_get_wallet_manager`. */ - void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ; + void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ; struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index b47d25acd..a6858970a 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -1,9 +1,9 @@ use crate::{ null_check, set_last_error, FFIClientConfig, FFIDetailedSyncProgress, FFIErrorCode, - FFIEventCallbacks, FFIMempoolStrategy, FFISpvStats, FFISyncProgress, + FFIEventCallbacks, FFIMempoolStrategy, FFISpvStats, FFISyncProgress, FFIWalletManager, }; // Import wallet types from key-wallet-ffi -use key_wallet_ffi::FFIWalletManager; +use key_wallet_ffi::FFIWalletManager as KeyWalletFFIWalletManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::SyncStage; @@ -1514,9 +1514,9 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_wallet_manager( let runtime = client.runtime.clone(); // Create the FFIWalletManager with the cloned Arc - let manager = FFIWalletManager::from_arc(wallet_arc, runtime); + let manager = KeyWalletFFIWalletManager::from_arc(wallet_arc, runtime); - Box::into_raw(Box::new(manager)) + Box::into_raw(Box::new(manager)) as *mut FFIWalletManager } else { set_last_error("Client not initialized"); std::ptr::null_mut() @@ -1538,5 +1538,5 @@ pub unsafe extern "C" fn dash_spv_ffi_wallet_manager_free(manager: *mut FFIWalle return; } - key_wallet_ffi::wallet_manager::wallet_manager_free(manager); + key_wallet_ffi::wallet_manager::wallet_manager_free(manager as *mut KeyWalletFFIWalletManager); } diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index d703e376a..a66a35211 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -4,6 +4,16 @@ use dash_spv::{ChainState, PeerInfo, SpvStats, SyncProgress}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; +/// Opaque handle to the wallet manager owned by the SPV client. +/// +/// This is intentionally zero-sized so it can be used purely as an FFI handle +/// while still allowing Rust to cast to the underlying key-wallet manager +/// implementation when necessary. +#[repr(C)] +pub struct FFIWalletManager { + _private: [u8; 0], +} + #[repr(C)] pub struct FFIString { pub ptr: *mut c_char, diff --git a/dash-spv-ffi/tests/test_wallet_manager.rs b/dash-spv-ffi/tests/test_wallet_manager.rs index 240c5e42e..531ae182f 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -55,6 +55,7 @@ mod tests { let wallet_manager = dash_spv_ffi_client_get_wallet_manager(client); assert!(!wallet_manager.is_null()); + let wallet_manager_ptr = wallet_manager as *mut key_wallet_ffi::FFIWalletManager; // Prepare a serialized wallet using the native manager so we can import it let mut native_manager = WalletManager::::new(); @@ -74,7 +75,7 @@ mod tests { let mut error = FFIError::success(); let mut imported_wallet_id = [0u8; 32]; let import_ok = wallet_manager_import_wallet_from_bytes( - wallet_manager, + wallet_manager_ptr, serialized_wallet.as_ptr(), serialized_wallet.len(), imported_wallet_id.as_mut_ptr(), @@ -87,7 +88,7 @@ mod tests { let mut ids_ptr: *mut u8 = std::ptr::null_mut(); let mut id_count: usize = 0; let ids_ok = wallet_manager_get_wallet_ids( - wallet_manager as *const FFIWalletManager, + wallet_manager_ptr as *const FFIWalletManager, &mut ids_ptr, &mut id_count, &mut error as *mut FFIError, @@ -103,7 +104,7 @@ mod tests { // Call the describe helper through FFI to ensure the shared instance reports correctly let mut description_error = FFIError::success(); let description_ptr = key_wallet_ffi::wallet_manager_describe( - wallet_manager as *const FFIWalletManager, + wallet_manager_ptr as *const FFIWalletManager, FFINetwork::Dash, &mut description_error as *mut FFIError, ); diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 9ffa0d0f4..47b46e261 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -2566,11 +2566,13 @@ mod tests { #[tokio::test] async fn client_exposes_shared_wallet_manager() { - let mut config = ClientConfig::default(); - config.network = Network::Dash; - config.enable_filters = false; - config.enable_masternodes = false; - config.enable_mempool_tracking = false; + let config = ClientConfig { + network: Network::Dash, + enable_filters: false, + enable_masternodes: false, + enable_mempool_tracking: false, + ..Default::default() + }; let network_manager = MockNetworkManager::new(); let storage = MemoryStorageManager::new().await.expect("memory storage should initialize"); diff --git a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h index bdbde3fbc..dae86e8a6 100644 --- a/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h +++ b/swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h @@ -161,6 +161,17 @@ typedef struct FFIEventCallbacks { void *user_data; } FFIEventCallbacks; +/** + * Opaque handle to the wallet manager owned by the SPV client. + * + * This is intentionally zero-sized so it can be used purely as an FFI handle + * while still allowing Rust to cast to the underlying key-wallet manager + * implementation when necessary. + */ +typedef struct FFIWalletManager { + uint8_t _private[0]; +} FFIWalletManager; + /** * Handle for Core SDK that can be passed to Platform SDK */ @@ -546,7 +557,7 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli * * A pointer to the wallet manager wrapper, or NULL if the client is not initialized. */ - FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; + struct FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ; /** * Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. @@ -559,7 +570,7 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli * - `manager` must either be null or a pointer previously returned by * `dash_spv_ffi_client_get_wallet_manager`. */ - void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ; + void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ; struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; diff --git a/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h b/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h index 17527ca2a..344fa14f8 100644 --- a/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h +++ b/swift-dash-core-sdk/Sources/KeyWalletFFI/include/key_wallet_ffi.h @@ -3743,6 +3743,12 @@ char *wallet_manager_describe(const FFIWalletManager *manager, /* Free a string previously returned by wallet manager APIs. + + # Safety + - `value` must be either null or a pointer obtained from + `wallet_manager_describe` (or other wallet manager FFI helpers that + specify this free function). + - The pointer must not be used after this call returns. */ void wallet_manager_free_string(char *value) ;