diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index d87b92a76..2b31a8c22 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**: 67 +**Total Functions**: 87 ## Table of Contents @@ -13,7 +13,6 @@ This document provides a comprehensive reference for all FFI (Foreign Function I - [Synchronization](#synchronization) - [Address Monitoring](#address-monitoring) - [Transaction Management](#transaction-management) -- [Mempool Operations](#mempool-operations) - [Platform Integration](#platform-integration) - [Event Callbacks](#event-callbacks) - [Error Handling](#error-handling) @@ -34,12 +33,33 @@ Functions: 4 ### Configuration -Functions: 25 +Functions: 46 | Function | Description | Module | |----------|-------------|--------| | `dash_spv_ffi_client_update_config` | Update the running client's configuration | client | | `dash_spv_ffi_config_add_peer` | Adds a peer address to the configuration Accepts socket addresses with or... | config | +| `dash_spv_ffi_config_builder_build` | Gets ownership of the builder and returns the built configuration destroying... | config | +| `dash_spv_ffi_config_builder_destroy` | Destroys an FFIConfigBuilder and frees its memory # Safety - `builder` must... | config | +| `dash_spv_ffi_config_builder_devnet` | No description | config | +| `dash_spv_ffi_config_builder_mainnet` | No description | config | +| `dash_spv_ffi_config_builder_regtest` | No description | config | +| `dash_spv_ffi_config_builder_set_fetch_mempool_transactions` | Sets whether to fetch full mempool transaction data # Safety - `builder`... | config | +| `dash_spv_ffi_config_builder_set_filter_load` | Sets whether to load bloom filters # Safety - `builder` must be a valid... | config | +| `dash_spv_ffi_config_builder_set_masternode_sync_enabled` | Enables or disables masternode synchronization # Safety - `builder` must be... | config | +| `dash_spv_ffi_config_builder_set_max_mempool_transactions` | Sets the maximum number of mempool transactions to track # Safety -... | config | +| `dash_spv_ffi_config_builder_set_max_peers` | Sets the maximum number of peers to connect to # Safety - `builder` must be... | config | +| `dash_spv_ffi_config_builder_set_mempool_strategy` | Sets the mempool synchronization strategy # Safety - `builder` must be a... | config | +| `dash_spv_ffi_config_builder_set_mempool_tracking` | Enables or disables mempool tracking # Safety - `builder` must be a valid... | config | +| `dash_spv_ffi_config_builder_set_persist_mempool` | Sets whether to persist mempool state to disk # Safety - `builder` must be... | config | +| `dash_spv_ffi_config_builder_set_relay_transactions` | Sets whether to relay transactions (currently a no-op) # Safety - `builder`... | config | +| `dash_spv_ffi_config_builder_set_restrict_to_configured_peers` | Restrict connections strictly to configured peers (disable DNS discovery and... | config | +| `dash_spv_ffi_config_builder_set_start_from_height` | Sets the starting block height for synchronization # Safety - `builder`... | config | +| `dash_spv_ffi_config_builder_set_storage_path` | Sets the data directory for storing blockchain data # Safety - `builder`... | config | +| `dash_spv_ffi_config_builder_set_user_agent` | Sets the user agent string to advertise in the P2P handshake # Safety -... | config | +| `dash_spv_ffi_config_builder_set_validation_mode` | Sets the validation mode for the SPV client # Safety - `builder` must be a... | config | +| `dash_spv_ffi_config_builder_set_worker_threads` | Sets the number of Tokio worker threads for the FFI runtime (0 = auto) #... | config | +| `dash_spv_ffi_config_builder_testnet` | No description | config | | `dash_spv_ffi_config_destroy` | Destroys an FFIClientConfig and frees its memory # Safety - `config` must... | config | | `dash_spv_ffi_config_get_data_dir` | Gets the data directory path from the configuration # Safety - `config`... | config | | `dash_spv_ffi_config_get_mempool_strategy` | Gets the mempool synchronization strategy # Safety - `config` must be a... | config | @@ -95,14 +115,6 @@ Functions: 3 | `dash_spv_ffi_unconfirmed_transaction_destroy` | Destroys an FFIUnconfirmedTransaction and all its associated resources #... | types | | `dash_spv_ffi_unconfirmed_transaction_destroy_raw_tx` | Destroys the raw transaction bytes allocated for an FFIUnconfirmedTransaction... | types | -### Mempool Operations - -Functions: 1 - -| Function | Description | Module | -|----------|-------------|--------| -| `dash_spv_ffi_client_enable_mempool_tracking` | Enable mempool tracking with a given strategy | client | - ### Platform Integration Functions: 4 @@ -251,10 +263,322 @@ dash_spv_ffi_config_add_peer(config: *mut FFIClientConfig, addr: *const c_char,) ``` **Description:** -Adds a peer address to the configuration Accepts socket addresses with or without port. When no port is specified, the default P2P port for the configured network is used. Supported formats: - IP with port: `192.168.1.1:9999`, `[::1]:19999` - IP without port: `127.0.0.1`, `2001:db8::1` - Hostname with port: `node.example.com:9999` - Hostname without port: `node.example.com` # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call +Adds a peer address to the configuration Accepts socket addresses with or without port. When no port is specified, the default P2P port for the configured network is used. Supported formats: - IP with port: `192.168.1.1:9999`, `[::1]:19999` - IP without port: `127.0.0.1`, `2001:db8::1` - Hostname with port: `node.example.com:9999` - Hostname without port: `node.example.com` # Safety - `config` must be a valid pointer to an FFIClientConfig - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call + +**Safety:** +- `config` must be a valid pointer to an FFIClientConfig - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_build` + +```c +dash_spv_ffi_config_builder_build(builder: *mut FFIClientConfigBuilder,) -> *mut FFIClientConfig +``` + +**Description:** +Gets ownership of the builder and returns the built configuration destroying the builder in the process # Safety - `builder` must be a valid pointer to an FFIConfigBuilder or null - If null, returns default configuration + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder or null - If null, returns default configuration + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_destroy` + +```c +dash_spv_ffi_config_builder_destroy(builder: *mut FFIClientConfigBuilder) -> () +``` + +**Description:** +Destroys an FFIConfigBuilder and frees its memory # Safety - `builder` must be a valid pointer to an FFIConfigBuilder, or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config builder instance if `built()` was not called + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder, or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config builder instance if `built()` was not called + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_devnet` + +```c +dash_spv_ffi_config_builder_devnet() -> *mut FFIClientConfigBuilder +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_mainnet` + +```c +dash_spv_ffi_config_builder_mainnet() -> *mut FFIClientConfigBuilder +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_regtest` + +```c +dash_spv_ffi_config_builder_regtest() -> *mut FFIClientConfigBuilder +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_fetch_mempool_transactions` + +```c +dash_spv_ffi_config_builder_set_fetch_mempool_transactions(builder: *mut FFIClientConfigBuilder, fetch: bool,) -> i32 +``` + +**Description:** +Sets whether to fetch full mempool transaction data # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_filter_load` + +```c +dash_spv_ffi_config_builder_set_filter_load(builder: *mut FFIClientConfigBuilder, load_filters: bool,) -> i32 +``` + +**Description:** +Sets whether to load bloom filters # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_masternode_sync_enabled` + +```c +dash_spv_ffi_config_builder_set_masternode_sync_enabled(builder: *mut FFIClientConfigBuilder, enable: bool,) -> i32 +``` + +**Description:** +Enables or disables masternode synchronization # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_max_mempool_transactions` + +```c +dash_spv_ffi_config_builder_set_max_mempool_transactions(builder: *mut FFIClientConfigBuilder, max_transactions: u32,) -> i32 +``` + +**Description:** +Sets the maximum number of mempool transactions to track # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_max_peers` + +```c +dash_spv_ffi_config_builder_set_max_peers(builder: *mut FFIClientConfigBuilder, max_peers: u32,) -> i32 +``` + +**Description:** +Sets the maximum number of peers to connect to # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_mempool_strategy` + +```c +dash_spv_ffi_config_builder_set_mempool_strategy(builder: *mut FFIClientConfigBuilder, strategy: FFIMempoolStrategy,) -> i32 +``` + +**Description:** +Sets the mempool synchronization strategy # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_mempool_tracking` + +```c +dash_spv_ffi_config_builder_set_mempool_tracking(builder: *mut FFIClientConfigBuilder, enable: bool,) -> i32 +``` + +**Description:** +Enables or disables mempool tracking # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_persist_mempool` + +```c +dash_spv_ffi_config_builder_set_persist_mempool(builder: *mut FFIClientConfigBuilder, persist: bool,) -> i32 +``` + +**Description:** +Sets whether to persist mempool state to disk # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_relay_transactions` + +```c +dash_spv_ffi_config_builder_set_relay_transactions(builder: *mut FFIClientConfigBuilder, _relay: bool,) -> i32 +``` + +**Description:** +Sets whether to relay transactions (currently a no-op) # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_restrict_to_configured_peers` + +```c +dash_spv_ffi_config_builder_set_restrict_to_configured_peers(builder: *mut FFIClientConfigBuilder, restrict_peers: bool,) -> i32 +``` + +**Description:** +Restrict connections strictly to configured peers (disable DNS discovery and peer store) # Safety - `builder` must be a valid pointer to an FFIConfigBuilder + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_start_from_height` + +```c +dash_spv_ffi_config_builder_set_start_from_height(builder: *mut FFIClientConfigBuilder, height: u32,) -> i32 +``` + +**Description:** +Sets the starting block height for synchronization # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_storage_path` + +```c +dash_spv_ffi_config_builder_set_storage_path(builder: *mut FFIClientConfigBuilder, path: *const c_char,) -> i32 +``` + +**Description:** +Sets the data directory for storing blockchain data # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - `path` must be a valid null-terminated C string - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - `path` must be a valid null-terminated C string - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_user_agent` + +```c +dash_spv_ffi_config_builder_set_user_agent(builder: *mut FFIClientConfigBuilder, user_agent: *const c_char,) -> i32 +``` + +**Description:** +Sets the user agent string to advertise in the P2P handshake # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - `user_agent` must be a valid null-terminated C string - The caller must ensure both pointers remain valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - `user_agent` must be a valid null-terminated C string - The caller must ensure both pointers remain valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_validation_mode` + +```c +dash_spv_ffi_config_builder_set_validation_mode(builder: *mut FFIClientConfigBuilder, mode: FFIValidationMode,) -> i32 +``` + +**Description:** +Sets the validation mode for the SPV client # Safety - `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Safety:** +- `builder` must be a valid pointer to an FFIConfigBuilder - The caller must ensure the config pointer remains valid for the duration of this call + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_worker_threads` + +```c +dash_spv_ffi_config_builder_set_worker_threads(builder: *mut FFIClientConfigBuilder, threads: u32,) -> i32 +``` + +**Description:** +Sets the number of Tokio worker threads for the FFI runtime (0 = auto) # Safety - `config` must be a valid pointer to an FFIConfig **Safety:** -- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `addr` must be a valid null-terminated C string containing a socket address or IP-only string - The caller must ensure both pointers remain valid for the duration of this call +- `config` must be a valid pointer to an FFIConfig + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_testnet` + +```c +dash_spv_ffi_config_builder_testnet() -> *mut FFIClientConfigBuilder +``` **Module:** `config` @@ -267,10 +591,10 @@ dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) -> () ``` **Description:** -Destroys an FFIClientConfig and frees its memory # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet, or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config instance +Destroys an FFIClientConfig and frees its memory # Safety - `config` must be a valid pointer to an FFIClientConfig or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config instance **Safety:** -- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet, or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config instance +- `config` must be a valid pointer to an FFIClientConfig or null - After calling this function, the config pointer becomes invalid and must not be used - This function should only be called once per config instance **Module:** `config` @@ -299,10 +623,10 @@ dash_spv_ffi_config_get_mempool_strategy(config: *const FFIClientConfig,) -> FFI ``` **Description:** -Gets the mempool synchronization strategy # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default +Gets the mempool synchronization strategy # Safety - `config` must be a valid pointer to an FFIConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default **Safety:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default +- `config` must be a valid pointer to an FFIConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default **Module:** `config` @@ -315,10 +639,10 @@ dash_spv_ffi_config_get_mempool_tracking(config: *const FFIClientConfig,) -> boo ``` **Description:** -Gets whether mempool tracking is enabled # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null, returns false as default +Gets whether mempool tracking is enabled # Safety - `config` must be a valid pointer to an FFIConfig or null - If null, returns false as default **Safety:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns false as default +- `config` must be a valid pointer to an FFIConfig or null - If null, returns false as default **Module:** `config` @@ -776,24 +1100,6 @@ Destroys the raw transaction bytes allocated for an FFIUnconfirmedTransaction # --- -### Mempool Operations - Detailed - -#### `dash_spv_ffi_client_enable_mempool_tracking` - -```c -dash_spv_ffi_client_enable_mempool_tracking(client: *mut FFIDashSpvClient, strategy: FFIMempoolStrategy,) -> i32 -``` - -**Description:** -Enable mempool tracking with a given strategy. # Safety - `client` must be a valid, non-null pointer. - -**Safety:** -- `client` must be a valid, non-null pointer. - -**Module:** `client` - ---- - ### Platform Integration - Detailed #### `ffi_dash_spv_get_core_handle` diff --git a/dash-spv-ffi/README.md b/dash-spv-ffi/README.md index 29c7c5af4..7454e5a45 100644 --- a/dash-spv-ffi/README.md +++ b/dash-spv-ffi/README.md @@ -80,8 +80,8 @@ dash_spv_ffi_config_destroy(config); - `dash_spv_ffi_config_mainnet()` - Create mainnet config - `dash_spv_ffi_config_testnet()` - Create testnet config - `dash_spv_ffi_config_set_data_dir(config, path)` - Set data directory -- `dash_spv_ffi_config_set_validation_mode(config, mode)` - Set validation mode -- `dash_spv_ffi_config_set_max_peers(config, max)` - Set maximum peers +- `dash_spv_ffi_config_builder_set_validation_mode(config, mode)` - Set validation mode +- `dash_spv_ffi_config_builder_set_max_peers(config, max)` - Set maximum peers - `dash_spv_ffi_config_add_peer(config, addr)` - Add a peer address. Accepts `"ip:port"`, `[ipv6]:port`, or IP-only (defaults to the network port). - `dash_spv_ffi_config_destroy(config)` - Free config memory diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index bdbde3fbc..1e42908f5 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -588,7 +588,7 @@ int32_t dash_spv_ffi_config_set_data_dir(struct FFIClientConfig *config, * - The caller must ensure the config pointer remains valid for the duration of this call */ -int32_t dash_spv_ffi_config_set_validation_mode(struct FFIClientConfig *config, +int32_t dash_spv_ffi_config_builder_set_validation_mode(struct FFIClientConfig *config, enum DashSpvValidationMode mode) ; @@ -600,7 +600,7 @@ int32_t dash_spv_ffi_config_set_validation_mode(struct FFIClientConfig *config, * - The caller must ensure the config pointer remains valid for the duration of this call */ -int32_t dash_spv_ffi_config_set_max_peers(struct FFIClientConfig *config, +int32_t dash_spv_ffi_config_builder_set_max_peers(struct FFIClientConfig *config, uint32_t max_peers) ; @@ -718,7 +718,7 @@ void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) * # Safety * - `config` must be a valid pointer to an FFIClientConfig */ - int32_t dash_spv_ffi_config_set_worker_threads(struct FFIClientConfig *config, uint32_t threads) ; + int32_t dash_spv_ffi_config_builder_set_worker_threads(struct FFIClientConfig *config, uint32_t threads) ; /** * Enables or disables mempool tracking diff --git a/dash-spv-ffi/examples/basic_usage.c b/dash-spv-ffi/examples/basic_usage.c index deae9d3ec..b4d519abf 100644 --- a/dash-spv-ffi/examples/basic_usage.c +++ b/dash-spv-ffi/examples/basic_usage.c @@ -10,19 +10,21 @@ int main() { } // Create a configuration for testnet - FFIClientConfig* config = dash_spv_ffi_config_testnet(); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); + FFIClientConfigBuilder* builder = dash_spv_ffi_config_builder_testnet(); + if (builder == NULL) { + fprintf(stderr, "Failed to create config builder\n"); return 1; } // Set data directory - if (dash_spv_ffi_config_set_data_dir(config, "/tmp/dash-spv-test") != 0) { + if (dash_spv_ffi_config_builder_set_storage_path(builder, "/tmp/dash-spv-test") != 0) { fprintf(stderr, "Failed to set data dir\n"); - dash_spv_ffi_config_destroy(config); + dash_spv_ffi_config_builder_destroy(builder); return 1; } + FFIClientConfig* config = dash_spv_ffi_config_builder_build(builder); + // Create the client FFIDashSpvClient* client = dash_spv_ffi_client_new(config); if (client == NULL) { diff --git a/dash-spv-ffi/examples/wallet_manager_usage.rs b/dash-spv-ffi/examples/wallet_manager_usage.rs index a49cc5af3..c12151868 100644 --- a/dash-spv-ffi/examples/wallet_manager_usage.rs +++ b/dash-spv-ffi/examples/wallet_manager_usage.rs @@ -10,7 +10,8 @@ use key_wallet_ffi::{wallet_manager_wallet_count, FFIError}; fn main() { unsafe { // Create a config for testnet - let config = dash_spv_ffi_config_testnet(); + let config = dash_spv_ffi_config_builder_testnet(); + let config = dash_spv_ffi_config_builder_build(config); if config.is_null() { panic!("Failed to create config"); } diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 4fcf2e12d..dcf0b86fd 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -29,17 +29,17 @@ typedef enum FFISyncStage { Failed = 9, } FFISyncStage; -typedef enum FFIMempoolStrategy { - FetchAll = 0, - BloomFilter = 1, -} FFIMempoolStrategy; - typedef enum DashSpvValidationMode { None = 0, Basic = 1, Full = 2, } DashSpvValidationMode; +typedef enum FFIMempoolStrategy { + FetchAll = 0, + BloomFilter = 1, +} FFIMempoolStrategy; + typedef struct FFIDashSpvClient FFIDashSpvClient; /** @@ -171,6 +171,11 @@ typedef struct FFIWalletManager { uint8_t _private[0]; } FFIWalletManager; +typedef struct FFIClientConfigBuilder { + void *inner; + uint32_t worker_threads; +} FFIClientConfigBuilder; + /** * Handle for Core SDK that can be passed to Platform SDK */ @@ -482,17 +487,6 @@ int32_t dash_spv_ffi_client_rescan_blockchain(struct FFIDashSpvClient *client, uint32_t _from_height) ; -/** - * Enable mempool tracking with a given strategy. - * - * # Safety - * - `client` must be a valid, non-null pointer. - */ - -int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *client, - enum FFIMempoolStrategy strategy) -; - /** * Record that we attempted to send a transaction by its txid. * @@ -534,6 +528,217 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli */ void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ; + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_mainnet(void) ; + + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_testnet(void) ; + + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_devnet(void) ; + + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_regtest(void) ; + +/** + * Sets the data directory for storing blockchain data + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - `path` must be a valid null-terminated C string + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_storage_path(struct FFIClientConfigBuilder *builder, + const char *path) +; + +/** + * Sets the validation mode for the SPV client + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_validation_mode(struct FFIClientConfigBuilder *builder, + enum DashSpvValidationMode mode) +; + +/** + * Sets the maximum number of peers to connect to + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_max_peers(struct FFIClientConfigBuilder *builder, + uint32_t max_peers) +; + +/** + * Sets the user agent string to advertise in the P2P handshake + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - `user_agent` must be a valid null-terminated C string + * - The caller must ensure both pointers remain valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_user_agent(struct FFIClientConfigBuilder *builder, + const char *user_agent) +; + +/** + * Sets whether to relay transactions (currently a no-op) + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_relay_transactions(struct FFIClientConfigBuilder *builder, + bool _relay) +; + +/** + * Sets whether to load bloom filters + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_filter_load(struct FFIClientConfigBuilder *builder, + bool load_filters) +; + +/** + * Restrict connections strictly to configured peers (disable DNS discovery and peer store) + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + */ + +int32_t dash_spv_ffi_config_builder_set_restrict_to_configured_peers(struct FFIClientConfigBuilder *builder, + bool restrict_peers) +; + +/** + * Enables or disables masternode synchronization + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_masternode_sync_enabled(struct FFIClientConfigBuilder *builder, + bool enable) +; + +/** + * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) + * + * # Safety + * - `config` must be a valid pointer to an FFIConfig + */ + +int32_t dash_spv_ffi_config_builder_set_worker_threads(struct FFIClientConfigBuilder *builder, + uint32_t threads) +; + +/** + * Enables or disables mempool tracking + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_mempool_tracking(struct FFIClientConfigBuilder *builder, + bool enable) +; + +/** + * Sets the mempool synchronization strategy + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_mempool_strategy(struct FFIClientConfigBuilder *builder, + enum FFIMempoolStrategy strategy) +; + +/** + * Sets the maximum number of mempool transactions to track + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_max_mempool_transactions(struct FFIClientConfigBuilder *builder, + uint32_t max_transactions) +; + +/** + * Sets whether to fetch full mempool transaction data + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_fetch_mempool_transactions(struct FFIClientConfigBuilder *builder, + bool fetch) +; + +/** + * Sets whether to persist mempool state to disk + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_persist_mempool(struct FFIClientConfigBuilder *builder, + bool persist) +; + +/** + * Sets the starting block height for synchronization + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call + */ + +int32_t dash_spv_ffi_config_builder_set_start_from_height(struct FFIClientConfigBuilder *builder, + uint32_t height) +; + +/** + * Gets ownership of the builder and returns the built configuration destroying the builder in the process + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder or null + * - If null, returns default configuration + */ + +struct FFIClientConfig *dash_spv_ffi_config_builder_build(struct FFIClientConfigBuilder *builder) +; + +/** + * Destroys an FFIConfigBuilder and frees its memory + * + * # Safety + * - `builder` must be a valid pointer to an FFIConfigBuilder, or null + * - After calling this function, the config pointer becomes invalid and must not be used + * - This function should only be called once per config builder instance if `built()` was not called + */ + +void dash_spv_ffi_config_builder_destroy(struct FFIClientConfigBuilder *builder) +; + struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; struct FFIClientConfig *dash_spv_ffi_config_mainnet(void) ; @@ -590,14 +795,11 @@ int32_t dash_spv_ffi_config_set_max_peers(struct FFIClientConfig *config, * - Hostname without port: `node.example.com` * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `config` must be a valid pointer to an FFIClientConfig * - `addr` must be a valid null-terminated C string containing a socket address or IP-only string * - The caller must ensure both pointers remain valid for the duration of this call */ - -int32_t dash_spv_ffi_config_add_peer(struct FFIClientConfig *config, - const char *addr) -; + int32_t dash_spv_ffi_config_add_peer(struct FFIClientConfig *config, const char *addr) ; /** * Sets the user agent string to advertise in the P2P handshake @@ -682,13 +884,11 @@ int32_t dash_spv_ffi_config_set_masternode_sync_enabled(struct FFIClientConfig * * Destroys an FFIClientConfig and frees its memory * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet, or null + * - `config` must be a valid pointer to an FFIClientConfig or null * - After calling this function, the config pointer becomes invalid and must not be used * - This function should only be called once per config instance */ - -void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) -; + void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) ; /** * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) @@ -762,7 +962,7 @@ int32_t dash_spv_ffi_config_set_persist_mempool(struct FFIClientConfig *config, * Gets whether mempool tracking is enabled * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig or null + * - `config` must be a valid pointer to an FFIConfig or null * - If null, returns false as default */ bool dash_spv_ffi_config_get_mempool_tracking(const struct FFIClientConfig *config) ; @@ -771,7 +971,7 @@ int32_t dash_spv_ffi_config_set_persist_mempool(struct FFIClientConfig *config, * Gets the mempool synchronization strategy * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig or null + * - `config` must be a valid pointer to an FFIConfig or null * - If null, returns FFIMempoolStrategy::FetchAll as default */ diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index b84a725dd..154a4ac2d 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -8,7 +8,6 @@ use std::time::Duration; use clap::{Arg, ArgAction, Command, ValueEnum}; use dash_spv_ffi::*; -use key_wallet_ffi::FFINetwork; #[derive(Copy, Clone, Debug, ValueEnum)] enum NetworkOpt { @@ -108,13 +107,21 @@ fn main() { .get_matches(); // Map network - let network = match matches.get_one::("network").map(|s| s.as_str()) { - Some("mainnet") => FFINetwork::Dash, - Some("testnet") => FFINetwork::Testnet, - Some("regtest") => FFINetwork::Regtest, - _ => FFINetwork::Dash, + let cfg_builder = match matches.get_one::("network").map(|s| s.as_str()) { + Some("mainnet") => dash_spv_ffi_config_builder_mainnet(), + Some("testnet") => dash_spv_ffi_config_builder_testnet(), + Some("regtest") => dash_spv_ffi_config_builder_regtest(), + _ => dash_spv_ffi_config_builder_mainnet(), }; + if cfg_builder.is_null() { + eprintln!( + "Failed to allocate config: {}", + ffi_string_to_rust(dash_spv_ffi_get_last_error()) + ); + std::process::exit(1); + } + let disable_filter_sync = matches.get_flag("no-filters"); unsafe { @@ -124,29 +131,22 @@ fn main() { let _ = dash_spv_ffi_init_logging(level_c.as_ptr(), true, std::ptr::null(), 0); // Build config - let cfg = dash_spv_ffi_config_new(network); - if cfg.is_null() { - eprintln!( - "Failed to allocate config: {}", - ffi_string_to_rust(dash_spv_ffi_get_last_error()) - ); - std::process::exit(1); - } - - let _ = dash_spv_ffi_config_set_filter_load(cfg, !disable_filter_sync); + let _ = dash_spv_ffi_config_builder_set_filter_load(cfg_builder, !disable_filter_sync); if let Some(workers) = matches.get_one::("workers") { - let _ = dash_spv_ffi_config_set_worker_threads(cfg, *workers); + let _ = dash_spv_ffi_config_builder_set_worker_threads(cfg_builder, *workers); } if let Some(height) = matches.get_one::("start-height") { - let _ = dash_spv_ffi_config_set_start_from_height(cfg, *height); + let _ = dash_spv_ffi_config_builder_set_start_from_height(cfg_builder, *height); } if matches.get_flag("no-masternodes") { - let _ = dash_spv_ffi_config_set_masternode_sync_enabled(cfg, false); + let _ = dash_spv_ffi_config_builder_set_masternode_sync_enabled(cfg_builder, false); } + let cfg = dash_spv_ffi_config_builder_build(cfg_builder); + if let Some(peers) = matches.get_many::("peer") { for p in peers { let c = CString::new(p.as_str()).unwrap(); diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 8848d188b..953f2e4c1 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -1,6 +1,6 @@ use crate::{ null_check, set_last_error, FFIClientConfig, FFIDetailedSyncProgress, FFIErrorCode, - FFIEventCallbacks, FFIMempoolStrategy, FFISpvStats, FFISyncProgress, FFIWalletManager, + FFIEventCallbacks, FFISpvStats, FFISyncProgress, FFIWalletManager, }; // Import wallet types from key-wallet-ffi use key_wallet_ffi::FFIWalletManager as KeyWalletFFIWalletManager; @@ -147,38 +147,15 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new( } }; - let mut client_config = config.clone_inner(); - - let storage_path = client_config.storage_path.clone().unwrap_or_else(|| { - // Create a unique temporary directory if none was provided - static PATH_COUNTER: AtomicU64 = AtomicU64::new(0); - - let mut path = std::env::temp_dir(); - path.push("dash-spv"); - path.push( - format!( - "{:?}-{}-{}", - client_config.network, - std::process::id(), - PATH_COUNTER.fetch_add(1, Ordering::Relaxed) - ) - .to_lowercase(), - ); - tracing::warn!( - "dash-spv FFI config missing storage path, falling back to temp dir {:?}", - path - ); - path - }); - client_config.storage_path = Some(storage_path.clone()); + let client_config = config.clone_inner(); let client_result = runtime.block_on(async move { // Construct concrete implementations for generics let network = dash_spv::network::PeerNetworkManager::new(&client_config).await; - let storage = DiskStorageManager::new(storage_path.clone()).await; + let storage = DiskStorageManager::new(&client_config).await; let wallet = key_wallet_manager::wallet_manager::WalletManager::< key_wallet::wallet::managed_wallet_info::ManagedWalletInfo, - >::new(client_config.network); + >::new(client_config.network()); let wallet = std::sync::Arc::new(tokio::sync::RwLock::new(wallet)); match (network, storage) { @@ -424,6 +401,8 @@ fn stop_client_internal(client: &mut FFIDashSpvClient) -> Result<(), dash_spv::S /// - `config` must be a valid pointer to an `FFIClientConfig`. /// - The network in `config` must match the client's network; changing networks at runtime is not supported. #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_client_update_config( client: *mut FFIDashSpvClient, config: *const FFIClientConfig, @@ -1253,49 +1232,6 @@ pub unsafe extern "C" fn dash_spv_ffi_client_rescan_blockchain( } } -/// Enable mempool tracking with a given strategy. -/// -/// # Safety -/// - `client` must be a valid, non-null pointer. -#[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_client_enable_mempool_tracking( - client: *mut FFIDashSpvClient, - strategy: FFIMempoolStrategy, -) -> i32 { - null_check!(client); - - let client = &(*client); - let inner = client.inner.clone(); - - let mempool_strategy = strategy.into(); - - let result = client.runtime.block_on(async { - let mut spv_client = { - let mut guard = inner.lock().unwrap(); - match guard.take() { - Some(client) => client, - None => { - return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound( - "Client not initialized".to_string(), - ))) - } - } - }; - let res = spv_client.enable_mempool_tracking(mempool_strategy).await; - let mut guard = inner.lock().unwrap(); - *guard = Some(spv_client); - res - }); - - match result { - Ok(()) => FFIErrorCode::Success as i32, - Err(e) => { - set_last_error(&e.to_string()); - FFIErrorCode::from(e) as i32 - } - } -} - /// Record that we attempted to send a transaction by its txid. /// /// # Safety diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 2e9133e08..7537d7f80 100644 --- a/dash-spv-ffi/src/config.rs +++ b/dash-spv-ffi/src/config.rs @@ -1,5 +1,5 @@ use crate::{null_check, set_last_error, FFIErrorCode, FFIMempoolStrategy, FFIString}; -use dash_spv::{ClientConfig, ValidationMode}; +use dash_spv::{ClientConfig, ClientConfigBuilder, ValidationMode}; use key_wallet_ffi::FFINetwork; use std::ffi::CStr; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; @@ -30,9 +30,384 @@ pub struct FFIClientConfig { pub worker_threads: u32, } +impl From for FFIClientConfig { + fn from(config: ClientConfig) -> Self { + let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; + Self { + inner, + worker_threads: 0, + } + } +} + +#[repr(C)] +pub struct FFIClientConfigBuilder { + // Opaque pointer to avoid exposing internal ClientConfigBuilder in generated C headers + inner: *mut std::ffi::c_void, + // Tokio runtime worker thread count (0 = auto) + pub worker_threads: u32, +} + +impl From for FFIClientConfigBuilder { + fn from(builder: ClientConfigBuilder) -> Self { + let inner = Box::into_raw(Box::new(builder)) as *mut std::ffi::c_void; + Self { + inner, + worker_threads: 0, + } + } +} + +#[no_mangle] +pub extern "C" fn dash_spv_ffi_config_builder_mainnet() -> *mut FFIClientConfigBuilder { + Box::into_raw(Box::new(ClientConfigBuilder::mainnet().into())) +} + +#[no_mangle] +pub extern "C" fn dash_spv_ffi_config_builder_testnet() -> *mut FFIClientConfigBuilder { + Box::into_raw(Box::new(ClientConfigBuilder::testnet().into())) +} + +#[no_mangle] +pub extern "C" fn dash_spv_ffi_config_builder_devnet() -> *mut FFIClientConfigBuilder { + Box::into_raw(Box::new(ClientConfigBuilder::devnet().into())) +} + +#[no_mangle] +pub extern "C" fn dash_spv_ffi_config_builder_regtest() -> *mut FFIClientConfigBuilder { + Box::into_raw(Box::new(ClientConfigBuilder::regtest().into())) +} + +/// Sets the data directory for storing blockchain data +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - `path` must be a valid null-terminated C string +/// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_storage_path( + builder: *mut FFIClientConfigBuilder, + path: *const c_char, +) -> i32 { + null_check!(builder); + null_check!(path); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + match CStr::from_ptr(path).to_str() { + Ok(path_str) => { + builder.storage_path(path_str); + FFIErrorCode::Success as i32 + } + Err(e) => { + set_last_error(&format!("Invalid UTF-8 in path: {}", e)); + FFIErrorCode::InvalidArgument as i32 + } + } +} + +/// Sets the validation mode for the SPV client +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_validation_mode( + builder: *mut FFIClientConfigBuilder, + mode: FFIValidationMode, +) -> i32 { + null_check!(builder); + + let config = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + config.validation_mode(mode.into()); + FFIErrorCode::Success as i32 +} + +/// Sets the maximum number of peers to connect to +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_peers( + builder: *mut FFIClientConfigBuilder, + max_peers: u32, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.max_peers(max_peers); + FFIErrorCode::Success as i32 +} + +/// Sets the user agent string to advertise in the P2P handshake +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - `user_agent` must be a valid null-terminated C string +/// - The caller must ensure both pointers remain valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_user_agent( + builder: *mut FFIClientConfigBuilder, + user_agent: *const c_char, +) -> i32 { + null_check!(builder); + null_check!(user_agent); + + // Validate the user_agent string + match CStr::from_ptr(user_agent).to_str() { + Ok(agent_str) => { + // Store as-is; normalization/length capping is applied at handshake build time + let cfg = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + cfg.user_agent(agent_str.to_string()); + FFIErrorCode::Success as i32 + } + Err(e) => { + set_last_error(&format!("Invalid UTF-8 in user agent: {}", e)); + FFIErrorCode::InvalidArgument as i32 + } + } +} + +/// Sets whether to relay transactions (currently a no-op) +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_relay_transactions( + builder: *mut FFIClientConfigBuilder, + _relay: bool, +) -> i32 { + null_check!(builder); + + let _builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + // relay_transactions not directly settable in current ClientConfig + FFIErrorCode::Success as i32 +} + +/// Sets whether to load bloom filters +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_filter_load( + builder: *mut FFIClientConfigBuilder, + load_filters: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.enable_filters(load_filters); + FFIErrorCode::Success as i32 +} + +/// Restrict connections strictly to configured peers (disable DNS discovery and peer store) +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_restrict_to_configured_peers( + builder: *mut FFIClientConfigBuilder, + restrict_peers: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.restrict_to_configured_peers(restrict_peers); + FFIErrorCode::Success as i32 +} + +/// Enables or disables masternode synchronization +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_masternode_sync_enabled( + builder: *mut FFIClientConfigBuilder, + enable: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.enable_masternodes(enable); + FFIErrorCode::Success as i32 +} + +/// Sets the number of Tokio worker threads for the FFI runtime (0 = auto) +/// +/// # Safety +/// - `config` must be a valid pointer to an FFIConfig +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_worker_threads( + builder: *mut FFIClientConfigBuilder, + threads: u32, +) -> i32 { + null_check!(builder); + let cfg = &mut *builder; + cfg.worker_threads = threads; + FFIErrorCode::Success as i32 +} + +/// Enables or disables mempool tracking +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_tracking( + builder: *mut FFIClientConfigBuilder, + enable: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.enable_mempool_tracking(enable); + FFIErrorCode::Success as i32 +} + +/// Sets the mempool synchronization strategy +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_strategy( + builder: *mut FFIClientConfigBuilder, + strategy: FFIMempoolStrategy, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.mempool_strategy(strategy.into()); + FFIErrorCode::Success as i32 +} + +/// Sets the maximum number of mempool transactions to track +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_mempool_transactions( + builder: *mut FFIClientConfigBuilder, + max_transactions: u32, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.max_mempool_transactions(max_transactions as usize); + FFIErrorCode::Success as i32 +} + +/// Sets whether to fetch full mempool transaction data +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_fetch_mempool_transactions( + builder: *mut FFIClientConfigBuilder, + fetch: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.fetch_mempool_transactions(fetch); + FFIErrorCode::Success as i32 +} + +/// Sets whether to persist mempool state to disk +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_persist_mempool( + builder: *mut FFIClientConfigBuilder, + persist: bool, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.persist_mempool(persist); + FFIErrorCode::Success as i32 +} + +/// Sets the starting block height for synchronization +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder +/// - The caller must ensure the config pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_start_from_height( + builder: *mut FFIClientConfigBuilder, + height: u32, +) -> i32 { + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; + builder.start_from_height(height); + FFIErrorCode::Success as i32 +} + +/// Gets ownership of the builder and returns the built configuration destroying the builder in the process +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder or null +/// - If null, returns default configuration +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_build( + builder: *mut FFIClientConfigBuilder, +) -> *mut FFIClientConfig { + if builder.is_null() { + return Box::into_raw(Box::new(ClientConfig::default().into())); + } + + let ffi_builder = Box::from_raw(builder); + let builder = Box::from_raw(ffi_builder.inner as *mut ClientConfigBuilder); + + match builder.build() { + Ok(config) => { + let mut config = FFIClientConfig::from(config); + config.worker_threads = ffi_builder.worker_threads; + Box::into_raw(Box::new(config)) + } + Err(err) => { + set_last_error(&format!("Failed to build config: {}", err)); + std::ptr::null_mut() + } + } +} + +/// Destroys an FFIConfigBuilder and frees its memory +/// +/// # Safety +/// - `builder` must be a valid pointer to an FFIConfigBuilder, or null +/// - After calling this function, the config pointer becomes invalid and must not be used +/// - This function should only be called once per config builder instance if `built()` was not called +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_builder_destroy(builder: *mut FFIClientConfigBuilder) { + if !builder.is_null() { + let builder = Box::from_raw(builder); + if !builder.inner.is_null() { + let _ = Box::from_raw(builder.inner as *mut ClientConfigBuilder); + } + } +} + +/***************** Config methods *****************/ +// Deprecated methods are kept in the codebase for retro compatibility +// but is encourage to use the config builder. Config is meant to be used +// only in construction and not queried in runtime + +#[no_mangle] +#[deprecated] +#[allow(deprecated)] pub extern "C" fn dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClientConfig { - let config = ClientConfig::new(network.into()); + let config = + ClientConfigBuilder::new(network.into()).build().expect("Constructor are always valid"); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -41,8 +416,10 @@ pub extern "C" fn dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClient } #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { - let config = ClientConfig::mainnet(); + let config = ClientConfigBuilder::mainnet().build().expect("Constructor are always valid"); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -51,8 +428,10 @@ pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { } #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub extern "C" fn dash_spv_ffi_config_testnet() -> *mut FFIClientConfig { - let config = ClientConfig::testnet(); + let config = ClientConfigBuilder::testnet().build().expect("Constructor are always valid"); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -67,6 +446,8 @@ pub extern "C" fn dash_spv_ffi_config_testnet() -> *mut FFIClientConfig { /// - `path` must be a valid null-terminated C string /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_data_dir( config: *mut FFIClientConfig, path: *const c_char, @@ -77,7 +458,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_data_dir( let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; match CStr::from_ptr(path).to_str() { Ok(path_str) => { - config.storage_path = Some(path_str.into()); + config.set_storage_path(path_str); FFIErrorCode::Success as i32 } Err(e) => { @@ -93,6 +474,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_data_dir( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_validation_mode( config: *mut FFIClientConfig, mode: FFIValidationMode, @@ -100,7 +483,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_validation_mode( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.validation_mode = mode.into(); + config.set_validation_mode(mode.into()); FFIErrorCode::Success as i32 } @@ -110,6 +493,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_validation_mode( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_max_peers( config: *mut FFIClientConfig, max_peers: u32, @@ -117,7 +502,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_peers( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.max_peers = max_peers; + config.set_max_peers(max_peers); FFIErrorCode::Success as i32 } @@ -135,7 +520,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_peers( /// - Hostname without port: `node.example.com` /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `config` must be a valid pointer to an FFIClientConfig /// - `addr` must be a valid null-terminated C string containing a socket address or IP-only string /// - The caller must ensure both pointers remain valid for the duration of this call #[no_mangle] @@ -147,7 +532,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( null_check!(addr); let cfg = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - let default_port = match cfg.network { + let default_port = match cfg.network() { dashcore::Network::Dash => 9999, dashcore::Network::Testnet => 19999, dashcore::Network::Regtest => 19899, @@ -166,7 +551,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( // Try parsing as bare IP address and apply default port if let Ok(ip) = addr_str.parse::() { let sock = SocketAddr::new(ip, default_port); - cfg.peers.push(sock); + cfg.add_peer(sock); return FFIErrorCode::Success as i32; } @@ -185,7 +570,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( match addr_with_port.to_socket_addrs() { Ok(mut iter) => match iter.next() { Some(sock) => { - cfg.peers.push(sock); + cfg.add_peer(sock); FFIErrorCode::Success as i32 } None => { @@ -207,6 +592,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( /// - `user_agent` must be a valid null-terminated C string /// - The caller must ensure both pointers remain valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_user_agent( config: *mut FFIClientConfig, user_agent: *const c_char, @@ -219,7 +606,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_user_agent( Ok(agent_str) => { // Store as-is; normalization/length capping is applied at handshake build time let cfg = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - cfg.user_agent = Some(agent_str.to_string()); + cfg.set_user_agent(agent_str); FFIErrorCode::Success as i32 } Err(e) => { @@ -235,6 +622,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_user_agent( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_relay_transactions( config: *mut FFIClientConfig, _relay: bool, @@ -252,6 +641,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_relay_transactions( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_filter_load( config: *mut FFIClientConfig, load_filters: bool, @@ -259,7 +650,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_filter_load( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.enable_filters = load_filters; + config.set_enable_filters(load_filters); FFIErrorCode::Success as i32 } @@ -268,6 +659,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_filter_load( /// # Safety /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_restrict_to_configured_peers( config: *mut FFIClientConfig, restrict_peers: bool, @@ -275,7 +668,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_restrict_to_configured_peers( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.restrict_to_configured_peers = restrict_peers; + config.set_restrict_to_configured_peers(restrict_peers); FFIErrorCode::Success as i32 } @@ -285,6 +678,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_restrict_to_configured_peers( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_masternode_sync_enabled( config: *mut FFIClientConfig, enable: bool, @@ -292,7 +687,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_masternode_sync_enabled( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.enable_masternodes = enable; + config.set_enable_masternodes(enable); FFIErrorCode::Success as i32 } @@ -310,7 +705,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_network( } let config = unsafe { &*((*config).inner as *const ClientConfig) }; - config.network.into() + config.network().into() } /// Gets the data directory path from the configuration @@ -331,19 +726,13 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir( } let config = unsafe { &*((*config).inner as *const ClientConfig) }; - match &config.storage_path { - Some(dir) => FFIString::new(&dir.to_string_lossy()), - None => FFIString { - ptr: std::ptr::null_mut(), - length: 0, - }, - } + FFIString::new(&config.storage_path().to_string_lossy()) } /// Destroys an FFIClientConfig and frees its memory /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet, or null +/// - `config` must be a valid pointer to an FFIClientConfig or null /// - After calling this function, the config pointer becomes invalid and must not be used /// - This function should only be called once per config instance #[no_mangle] @@ -373,6 +762,8 @@ impl FFIClientConfig { /// # Safety /// - `config` must be a valid pointer to an FFIClientConfig #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_worker_threads( config: *mut FFIClientConfig, threads: u32, @@ -391,6 +782,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_worker_threads( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_tracking( config: *mut FFIClientConfig, enable: bool, @@ -398,7 +791,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_tracking( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.enable_mempool_tracking = enable; + config.set_enable_mempool_tracking(enable); FFIErrorCode::Success as i32 } @@ -408,6 +801,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_tracking( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_strategy( config: *mut FFIClientConfig, strategy: FFIMempoolStrategy, @@ -415,7 +810,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_strategy( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.mempool_strategy = strategy.into(); + config.set_mempool_strategy(strategy.into()); FFIErrorCode::Success as i32 } @@ -425,6 +820,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_strategy( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_max_mempool_transactions( config: *mut FFIClientConfig, max_transactions: u32, @@ -432,7 +829,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_mempool_transactions( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.max_mempool_transactions = max_transactions as usize; + config.set_max_mempool_transactions(max_transactions as usize); FFIErrorCode::Success as i32 } @@ -442,6 +839,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_mempool_transactions( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_fetch_mempool_transactions( config: *mut FFIClientConfig, fetch: bool, @@ -449,7 +848,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_fetch_mempool_transactions( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.fetch_mempool_transactions = fetch; + config.set_fetch_mempool_transactions(fetch); FFIErrorCode::Success as i32 } @@ -459,6 +858,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_fetch_mempool_transactions( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_persist_mempool( config: *mut FFIClientConfig, persist: bool, @@ -466,14 +867,14 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_persist_mempool( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.persist_mempool = persist; + config.set_persist_mempool(persist); FFIErrorCode::Success as i32 } /// Gets whether mempool tracking is enabled /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig or null +/// - `config` must be a valid pointer to an FFIConfig or null /// - If null, returns false as default #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_tracking( @@ -484,13 +885,13 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_tracking( } let config = unsafe { &*((*config).inner as *const ClientConfig) }; - config.enable_mempool_tracking + config.enable_mempool_tracking() } /// Gets the mempool synchronization strategy /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig or null +/// - `config` must be a valid pointer to an FFIConfig or null /// - If null, returns FFIMempoolStrategy::FetchAll as default #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( @@ -501,7 +902,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( } let config = unsafe { &*((*config).inner as *const ClientConfig) }; - config.mempool_strategy.into() + config.mempool_strategy().into() } // Checkpoint sync configuration functions @@ -512,6 +913,8 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( /// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet /// - The caller must ensure the config pointer remains valid for the duration of this call #[no_mangle] +#[deprecated] +#[allow(deprecated)] pub unsafe extern "C" fn dash_spv_ffi_config_set_start_from_height( config: *mut FFIClientConfig, height: u32, @@ -519,6 +922,6 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_start_from_height( null_check!(config); let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; - config.start_from_height = Some(height); + config.set_start_from_height(height); FFIErrorCode::Success as i32 } diff --git a/dash-spv-ffi/src/types.rs b/dash-spv-ffi/src/types.rs index e05504e59..6852e4452 100644 --- a/dash-spv-ffi/src/types.rs +++ b/dash-spv-ffi/src/types.rs @@ -1,5 +1,5 @@ -use dash_spv::client::config::MempoolStrategy; use dash_spv::types::{DetailedSyncProgress, MempoolRemovalReason, SyncStage}; +use dash_spv::MempoolStrategy; use dash_spv::{ChainState, PeerInfo, SpvStats, SyncProgress}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; diff --git a/dash-spv-ffi/tests/c_tests/test_basic.c b/dash-spv-ffi/tests/c_tests/test_basic.c index ad451e8d4..c0bb8b592 100644 --- a/dash-spv-ffi/tests/c_tests/test_basic.c +++ b/dash-spv-ffi/tests/c_tests/test_basic.c @@ -55,11 +55,11 @@ void test_config_setters() { TEST_ASSERT(result == FFIErrorCode_Success); // Test setting validation mode - result = dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode_Basic); + result = dash_spv_ffi_config_builder_set_validation_mode(config, FFIValidationMode_Basic); TEST_ASSERT(result == FFIErrorCode_Success); // Test setting max peers - result = dash_spv_ffi_config_set_max_peers(config, 16); + result = dash_spv_ffi_config_builder_set_max_peers(config, 16); TEST_ASSERT(result == FFIErrorCode_Success); // Test adding peers @@ -222,8 +222,8 @@ void test_null_pointer_handling() { // Config functions TEST_ASSERT(dash_spv_ffi_config_set_data_dir(NULL, NULL) == FFIErrorCode_NullPointer); - TEST_ASSERT(dash_spv_ffi_config_set_validation_mode(NULL, FFIValidationMode_Basic) == FFIErrorCode_NullPointer); - TEST_ASSERT(dash_spv_ffi_config_set_max_peers(NULL, 10) == FFIErrorCode_NullPointer); + TEST_ASSERT(dash_spv_ffi_config_builder_set_validation_mode(NULL, FFIValidationMode_Basic) == FFIErrorCode_NullPointer); + TEST_ASSERT(dash_spv_ffi_config_builder_set_max_peers(NULL, 10) == FFIErrorCode_NullPointer); TEST_ASSERT(dash_spv_ffi_config_add_peer(NULL, NULL) == FFIErrorCode_NullPointer); // Client functions diff --git a/dash-spv-ffi/tests/c_tests/test_configuration.c b/dash-spv-ffi/tests/c_tests/test_configuration.c index 5535fd7f5..bd3e81c36 100644 --- a/dash-spv-ffi/tests/c_tests/test_configuration.c +++ b/dash-spv-ffi/tests/c_tests/test_configuration.c @@ -33,7 +33,7 @@ void test_worker_threads_basic() { TEST_ASSERT(config != NULL); // Test setting worker threads to 0 (auto mode) - int result = dash_spv_ffi_config_set_worker_threads(config, 0); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, 0); TEST_ASSERT(result == FFIErrorCode_Success); // Test setting specific worker thread counts @@ -41,7 +41,7 @@ void test_worker_threads_basic() { size_t num_counts = sizeof(thread_counts) / sizeof(thread_counts[0]); for (size_t i = 0; i < num_counts; i++) { - result = dash_spv_ffi_config_set_worker_threads(config, thread_counts[i]); + result = dash_spv_ffi_config_builder_set_worker_threads(config, thread_counts[i]); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -53,7 +53,7 @@ void test_worker_threads_null_config() { TEST_START("test_worker_threads_null_config"); // Test with null config pointer - int result = dash_spv_ffi_config_set_worker_threads(NULL, 4); + int result = dash_spv_ffi_config_builder_set_worker_threads(NULL, 4); TEST_ASSERT(result == FFIErrorCode_NullPointer); // Check error was set @@ -71,15 +71,15 @@ void test_worker_threads_extreme_values() { TEST_ASSERT(config != NULL); // Test large worker thread count - int result = dash_spv_ffi_config_set_worker_threads(config, 1000); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, 1000); TEST_ASSERT(result == FFIErrorCode_Success); // Test maximum value - result = dash_spv_ffi_config_set_worker_threads(config, UINT32_MAX); + result = dash_spv_ffi_config_builder_set_worker_threads(config, UINT32_MAX); TEST_ASSERT(result == FFIErrorCode_Success); // Test back to reasonable value - result = dash_spv_ffi_config_set_worker_threads(config, 4); + result = dash_spv_ffi_config_builder_set_worker_threads(config, 4); TEST_ASSERT(result == FFIErrorCode_Success); dash_spv_ffi_config_destroy(config); @@ -98,7 +98,7 @@ void test_worker_threads_with_client_creation() { TEST_ASSERT(config != NULL); // Set worker threads - int result = dash_spv_ffi_config_set_worker_threads(config, thread_counts[i]); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, thread_counts[i]); TEST_ASSERT(result == FFIErrorCode_Success); // Set up config for client creation @@ -107,7 +107,7 @@ void test_worker_threads_with_client_creation() { result = dash_spv_ffi_config_set_data_dir(config, temp_path); TEST_ASSERT(result == FFIErrorCode_Success); - result = dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode_None); + result = dash_spv_ffi_config_builder_set_validation_mode(config, FFIValidationMode_None); TEST_ASSERT(result == FFIErrorCode_Success); // Create client - should succeed regardless of worker thread count @@ -141,7 +141,7 @@ void test_worker_threads_multiple_configs() { for (size_t i = 0; i < num_pairs; i++) { TEST_ASSERT(pairs[i].config != NULL); - int result = dash_spv_ffi_config_set_worker_threads(pairs[i].config, pairs[i].thread_count); + int result = dash_spv_ffi_config_builder_set_worker_threads(pairs[i].config, pairs[i].thread_count); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -161,7 +161,7 @@ void test_worker_threads_repeated_setting() { // Test repeated setting of worker threads for (int i = 0; i < 10; i++) { - int result = dash_spv_ffi_config_set_worker_threads(config, 4); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, 4); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -170,7 +170,7 @@ void test_worker_threads_repeated_setting() { size_t sequence_len = sizeof(sequence) / sizeof(sequence[0]); for (size_t i = 0; i < sequence_len; i++) { - int result = dash_spv_ffi_config_set_worker_threads(config, sequence[i]); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, sequence[i]); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -190,7 +190,7 @@ void test_worker_threads_performance() { for (int i = 0; i < num_calls; i++) { uint32_t thread_count = (i % 8) + 1; // 1-8 threads - int result = dash_spv_ffi_config_set_worker_threads(config, thread_count); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, thread_count); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -219,11 +219,11 @@ void test_worker_threads_edge_cases() { TEST_ASSERT(config != NULL); // Set worker threads - int result = dash_spv_ffi_config_set_worker_threads(config, 2); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, 2); TEST_ASSERT(result == FFIErrorCode_Success); // Test setting to 0 (auto) - result = dash_spv_ffi_config_set_worker_threads(config, 0); + result = dash_spv_ffi_config_builder_set_worker_threads(config, 0); TEST_ASSERT(result == FFIErrorCode_Success); dash_spv_ffi_config_destroy(config); @@ -245,7 +245,7 @@ void test_worker_threads_memory_safety() { size_t num_counts = sizeof(counts) / sizeof(counts[0]); for (size_t i = 0; i < num_counts; i++) { - int result = dash_spv_ffi_config_set_worker_threads(config, counts[i]); + int result = dash_spv_ffi_config_builder_set_worker_threads(config, counts[i]); TEST_ASSERT(result == FFIErrorCode_Success); } @@ -256,7 +256,7 @@ void test_worker_threads_memory_safety() { } int main() { - printf("=== C Tests for dash_spv_ffi_config_set_worker_threads ===\n"); + printf("=== C Tests for dash_spv_ffi_config_builder_set_worker_threads ===\n"); test_worker_threads_basic(); test_worker_threads_null_config(); diff --git a/dash-spv-ffi/tests/c_tests/test_event_draining.c b/dash-spv-ffi/tests/c_tests/test_event_draining.c index 48e404fe3..d8869ad8b 100644 --- a/dash-spv-ffi/tests/c_tests/test_event_draining.c +++ b/dash-spv-ffi/tests/c_tests/test_event_draining.c @@ -37,7 +37,7 @@ FFIDashSpvClient* create_simple_test_client() { TEST_ASSERT(result == FFIErrorCode_Success); // Set validation mode to none for faster testing - result = dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode_None); + result = dash_spv_ffi_config_builder_set_validation_mode(config, FFIValidationMode_None); TEST_ASSERT(result == FFIErrorCode_Success); // Create client diff --git a/dash-spv-ffi/tests/c_tests/test_integration.c b/dash-spv-ffi/tests/c_tests/test_integration.c index 7b33cb8e1..99b08762c 100644 --- a/dash-spv-ffi/tests/c_tests/test_integration.c +++ b/dash-spv-ffi/tests/c_tests/test_integration.c @@ -59,8 +59,8 @@ void test_full_workflow() { // Configure client dash_spv_ffi_config_set_data_dir(ctx.config, "/tmp/dash-spv-integration"); - dash_spv_ffi_config_set_validation_mode(ctx.config, FFIValidationMode_Basic); - dash_spv_ffi_config_set_max_peers(ctx.config, 8); + dash_spv_ffi_config_builder_set_validation_mode(ctx.config, FFIValidationMode_Basic); + dash_spv_ffi_config_builder_set_max_peers(ctx.config, 8); // Add some test peers dash_spv_ffi_config_add_peer(ctx.config, "127.0.0.1:19999"); diff --git a/dash-spv-ffi/tests/integration/test_full_workflow.rs b/dash-spv-ffi/tests/integration/test_full_workflow.rs index cf0ceb09c..2e9d342ee 100644 --- a/dash-spv-ffi/tests/integration/test_full_workflow.rs +++ b/dash-spv-ffi/tests/integration/test_full_workflow.rs @@ -25,8 +25,8 @@ mod tests { let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::Basic); - dash_spv_ffi_config_set_max_peers(config, 8); + dash_spv_ffi_config_builder_set_validation_mode(config, FFIValidationMode::Basic); + dash_spv_ffi_config_builder_set_max_peers(config, 8); // Add some test peers if available let test_peers = [ diff --git a/dash-spv-ffi/tests/security/test_security.rs b/dash-spv-ffi/tests/security/test_security.rs index 824a97c87..88c5bb612 100644 --- a/dash-spv-ffi/tests/security/test_security.rs +++ b/dash-spv-ffi/tests/security/test_security.rs @@ -50,7 +50,7 @@ mod tests { // Config functions assert_eq!(dash_spv_ffi_config_set_data_dir(ptr::null_mut(), ptr::null()), FFIErrorCode::NullPointer as i32); - assert_eq!(dash_spv_ffi_config_set_validation_mode(ptr::null_mut(), FFIValidationMode::Basic), + assert_eq!(dash_spv_ffi_config_builder_set_validation_mode(ptr::null_mut(), FFIValidationMode::Basic), FFIErrorCode::NullPointer as i32); assert_eq!(dash_spv_ffi_config_add_peer(ptr::null_mut(), ptr::null()), FFIErrorCode::NullPointer as i32); @@ -74,9 +74,11 @@ mod tests { fn test_use_after_free_prevention() { unsafe { let temp_dir = TempDir::new().unwrap(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_data_dir(builder, path.as_ptr()); + + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); @@ -93,7 +95,7 @@ mod tests { dash_spv_ffi_config_destroy(config); // Using config after free should fail - let result = dash_spv_ffi_config_set_max_peers(config, 10); + let result = dash_spv_ffi_config_builder_set_max_peers(builder, 10); assert_ne!(result, FFIErrorCode::Success as i32); } } @@ -106,7 +108,7 @@ mod tests { let config = dash_spv_ffi_config_new(FFINetwork::Regtest); // Test setting max peers to u32::MAX - let result = dash_spv_ffi_config_set_max_peers(config, u32::MAX); + let result = dash_spv_ffi_config_builder_set_max_peers(config, u32::MAX); assert_eq!(result, FFIErrorCode::Success as i32); // Test large array allocation diff --git a/dash-spv-ffi/tests/test_client.rs b/dash-spv-ffi/tests/test_client.rs index 532238559..5a3e5c106 100644 --- a/dash-spv-ffi/tests/test_client.rs +++ b/dash-spv-ffi/tests/test_client.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use dash_spv_ffi::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::CString; use std::os::raw::c_void; @@ -35,13 +34,14 @@ mod tests { fn create_test_config() -> (*mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); - unsafe { + let config = unsafe { let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); - } + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + dash_spv_ffi_config_builder_build(builder) + }; (config, temp_dir) } @@ -155,10 +155,11 @@ mod tests { } // Create testnet config for the diagnostic test - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); let temp_dir = TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + let config = dash_spv_ffi_config_builder_build(builder); // Enable test mode to use deterministic peers dash_spv_ffi_enable_test_mode(); diff --git a/dash-spv-ffi/tests/test_config.rs b/dash-spv-ffi/tests/test_config.rs deleted file mode 100644 index 4531ed109..000000000 --- a/dash-spv-ffi/tests/test_config.rs +++ /dev/null @@ -1,151 +0,0 @@ -#[cfg(test)] -mod tests { - use dash_spv_ffi::*; - use key_wallet_ffi::FFINetwork; - use serial_test::serial; - use std::ffi::CString; - - #[test] - #[serial] - fn test_config_creation() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - assert!(!config.is_null()); - - let network = dash_spv_ffi_config_get_network(config); - assert_eq!(network as i32, FFINetwork::Testnet as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_mainnet() { - unsafe { - let config = dash_spv_ffi_config_mainnet(); - assert!(!config.is_null()); - - let network = dash_spv_ffi_config_get_network(config); - assert_eq!(network as i32, FFINetwork::Dash as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_testnet() { - unsafe { - let config = dash_spv_ffi_config_testnet(); - assert!(!config.is_null()); - - let network = dash_spv_ffi_config_get_network(config); - assert_eq!(network as i32, FFINetwork::Testnet as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_set_data_dir() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - - let path = CString::new("/tmp/dash-spv-test").unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - assert_eq!(result, FFIErrorCode::Success as i32); - - let data_dir = dash_spv_ffi_config_get_data_dir(config); - if !data_dir.ptr.is_null() { - let dir_str = FFIString::from_ptr(data_dir.ptr).unwrap(); - assert_eq!(dir_str, "/tmp/dash-spv-test"); - dash_spv_ffi_string_destroy(data_dir); - } - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_null_checks() { - unsafe { - let result = dash_spv_ffi_config_set_data_dir(std::ptr::null_mut(), std::ptr::null()); - assert_eq!(result, FFIErrorCode::NullPointer as i32); - - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - let result = dash_spv_ffi_config_set_data_dir(config, std::ptr::null()); - assert_eq!(result, FFIErrorCode::NullPointer as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_validation_mode() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - - let result = dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::Full); - assert_eq!(result, FFIErrorCode::Success as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_peers() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - - let result = dash_spv_ffi_config_set_max_peers(config, 10); - assert_eq!(result, FFIErrorCode::Success as i32); - - // min_peers not available in dash-spv, only max_peers - - let peer_addr = CString::new("127.0.0.1:9999").unwrap(); - let result = dash_spv_ffi_config_add_peer(config, peer_addr.as_ptr()); - assert_eq!(result, FFIErrorCode::Success as i32); - - let invalid_addr = CString::new("not-an-address").unwrap(); - let result = dash_spv_ffi_config_add_peer(config, invalid_addr.as_ptr()); - assert_eq!(result, FFIErrorCode::InvalidArgument as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_user_agent() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - - let agent = CString::new("TestAgent/1.0").unwrap(); - let result = dash_spv_ffi_config_set_user_agent(config, agent.as_ptr()); - assert_eq!(result, FFIErrorCode::Success as i32); - - dash_spv_ffi_config_destroy(config); - } - } - - #[test] - #[serial] - fn test_config_booleans() { - unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - - let result = dash_spv_ffi_config_set_relay_transactions(config, true); - assert_eq!(result, FFIErrorCode::Success as i32); - - let result = dash_spv_ffi_config_set_filter_load(config, false); - assert_eq!(result, FFIErrorCode::Success as i32); - - dash_spv_ffi_config_destroy(config); - } - } -} diff --git a/dash-spv-ffi/tests/test_event_callbacks.rs b/dash-spv-ffi/tests/test_event_callbacks.rs index 9e1838038..39daf07e5 100644 --- a/dash-spv-ffi/tests/test_event_callbacks.rs +++ b/dash-spv-ffi/tests/test_event_callbacks.rs @@ -1,6 +1,5 @@ use dash_spv_ffi::callbacks::FFIEventCallbacks; use dash_spv_ffi::*; -use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::{c_char, c_void, CStr, CString}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; @@ -150,15 +149,17 @@ fn test_event_callbacks_setup() { unsafe { // Create config - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_testnet(); + assert!(!builder.is_null()); // Set data directory to temp directory let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); // Set validation mode to basic for faster testing - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::Basic); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::Basic); + + let config = dash_spv_ffi_config_builder_build(builder); // Create client let client = dash_spv_ffi_client_new(config); @@ -237,14 +238,16 @@ fn test_enhanced_event_callbacks() { let event_data = TestEventData::new(); // Create config - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_regtest(); + assert!(!builder.is_null()); // Set data directory let temp_dir = TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); // Create client let client = dash_spv_ffi_client_new(config); @@ -292,14 +295,16 @@ fn test_drain_events_integration() { let event_data = TestEventData::new(); // Create config - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_regtest(); + assert!(!builder.is_null()); // Set data directory let temp_dir = TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); // Create client let client = dash_spv_ffi_client_new(config); @@ -358,13 +363,15 @@ fn test_drain_events_concurrent_with_callbacks() { let event_data = TestEventData::new(); // Create config and client - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_regtest(); + assert!(!builder.is_null()); let temp_dir = TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); @@ -434,13 +441,15 @@ fn test_drain_events_callback_lifecycle() { let event_data = TestEventData::new(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_regtest(); + assert!(!builder.is_null()); let temp_dir = TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); diff --git a/dash-spv-ffi/tests/test_wallet_manager.rs b/dash-spv-ffi/tests/test_wallet_manager.rs index d43da9506..78f676309 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -11,16 +11,24 @@ mod tests { FFIError, FFINetwork, FFIWalletManager, }; use key_wallet_manager::wallet_manager::WalletManager; - use std::ffi::CStr; + use std::ffi::{CStr, CString}; + use tempfile::TempDir; #[test] fn test_get_wallet_manager() { unsafe { // Create a config - let config = dash_spv_ffi_config_testnet(); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_testnet(); + assert!(!builder.is_null()); + + let temp_dir = TempDir::new().unwrap(); + dash_spv_ffi_config_builder_set_storage_path( + builder, + CString::new(temp_dir.path().to_str().unwrap()).unwrap().as_ptr(), + ); // Create a client + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); @@ -48,9 +56,16 @@ mod tests { #[test] fn test_wallet_manager_shared_via_client_imports_wallet() { unsafe { - let config = dash_spv_ffi_config_testnet(); - assert!(!config.is_null()); + let builder = dash_spv_ffi_config_builder_testnet(); + assert!(!builder.is_null()); + + let temp_dir = TempDir::new().unwrap(); + dash_spv_ffi_config_builder_set_storage_path( + builder, + CString::new(temp_dir.path().to_str().unwrap()).unwrap().as_ptr(), + ); + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); @@ -61,7 +76,7 @@ mod tests { // Prepare a serialized wallet using the native manager so we can import it let mut native_manager = - WalletManager::::new((*config).get_inner().network); + WalletManager::::new((*config).get_inner().network()); 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", diff --git a/dash-spv-ffi/tests/unit/test_async_operations.rs b/dash-spv-ffi/tests/unit/test_async_operations.rs index 5546a7151..03c42dde1 100644 --- a/dash-spv-ffi/tests/unit/test_async_operations.rs +++ b/dash-spv-ffi/tests/unit/test_async_operations.rs @@ -2,7 +2,6 @@ mod tests { use crate::types::FFIDetailedSyncProgress; use crate::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; @@ -62,12 +61,14 @@ mod tests { fn create_test_client() -> (*mut FFIDashSpvClient, *mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); - assert!(!config.is_null(), "Failed to create config"); + let builder = dash_spv_ffi_config_builder_regtest(); + assert!(!builder.is_null(), "Failed to create config"); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null(), "Failed to create client"); diff --git a/dash-spv-ffi/tests/unit/test_client_lifecycle.rs b/dash-spv-ffi/tests/unit/test_client_lifecycle.rs index 6d031995b..e89c834da 100644 --- a/dash-spv-ffi/tests/unit/test_client_lifecycle.rs +++ b/dash-spv-ffi/tests/unit/test_client_lifecycle.rs @@ -6,7 +6,6 @@ #[cfg(test)] mod tests { use crate::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::CString; use std::thread; @@ -16,10 +15,11 @@ mod tests { fn create_test_config_with_dir() -> (*mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + let config = dash_spv_ffi_config_builder_build(builder); (config, temp_dir) } } @@ -113,9 +113,11 @@ mod tests { fn test_client_with_no_peers() { unsafe { let temp_dir = TempDir::new().unwrap(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_devnet(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + + let config = dash_spv_ffi_config_builder_build(builder); // Don't add any peers let client = dash_spv_ffi_client_new(config); diff --git a/dash-spv-ffi/tests/unit/test_configuration.rs b/dash-spv-ffi/tests/unit/test_configuration.rs index 695dc3d0b..d1369686f 100644 --- a/dash-spv-ffi/tests/unit/test_configuration.rs +++ b/dash-spv-ffi/tests/unit/test_configuration.rs @@ -5,44 +5,21 @@ mod tests { use serial_test::serial; use std::ffi::{CStr, CString}; - #[test] - #[serial] - fn test_config_with_invalid_network() { - unsafe { - // Test creating config with each valid network - let networks = - [FFINetwork::Dash, FFINetwork::Testnet, FFINetwork::Regtest, FFINetwork::Devnet]; - for net in networks { - let config = dash_spv_ffi_config_new(net); - assert!(!config.is_null()); - let retrieved_net = dash_spv_ffi_config_get_network(config); - assert_eq!(retrieved_net as i32, net as i32); - dash_spv_ffi_config_destroy(config); - } - } - } - #[test] #[serial] fn test_extremely_long_paths() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test with very long path (near filesystem limits) let long_path = format!("/tmp/{}", "x".repeat(4000)); let c_path = CString::new(long_path.clone()).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); // Verify it was set - let retrieved = dash_spv_ffi_config_get_data_dir(config); - if !retrieved.ptr.is_null() { - let path_str = FFIString::from_ptr(retrieved.ptr).unwrap(); - assert_eq!(path_str, long_path); - dash_spv_ffi_string_destroy(retrieved); - } - - dash_spv_ffi_config_destroy(config); + let config = dash_spv_ffi_config_builder_build(builder); + assert!(config.is_null()); } } @@ -50,7 +27,8 @@ mod tests { #[serial] fn test_invalid_peer_addresses() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); + let config = dash_spv_ffi_config_builder_build(builder); // Test various invalid addresses let invalid_addrs = [ @@ -106,7 +84,8 @@ mod tests { #[serial] fn test_adding_maximum_peers() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); + let config = dash_spv_ffi_config_builder_build(builder); // Add many peers for i in 0..1000 { @@ -124,20 +103,21 @@ mod tests { #[serial] fn test_config_with_special_characters_in_paths() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test paths with spaces let path_with_spaces = "/tmp/path with spaces/dash spv"; let c_path = CString::new(path_with_spaces).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); // Test paths with unicode let unicode_path = "/tmp/путь/目录/dossier"; let c_path = CString::new(unicode_path).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -146,26 +126,27 @@ mod tests { #[serial] fn test_relative_vs_absolute_paths() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test relative path let rel_path = "./data/dash-spv"; let c_path = CString::new(rel_path).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); // Test absolute path let abs_path = "/tmp/dash-spv-test"; let c_path = CString::new(abs_path).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); // Test home directory expansion (won't actually expand in FFI) let home_path = "~/dash-spv"; let c_path = CString::new(home_path).unwrap(); - let result = dash_spv_ffi_config_set_data_dir(config, c_path.as_ptr()); + let result = dash_spv_ffi_config_builder_set_storage_path(builder, c_path.as_ptr()); assert_eq!(result, FFIErrorCode::Success as i32); + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -174,46 +155,51 @@ mod tests { #[serial] fn test_config_all_settings() { unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); // Set all possible configuration options let data_dir = CString::new("/tmp/test-dash-spv").unwrap(); assert_eq!( - dash_spv_ffi_config_set_data_dir(config, data_dir.as_ptr()), + dash_spv_ffi_config_builder_set_storage_path(builder, data_dir.as_ptr()), FFIErrorCode::Success as i32 ); assert_eq!( - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::Full), + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::Full), FFIErrorCode::Success as i32 ); - assert_eq!(dash_spv_ffi_config_set_max_peers(config, 50), FFIErrorCode::Success as i32); - - let peer = CString::new("127.0.0.1:9999").unwrap(); assert_eq!( - dash_spv_ffi_config_add_peer(config, peer.as_ptr()), + dash_spv_ffi_config_builder_set_max_peers(builder, 50), FFIErrorCode::Success as i32 ); let user_agent = CString::new("TestAgent/1.0").unwrap(); assert_eq!( - dash_spv_ffi_config_set_user_agent(config, user_agent.as_ptr()), + dash_spv_ffi_config_builder_set_user_agent(builder, user_agent.as_ptr()), + FFIErrorCode::Success as i32 + ); + + assert_eq!( + dash_spv_ffi_config_builder_set_relay_transactions(builder, true), FFIErrorCode::Success as i32 ); assert_eq!( - dash_spv_ffi_config_set_relay_transactions(config, true), + dash_spv_ffi_config_builder_set_filter_load(builder, true), FFIErrorCode::Success as i32 ); assert_eq!( - dash_spv_ffi_config_set_filter_load(config, true), + dash_spv_ffi_config_builder_set_restrict_to_configured_peers(builder, true), FFIErrorCode::Success as i32 ); + let config = dash_spv_ffi_config_builder_build(builder); + + let peer = CString::new("127.0.0.1:9999").unwrap(); assert_eq!( - dash_spv_ffi_config_set_restrict_to_configured_peers(config, true), + dash_spv_ffi_config_add_peer(config, peer.as_ptr()), FFIErrorCode::Success as i32 ); @@ -227,12 +213,15 @@ mod tests { unsafe { // Test all functions with null config assert_eq!( - dash_spv_ffi_config_set_data_dir(std::ptr::null_mut(), std::ptr::null()), + dash_spv_ffi_config_builder_set_storage_path( + std::ptr::null_mut(), + std::ptr::null() + ), FFIErrorCode::NullPointer as i32 ); assert_eq!( - dash_spv_ffi_config_set_validation_mode( + dash_spv_ffi_config_builder_set_validation_mode( std::ptr::null_mut(), FFIValidationMode::Basic ), @@ -240,7 +229,7 @@ mod tests { ); assert_eq!( - dash_spv_ffi_config_set_max_peers(std::ptr::null_mut(), 10), + dash_spv_ffi_config_builder_set_max_peers(std::ptr::null_mut(), 10), FFIErrorCode::NullPointer as i32 ); @@ -250,17 +239,17 @@ mod tests { ); assert_eq!( - dash_spv_ffi_config_set_user_agent(std::ptr::null_mut(), std::ptr::null()), + dash_spv_ffi_config_builder_set_user_agent(std::ptr::null_mut(), std::ptr::null()), FFIErrorCode::NullPointer as i32 ); assert_eq!( - dash_spv_ffi_config_set_relay_transactions(std::ptr::null_mut(), false), + dash_spv_ffi_config_builder_set_relay_transactions(std::ptr::null_mut(), false), FFIErrorCode::NullPointer as i32 ); assert_eq!( - dash_spv_ffi_config_set_filter_load(std::ptr::null_mut(), false), + dash_spv_ffi_config_builder_set_filter_load(std::ptr::null_mut(), false), FFIErrorCode::NullPointer as i32 ); @@ -280,16 +269,17 @@ mod tests { #[serial] fn test_config_validation_modes() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test all validation modes let modes = [FFIValidationMode::None, FFIValidationMode::Basic, FFIValidationMode::Full]; for mode in modes { - let result = dash_spv_ffi_config_set_validation_mode(config, mode); + let result = dash_spv_ffi_config_builder_set_validation_mode(builder, mode); assert_eq!(result, FFIErrorCode::Success as i32); } + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -298,25 +288,32 @@ mod tests { #[serial] fn test_config_edge_case_values() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test max peers with edge values - assert_eq!(dash_spv_ffi_config_set_max_peers(config, 0), FFIErrorCode::Success as i32); + assert_eq!( + dash_spv_ffi_config_builder_set_max_peers(builder, 0), + FFIErrorCode::Success as i32 + ); - assert_eq!(dash_spv_ffi_config_set_max_peers(config, 1), FFIErrorCode::Success as i32); + assert_eq!( + dash_spv_ffi_config_builder_set_max_peers(builder, 1), + FFIErrorCode::Success as i32 + ); assert_eq!( - dash_spv_ffi_config_set_max_peers(config, u32::MAX), + dash_spv_ffi_config_builder_set_max_peers(builder, u32::MAX), FFIErrorCode::Success as i32 ); // Test empty strings let empty = CString::new("").unwrap(); assert_eq!( - dash_spv_ffi_config_set_data_dir(config, empty.as_ptr()), + dash_spv_ffi_config_builder_set_storage_path(builder, empty.as_ptr()), FFIErrorCode::Success as i32 ); + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -325,27 +322,28 @@ mod tests { #[serial] fn test_worker_threads_configuration() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test setting worker threads to 0 (auto mode) - let result = dash_spv_ffi_config_set_worker_threads(config, 0); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, 0); assert_eq!(result, FFIErrorCode::Success as i32); // Test setting specific worker thread counts let thread_counts = [1, 2, 4, 8, 16, 32]; for &count in &thread_counts { - let result = dash_spv_ffi_config_set_worker_threads(config, count); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, count); assert_eq!(result, FFIErrorCode::Success as i32); } // Test large worker thread count - let result = dash_spv_ffi_config_set_worker_threads(config, 1000); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, 1000); assert_eq!(result, FFIErrorCode::Success as i32); // Test maximum value - let result = dash_spv_ffi_config_set_worker_threads(config, u32::MAX); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, u32::MAX); assert_eq!(result, FFIErrorCode::Success as i32); + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -355,7 +353,7 @@ mod tests { fn test_worker_threads_with_null_config() { unsafe { // Test with null config pointer - let result = dash_spv_ffi_config_set_worker_threads(std::ptr::null_mut(), 4); + let result = dash_spv_ffi_config_builder_set_worker_threads(std::ptr::null_mut(), 4); assert_eq!(result, FFIErrorCode::NullPointer as i32); // Check error was set @@ -376,17 +374,19 @@ mod tests { unsafe { // Test that worker thread setting is preserved for &thread_count in &[0, 1, 4, 8] { - let config = dash_spv_ffi_config_new(FFINetwork::Testnet); + let builder = dash_spv_ffi_config_builder_testnet(); // Set worker threads - let result = dash_spv_ffi_config_set_worker_threads(config, thread_count); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, thread_count); assert_eq!(result, FFIErrorCode::Success as i32); // Create client with this config (this tests that the setting is used) let temp_dir = tempfile::TempDir::new().unwrap(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); - dash_spv_ffi_config_set_validation_mode(config, FFIValidationMode::None); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + dash_spv_ffi_config_builder_set_validation_mode(builder, FFIValidationMode::None); + + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); // Client creation should succeed regardless of worker thread count @@ -407,19 +407,20 @@ mod tests { fn test_worker_threads_multiple_configs() { unsafe { // Test that different configs can have different worker thread counts - let configs = [ - (dash_spv_ffi_config_testnet(), 1), - (dash_spv_ffi_config_mainnet(), 4), - (dash_spv_ffi_config_new(FFINetwork::Regtest), 8), + let builders = [ + (dash_spv_ffi_config_builder_testnet(), 1), + (dash_spv_ffi_config_builder_mainnet(), 4), + (dash_spv_ffi_config_builder_regtest(), 8), ]; - for (config, thread_count) in configs { - let result = dash_spv_ffi_config_set_worker_threads(config, thread_count); + for (builder, thread_count) in builders { + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, thread_count); assert_eq!(result, FFIErrorCode::Success as i32); } // Clean up all configs - for (config, _) in configs { + for (builder, _) in builders { + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } @@ -429,21 +430,22 @@ mod tests { #[serial] fn test_worker_threads_edge_cases() { unsafe { - let config = dash_spv_ffi_config_testnet(); + let builder = dash_spv_ffi_config_builder_testnet(); // Test repeated setting of worker threads for _ in 0..10 { - let result = dash_spv_ffi_config_set_worker_threads(config, 4); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, 4); assert_eq!(result, FFIErrorCode::Success as i32); } // Test setting different values in sequence let sequence = [0, 1, 0, 8, 0, 16, 0]; for &count in &sequence { - let result = dash_spv_ffi_config_set_worker_threads(config, count); + let result = dash_spv_ffi_config_builder_set_worker_threads(builder, count); assert_eq!(result, FFIErrorCode::Success as i32); } + let config = dash_spv_ffi_config_builder_build(builder); dash_spv_ffi_config_destroy(config); } } diff --git a/dash-spv-ffi/tests/unit/test_error_handling.rs b/dash-spv-ffi/tests/unit/test_error_handling.rs index f47f48c09..630a95aa8 100644 --- a/dash-spv-ffi/tests/unit/test_error_handling.rs +++ b/dash-spv-ffi/tests/unit/test_error_handling.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::CStr; use std::sync::{Arc, Barrier}; @@ -162,7 +161,10 @@ mod tests { // Test null_check! macro behavior unsafe { // Test with config functions - let result = dash_spv_ffi_config_set_data_dir(std::ptr::null_mut(), std::ptr::null()); + let result = dash_spv_ffi_config_builder_set_storage_path( + std::ptr::null_mut(), + std::ptr::null(), + ); assert_eq!(result, FFIErrorCode::NullPointer as i32); // Check error was set @@ -178,7 +180,8 @@ mod tests { // Use a valid enum value to avoid UB in Rust tests. If invalid raw inputs // need to be tested, do so from a C test or add a raw-int FFI entrypoint. unsafe { - let config = dash_spv_ffi_config_new(FFINetwork::Dash); + let builder = dash_spv_ffi_config_builder_mainnet(); + let config = dash_spv_ffi_config_builder_build(builder); assert!(!config.is_null()); dash_spv_ffi_config_destroy(config); } diff --git a/dash-spv-ffi/tests/unit/test_memory_management.rs b/dash-spv-ffi/tests/unit/test_memory_management.rs index 4ceeba5da..94c6fad2a 100644 --- a/dash-spv-ffi/tests/unit/test_memory_management.rs +++ b/dash-spv-ffi/tests/unit/test_memory_management.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { use crate::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; @@ -72,9 +71,10 @@ mod tests { fn test_client_memory_lifecycle() { unsafe { let temp_dir = TempDir::new().unwrap(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + let config = dash_spv_ffi_config_builder_build(builder); // Create and destroy multiple clients for _ in 0..10 { @@ -268,9 +268,10 @@ mod tests { unsafe { // Test cleanup of structures containing pointers to other structures let temp_dir = TempDir::new().unwrap(); - let config = dash_spv_ffi_config_new(FFINetwork::Regtest); + let builder = dash_spv_ffi_config_builder_regtest(); let path = CString::new(temp_dir.path().to_str().unwrap()).unwrap(); - dash_spv_ffi_config_set_data_dir(config, path.as_ptr()); + dash_spv_ffi_config_builder_set_storage_path(builder, path.as_ptr()); + let config = dash_spv_ffi_config_builder_build(builder); let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index 6f03bf239..40ccfc4a2 100644 --- a/dash-spv/Cargo.toml +++ b/dash-spv/Cargo.toml @@ -15,6 +15,11 @@ dashcore_hashes = { path = "../hashes" } key-wallet = { path = "../key-wallet" } key-wallet-manager = { path = "../key-wallet-manager" } +# Quality-of-life dependencies: +# fewer lines of code, fewer regrets +getset = { version = "0.1.6" } +derive_builder = { version = "0.20.2" } + # BLS signatures blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "0c34a7a488a0bd1c9a9a2196e793b303ad35c900" } diff --git a/dash-spv/benches/storage.rs b/dash-spv/benches/storage.rs index 5677e6ddc..68575299a 100644 --- a/dash-spv/benches/storage.rs +++ b/dash-spv/benches/storage.rs @@ -3,7 +3,7 @@ use std::time::Duration; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use dash_spv::{ storage::{BlockHeaderStorage, DiskStorageManager, StorageManager}, - Hash, + ClientConfigBuilder, Hash, }; use dashcore::{block::Version, BlockHash, CompactTarget, Header}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -34,7 +34,11 @@ fn bench_disk_storage(c: &mut Criterion) { c.bench_function("storage/disk/store", |b| { b.to_async(&rt).iter_batched( || async { - DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()).await.unwrap() + let config = ClientConfigBuilder::testnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + DiskStorageManager::new(&config).await.unwrap() }, |a| async { let mut storage = a.await; @@ -47,10 +51,13 @@ fn bench_disk_storage(c: &mut Criterion) { ) }); - let temp_dir = TempDir::new().unwrap(); + let config = ClientConfigBuilder::testnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); let mut storage = rt.block_on(async { - let mut storage = DiskStorageManager::new(temp_dir.path().to_path_buf()).await.unwrap(); + let mut storage = DiskStorageManager::new(&config).await.unwrap(); for chunk in headers.chunks(CHUNK_SIZE as usize) { storage.store_headers(chunk).await.unwrap(); diff --git a/dash-spv/examples/filter_sync.rs b/dash-spv/examples/filter_sync.rs index 9503136ef..500d0ab01 100644 --- a/dash-spv/examples/filter_sync.rs +++ b/dash-spv/examples/filter_sync.rs @@ -2,7 +2,8 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{init_console_logging, ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::ClientConfigBuilder; +use dash_spv::{init_console_logging, DashSpvClient, LevelFilter}; use dashcore::Address; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -22,16 +23,19 @@ async fn main() -> Result<(), Box> { )?; // Create configuration with filter support - let config = ClientConfig::mainnet().without_masternodes(); // Skip masternode sync for this example + let config = ClientConfigBuilder::mainnet() + .storage_path("./.tmp/filter-sync-example-storage") + .enable_masternodes(false) // Skip masternode sync for this example + .build()?; // Create network manager let network_manager = PeerNetworkManager::new(&config).await?; // Create storage manager - let storage_manager = DiskStorageManager::new("./.tmp/filter-sync-example-storage").await?; + let storage_manager = DiskStorageManager::new(&config).await?; // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); // Create the client let mut client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await?; diff --git a/dash-spv/examples/simple_sync.rs b/dash-spv/examples/simple_sync.rs index 373e30cc7..3a7270468 100644 --- a/dash-spv/examples/simple_sync.rs +++ b/dash-spv/examples/simple_sync.rs @@ -2,7 +2,8 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{init_console_logging, ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::ClientConfigBuilder; +use dash_spv::{init_console_logging, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -16,18 +17,19 @@ async fn main() -> Result<(), Box> { let _logging_guard = init_console_logging(LevelFilter::INFO)?; // Create a simple configuration - let config = ClientConfig::mainnet() - .without_filters() // Skip filter sync for this example - .without_masternodes(); // Skip masternode sync for this example - + let config = ClientConfigBuilder::mainnet() + .storage_path("./.tmp/simple-sync-example-storage") + .enable_filters(false) // Skip filter sync for this example + .enable_masternodes(false) // Skip masternode sync for this example + .build()?; // Create network manager let network_manager = PeerNetworkManager::new(&config).await?; // Create storage manager - let storage_manager = DiskStorageManager::new("./.tmp/simple-sync-example-storage").await?; + let storage_manager = DiskStorageManager::new(&config).await?; // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); // Create the client let mut client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await?; diff --git a/dash-spv/examples/spv_with_wallet.rs b/dash-spv/examples/spv_with_wallet.rs index f6d4a42da..3f44e9748 100644 --- a/dash-spv/examples/spv_with_wallet.rs +++ b/dash-spv/examples/spv_with_wallet.rs @@ -4,7 +4,8 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::ClientConfigBuilder; +use dash_spv::{DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; @@ -17,19 +18,20 @@ async fn main() -> Result<(), Box> { let _logging_guard = dash_spv::init_console_logging(LevelFilter::INFO)?; // Create SPV client configuration - let mut config = ClientConfig::testnet(); - config.storage_path = Some("/tmp/dash-spv-example".into()); - config.validation_mode = dash_spv::types::ValidationMode::Full; - config.enable_filters = true; + let config = ClientConfigBuilder::testnet() + .storage_path("./.tmp/spv-with-wallet-example-storage") + .validation_mode(dash_spv::ValidationMode::Full) + .enable_mempool_tracking(true) + .build()?; // Create network manager let network_manager = PeerNetworkManager::new(&config).await?; // Create storage manager - use disk storage for persistence - let storage_manager = DiskStorageManager::new("./.tmp/spv-with-wallet-example-storage").await?; + let storage_manager = DiskStorageManager::new(&config).await?; // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); // Create the SPV client with all components let mut client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await?; diff --git a/dash-spv/src/client/chainlock.rs b/dash-spv/src/client/chainlock.rs index 8408189eb..2c374ca4c 100644 --- a/dash-spv/src/client/chainlock.rs +++ b/dash-spv/src/client/chainlock.rs @@ -12,6 +12,7 @@ use crate::error::{Result, SpvError}; use crate::network::NetworkManager; use crate::storage::StorageManager; use crate::types::SpvEvent; +use crate::validation::{InstantLockValidator, Validator}; use key_wallet_manager::wallet_interface::WalletInterface; use super::DashSpvClient; @@ -99,8 +100,8 @@ impl DashSpvClient, + #[getset(get = "pub")] + peers: Vec, /// Restrict connections strictly to the configured peers. /// /// When true, the client will not use DNS discovery or peer persistence and /// will only attempt to connect to addresses provided in `peers`. /// If no peers are configured, no outbound connections will be made. - pub restrict_to_configured_peers: bool, + #[getset(get_copy = "pub")] + restrict_to_configured_peers: bool, - /// Optional path for persistent storage. - pub storage_path: Option, + /// Path for persistent storage. Defaults to ./dash-spv-storage + #[getset(get = "pub")] + #[builder(setter(into))] + storage_path: PathBuf, /// Validation mode. - pub validation_mode: ValidationMode, + #[getset(get_copy = "pub")] + validation_mode: ValidationMode, /// Whether to enable filter syncing. - pub enable_filters: bool, + #[getset(get_copy = "pub")] + enable_filters: bool, /// Whether to enable masternode syncing. - pub enable_masternodes: bool, + #[getset(get_copy = "pub")] + enable_masternodes: bool, /// Maximum number of peers to connect to. - pub max_peers: u32, + #[getset(get_copy = "pub")] + max_peers: u32, /// Optional user agent string to advertise in the P2P version message. /// If not set, a sensible default is used (includes crate version). - pub user_agent: Option, + #[getset(get = "pub")] + user_agent: Option, // Mempool configuration /// Enable tracking of unconfirmed (mempool) transactions. - pub enable_mempool_tracking: bool, + #[getset(get_copy = "pub")] + enable_mempool_tracking: bool, /// Strategy for handling mempool transactions. - pub mempool_strategy: MempoolStrategy, + #[getset(get_copy = "pub")] + mempool_strategy: MempoolStrategy, /// Maximum number of unconfirmed transactions to track. - pub max_mempool_transactions: usize, + #[getset(get_copy = "pub")] + max_mempool_transactions: usize, /// Whether to fetch transactions from INV messages immediately. - pub fetch_mempool_transactions: bool, + #[getset(get_copy = "pub")] + fetch_mempool_transactions: bool, /// Whether to persist mempool transactions. - pub persist_mempool: bool, + #[getset(get_copy = "pub")] + persist_mempool: bool, /// Start syncing from a specific block height. /// The client will use the nearest checkpoint at or before this height. - pub start_from_height: Option, + #[getset(get_copy = "pub")] + start_from_height: Option, } impl Default for ClientConfig { @@ -80,13 +101,12 @@ impl Default for ClientConfig { network: Network::Dash, peers: vec![], restrict_to_configured_peers: false, - storage_path: None, + storage_path: PathBuf::from("./dash-spv-storage"), validation_mode: ValidationMode::Full, enable_filters: true, enable_masternodes: true, max_peers: 8, user_agent: None, - // Mempool defaults enable_mempool_tracking: true, mempool_strategy: MempoolStrategy::FetchAll, max_mempool_transactions: 1000, @@ -97,136 +117,242 @@ impl Default for ClientConfig { } } -impl ClientConfig { - /// Create a new configuration for the given network. +impl ClientConfigBuilder { pub fn new(network: Network) -> Self { - Self { - network, - peers: Self::default_peers_for_network(network), - restrict_to_configured_peers: false, - ..Self::default() + match network { + Network::Dash => Self::mainnet(), + Network::Testnet => Self::testnet(), + Network::Devnet => Self::devnet(), + Network::Regtest => Self::regtest(), + _ => panic!("Unsupported network"), } } - - /// Create a configuration for mainnet. pub fn mainnet() -> Self { - Self::new(Network::Dash) + let mut builder = Self::default(); + builder.network(Network::Dash); + builder } - /// Create a configuration for testnet. pub fn testnet() -> Self { - Self::new(Network::Testnet) + let mut builder = Self::default(); + builder.network(Network::Testnet); + builder + } + + pub fn devnet() -> Self { + let mut builder = Self::default(); + builder.network(Network::Devnet); + builder } - /// Create a configuration for regtest. pub fn regtest() -> Self { - Self::new(Network::Regtest) + let peers = vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 19899)]; + + let mut builder = Self::default(); + builder.network(Network::Regtest).peers(peers); + builder } - /// Add a peer address. - pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { - self.peers.push(address); - self + fn validate(&self) -> Result<(), String> { + if let Some(0) = self.max_peers { + return Err("max_peers must be > 0".to_string()); + } + + // Validación de mempool + if let (Some(true), Some(0)) = (self.enable_mempool_tracking, self.max_mempool_transactions) + { + return Err( + "max_mempool_transactions must be > 0 when mempool tracking is enabled".to_string() + ); + } + + if let Some(path) = &self.storage_path { + std::fs::create_dir_all(path) + .map_err(|e| format!("A valid storage path must be provided: {:?}: {e}", path))?; + } + + match (&self.peers, self.restrict_to_configured_peers) { + (Some(peers), Some(true)) if peers.is_empty() => { + return Err( + "restrict_to_configured_peers is true but no peers were provided".to_string() + ); + } + _ => {} + } + + Ok(()) } +} - /// Restrict connections to the configured peers only. - pub fn with_restrict_to_configured_peers(mut self, restrict: bool) -> Self { - self.restrict_to_configured_peers = restrict; +impl ClientConfig { + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_restrict_to_configured_peers(&mut self, val: bool) -> &mut Self { + self.restrict_to_configured_peers = val; self } - /// Set storage path. - pub fn with_storage_path(mut self, path: PathBuf) -> Self { - self.storage_path = Some(path); + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_storage_path>(&mut self, path: P) -> &mut Self { + self.storage_path = path.into(); self } - /// Set validation mode. - pub fn with_validation_mode(mut self, mode: ValidationMode) -> Self { + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_validation_mode(&mut self, mode: ValidationMode) -> &mut Self { self.validation_mode = mode; self } - /// Disable filters. - pub fn without_filters(mut self) -> Self { - self.enable_filters = false; + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_enable_filters(&mut self, val: bool) -> &mut Self { + self.enable_filters = val; + self + } + + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_enable_masternodes(&mut self, val: bool) -> &mut Self { + self.enable_masternodes = val; + self + } + + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_max_peers(&mut self, max: u32) -> &mut Self { + self.max_peers = max; self } - /// Disable masternodes. - pub fn without_masternodes(mut self) -> Self { - self.enable_masternodes = false; + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_user_agent>(&mut self, ua: S) -> &mut Self { + self.user_agent = Some(ua.into()); self } - /// Set custom user agent string for the P2P handshake. - /// The library will lightly validate and normalize it during handshake. - pub fn with_user_agent(mut self, agent: impl Into) -> Self { - self.user_agent = Some(agent.into()); + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_enable_mempool_tracking(&mut self, val: bool) -> &mut Self { + self.enable_mempool_tracking = val; self } - /// Enable mempool tracking with specified strategy. - pub fn with_mempool_tracking(mut self, strategy: MempoolStrategy) -> Self { - self.enable_mempool_tracking = true; + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_mempool_strategy(&mut self, strategy: MempoolStrategy) -> &mut Self { self.mempool_strategy = strategy; self } - /// Set maximum number of mempool transactions to track. - pub fn with_max_mempool_transactions(mut self, max: usize) -> Self { + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_max_mempool_transactions(&mut self, max: usize) -> &mut Self { self.max_mempool_transactions = max; self } - /// Enable or disable mempool persistence. - pub fn with_mempool_persistence(mut self, enabled: bool) -> Self { - self.persist_mempool = enabled; + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_fetch_mempool_transactions(&mut self, val: bool) -> &mut Self { + self.fetch_mempool_transactions = val; self } - /// Set the starting height for synchronization. - pub fn with_start_height(mut self, height: u32) -> Self { + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_persist_mempool(&mut self, val: bool) -> &mut Self { + self.persist_mempool = val; + self + } + + #[deprecated(since = "0.42.0", note = "Use builder pattern")] + pub fn set_start_from_height(&mut self, height: u32) -> &mut Self { self.start_from_height = Some(height); self } - /// Validate the configuration. - pub fn validate(&self) -> Result<(), String> { - // Note: Empty peers list is now valid - DNS discovery will be used automatically + pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { + self.peers.push(address); + self + } +} - if self.max_peers == 0 { - return Err("max_peers must be > 0".to_string()); - } +#[cfg(test)] +mod tests { + use crate::client::config::{ClientConfig, ClientConfigBuilder, MempoolStrategy}; + use crate::types::ValidationMode; + use dashcore::Network; + use std::net::SocketAddr; + + #[test] + fn test_default_config() { + let config = ClientConfig::default(); + + assert_eq!(config.network(), Network::Dash); + assert!(config.peers().is_empty()); + assert_eq!(config.validation_mode(), ValidationMode::Full); + assert!(config.enable_filters()); + assert!(config.enable_masternodes()); + assert_eq!(config.max_peers(), 8); + + // Mempool defaults + assert!(config.enable_mempool_tracking()); + assert_eq!(config.mempool_strategy(), MempoolStrategy::FetchAll); + assert_eq!(config.max_mempool_transactions(), 1000); + assert!(config.fetch_mempool_transactions()); + assert!(!config.persist_mempool()); + } - // Mempool validation - if self.enable_mempool_tracking && self.max_mempool_transactions == 0 { - return Err( - "max_mempool_transactions must be > 0 when mempool tracking is enabled".to_string() - ); - } + #[test] + fn test_network_specific_configs() { + let mainnet = ClientConfigBuilder::mainnet().build().expect("Valid configuration"); + assert_eq!(mainnet.network(), Network::Dash); + assert!(mainnet.peers().is_empty()); // Should use DNS discovery - Ok(()) + let testnet = ClientConfigBuilder::testnet().build().expect("Valid configuration"); + assert_eq!(testnet.network(), Network::Testnet); + assert!(testnet.peers().is_empty()); // Should use DNS discovery + + let regtest = ClientConfigBuilder::regtest().build().expect("Valid configuration"); + assert_eq!(regtest.network(), Network::Regtest); + assert_eq!(regtest.peers().len(), 1); + assert_eq!(regtest.peers()[0].to_string(), "127.0.0.1:19899"); } - /// Get default peers for a network. - /// Returns empty vector to enable immediate DNS discovery on startup. - /// Explicit peers can still be added via add_peer() or configuration. - fn default_peers_for_network(network: Network) -> Vec { - match network { - Network::Dash | Network::Testnet => { - // Return empty to trigger immediate DNS discovery - // DNS seeds will be used: dnsseed.dash.org (mainnet), testnet-seed.dashdot.io (testnet) - vec![] - } - Network::Regtest => { - // Regtest typically uses local peers - vec!["127.0.0.1:19899".parse::()] - .into_iter() - .filter_map(Result::ok) - .collect() - } - _ => vec![], - } + #[test] + fn test_add_peer() { + let mut config = ClientConfigBuilder::default().build().expect("Valid configuration"); + let addr1: SocketAddr = "1.2.3.4:9999".parse().unwrap(); + let addr2: SocketAddr = "5.6.7.8:9999".parse().unwrap(); + + config.add_peer(addr1); + config.add_peer(addr2); + + assert_eq!(config.peers().len(), 2); + assert_eq!(config.peers()[0], addr1); + assert_eq!(config.peers()[1], addr2); + } + + #[test] + fn test_disable_features() { + let config = ClientConfigBuilder::testnet() + .enable_filters(false) + .enable_masternodes(false) + .build() + .expect("Valid configuration"); + + assert!(!config.enable_filters()); + assert!(!config.enable_masternodes()); + } + + #[test] + fn test_validation_invalid_max_peers() { + let result = ClientConfigBuilder::testnet().max_peers(0).build(); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "max_peers must be > 0"); + } + + #[test] + fn test_validation_invalid_mempool_config() { + let result = ClientConfigBuilder::testnet() + .enable_mempool_tracking(true) + .max_mempool_transactions(0) + .build(); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("max_mempool_transactions must be > 0")); } } diff --git a/dash-spv/src/client/config_test.rs b/dash-spv/src/client/config_test.rs deleted file mode 100644 index 6c564799e..000000000 --- a/dash-spv/src/client/config_test.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Unit tests for client configuration - -#[cfg(test)] -mod tests { - use crate::client::config::{ClientConfig, MempoolStrategy}; - use crate::types::ValidationMode; - use dashcore::Network; - use std::net::SocketAddr; - use std::path::PathBuf; - - #[test] - fn test_default_config() { - let config = ClientConfig::default(); - - assert_eq!(config.network, Network::Dash); - assert!(config.peers.is_empty()); - assert_eq!(config.validation_mode, ValidationMode::Full); - assert!(config.enable_filters); - assert!(config.enable_masternodes); - assert_eq!(config.max_peers, 8); - - // Mempool defaults - assert!(config.enable_mempool_tracking); - assert_eq!(config.mempool_strategy, MempoolStrategy::FetchAll); - assert_eq!(config.max_mempool_transactions, 1000); - assert!(config.fetch_mempool_transactions); - assert!(!config.persist_mempool); - } - - #[test] - fn test_network_specific_configs() { - let mainnet = ClientConfig::mainnet(); - assert_eq!(mainnet.network, Network::Dash); - assert!(mainnet.peers.is_empty()); // Should use DNS discovery - - let testnet = ClientConfig::testnet(); - assert_eq!(testnet.network, Network::Testnet); - assert!(testnet.peers.is_empty()); // Should use DNS discovery - - let regtest = ClientConfig::regtest(); - assert_eq!(regtest.network, Network::Regtest); - assert_eq!(regtest.peers.len(), 1); - assert_eq!(regtest.peers[0].to_string(), "127.0.0.1:19899"); - } - - #[test] - fn test_builder_pattern() { - let path = PathBuf::from("/test/storage"); - - let config = ClientConfig::mainnet() - .with_storage_path(path.clone()) - .with_validation_mode(ValidationMode::Basic) - .with_mempool_tracking(MempoolStrategy::BloomFilter) - .with_max_mempool_transactions(500) - .with_mempool_persistence(true) - .with_start_height(100000); - - assert_eq!(config.storage_path, Some(path)); - assert_eq!(config.validation_mode, ValidationMode::Basic); - - // Mempool settings - assert!(config.enable_mempool_tracking); - assert_eq!(config.mempool_strategy, MempoolStrategy::BloomFilter); - assert_eq!(config.max_mempool_transactions, 500); - assert!(config.persist_mempool); - assert_eq!(config.start_from_height, Some(100000)); - } - - #[test] - fn test_add_peer() { - let mut config = ClientConfig::default(); - let addr1: SocketAddr = "1.2.3.4:9999".parse().unwrap(); - let addr2: SocketAddr = "5.6.7.8:9999".parse().unwrap(); - - config.add_peer(addr1); - config.add_peer(addr2); - - assert_eq!(config.peers.len(), 2); - assert_eq!(config.peers[0], addr1); - assert_eq!(config.peers[1], addr2); - } - - #[test] - fn test_disable_features() { - let config = ClientConfig::default().without_filters().without_masternodes(); - - assert!(!config.enable_filters); - assert!(!config.enable_masternodes); - } - - #[test] - fn test_validation_valid_config() { - let config = ClientConfig::default(); - assert!(config.validate().is_ok()); - } - - #[test] - fn test_validation_invalid_max_peers() { - let config = ClientConfig { - max_peers: 0, - ..Default::default() - }; - - let result = config.validate(); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "max_peers must be > 0"); - } - - #[test] - fn test_validation_invalid_mempool_config() { - let config = ClientConfig { - enable_mempool_tracking: true, - max_mempool_transactions: 0, - ..Default::default() - }; - - let result = config.validate(); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("max_mempool_transactions must be > 0")); - } - - // Removed selective strategy validation test; Selective variant no longer exists -} diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index b41a7aeae..ef5243522 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -155,7 +155,7 @@ impl DashSpvClient dashcore::Network { - self.config.network + self.config.network() } /// Get access to storage manager (requires locking). @@ -211,7 +211,7 @@ impl DashSpvClient DashSpvClient Result<()> { - // Validate new configuration - new_config.validate().map_err(SpvError::Config)?; - // Ensure network hasn't changed - if new_config.network != self.config.network { + if new_config.network() != self.config.network() { return Err(SpvError::Config("Cannot change network on running client".to_string())); } diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index ce061bc8d..86a17e0ff 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -33,11 +33,8 @@ impl DashSpvClient>, ) -> Result { - // Validate configuration - config.validate().map_err(SpvError::Config)?; - // Initialize state for the network - let state = Arc::new(RwLock::new(ChainState::new_for_network(config.network))); + let state = Arc::new(RwLock::new(ChainState::new_for_network(config.network()))); let stats = Arc::new(RwLock::new(SpvStats::default())); // Wrap storage in Arc @@ -102,18 +99,18 @@ impl DashSpvClient DashSpvClient DashSpvClient crate::chain::checkpoints::mainnet_checkpoints(), dashcore::Network::Testnet => crate::chain::checkpoints::testnet_checkpoints(), _ => vec![], @@ -284,7 +281,7 @@ impl DashSpvClient DashSpvClient DashSpvClient { - /// Enable mempool tracking with the specified strategy. - pub async fn enable_mempool_tracking( - &mut self, - strategy: config::MempoolStrategy, - ) -> Result<()> { - // Update config - self.config.enable_mempool_tracking = true; - self.config.mempool_strategy = strategy; - - // Initialize mempool filter if not already done - if self.mempool_filter.is_none() { - // TODO: Get monitored addresses from wallet - self.mempool_filter = Some(Arc::new(MempoolFilter::new( - self.config.mempool_strategy, - self.config.max_mempool_transactions, - self.mempool_state.clone(), - HashSet::new(), // Will be populated from wallet's monitored addresses - self.config.network, - ))); - } - - Ok(()) - } - /// Get mempool balance for an address. pub async fn get_mempool_balance( &self, @@ -72,7 +48,7 @@ impl DashSpvClient DashSpvClient MessageHandle drop(state); // Store in storage if persistence is enabled - if self.config.persist_mempool { + if self.config.persist_mempool() { if let Err(e) = self.storage.store_mempool_transaction(&txid, &unconfirmed_tx).await { @@ -354,7 +354,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle // Check if we should fetch this transaction if let Some(filter) = self.mempool_filter { - if self.config.fetch_mempool_transactions + if self.config.fetch_mempool_transactions() && filter.should_fetch_transaction(&txid).await { tracing::info!("📥 Requesting transaction {}", txid); @@ -366,7 +366,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle } else { tracing::debug!("Not fetching transaction {} (fetch_mempool_transactions={}, should_fetch={})", txid, - self.config.fetch_mempool_transactions, + self.config.fetch_mempool_transactions(), filter.should_fetch_transaction(&txid).await ); } @@ -416,7 +416,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle &mut self, headers: &[dashcore::block::Header], ) -> Result<()> { - if !self.config.enable_filters { + if !self.config.enable_filters() { tracing::debug!( "Filters not enabled, skipping post-sync filter requests for {} headers", headers.len() diff --git a/dash-spv/src/client/message_handler_test.rs b/dash-spv/src/client/message_handler_test.rs index 63ef8b434..7750475f3 100644 --- a/dash-spv/src/client/message_handler_test.rs +++ b/dash-spv/src/client/message_handler_test.rs @@ -215,13 +215,9 @@ mod tests { #[tokio::test] async fn test_handle_inv_message_with_mempool() { - let (mut network, mut storage, mut sync_manager, mut config, mempool_state, event_tx) = + let (mut network, mut storage, mut sync_manager, config, mempool_state, event_tx) = setup_test_components().await; - // Enable mempool tracking - config.enable_mempool_tracking = true; - config.fetch_mempool_transactions = true; - let mut handler = MessageHandler::new( &mut sync_manager, &mut storage, diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index bd6be889d..facc95599 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -30,14 +30,12 @@ //! //! Never acquire locks in reverse order or deadlock will occur! -// Existing extracted modules -pub mod config; pub mod interface; pub mod message_handler; pub mod status_display; -// New refactored modules mod chainlock; +mod config; mod core; mod events; mod lifecycle; @@ -48,28 +46,27 @@ mod sync_coordinator; mod transactions; // Re-export public types from extracted modules -pub use config::ClientConfig; +pub use config::{ClientConfig, ClientConfigBuilder, MempoolStrategy}; pub use message_handler::MessageHandler; pub use status_display::StatusDisplay; // Re-export the main client struct pub use core::DashSpvClient; -#[cfg(test)] -mod config_test; - #[cfg(test)] mod message_handler_test; #[cfg(test)] mod tests { - use super::{ClientConfig, DashSpvClient}; + use super::DashSpvClient; + use crate::client::config::{ClientConfigBuilder, MempoolStrategy}; use crate::storage::DiskStorageManager; use crate::{test_utils::MockNetworkManager, types::UnconfirmedTransaction}; - use dashcore::{Address, Amount, Network, Transaction, TxOut}; + use dashcore::{Address, Amount, Transaction, TxOut}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; + use tempfile::TempDir; use tokio::sync::RwLock; // Tests for get_mempool_balance function @@ -80,18 +77,20 @@ mod tests { #[tokio::test] async fn client_exposes_shared_wallet_manager() { - let config = ClientConfig { - network: Network::Dash, - enable_filters: false, - enable_masternodes: false, - enable_mempool_tracking: false, - ..Default::default() - }; + let config = ClientConfigBuilder::mainnet() + .enable_filters(false) + .enable_masternodes(false) + .enable_mempool_tracking(true) + .mempool_strategy(MempoolStrategy::FetchAll) + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid configuration"); let network_manager = MockNetworkManager::new(); let storage = DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = + Arc::new(RwLock::new(WalletManager::::new(config.network()))); let client = DashSpvClient::new(config, network_manager, storage, wallet) .await @@ -108,31 +107,26 @@ mod tests { // This test validates the get_mempool_balance logic by directly testing // the balance calculation code using a mocked mempool state. - let config = ClientConfig { - network: Network::Testnet, - enable_filters: false, - enable_masternodes: false, - enable_mempool_tracking: true, - ..Default::default() - }; + let config = ClientConfigBuilder::testnet() + .enable_filters(false) + .enable_masternodes(false) + .enable_mempool_tracking(true) + .mempool_strategy(MempoolStrategy::FetchAll) + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid configuration"); let network_manager = MockNetworkManager::new(); - let storage = - DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); + let wallet = + Arc::new(RwLock::new(WalletManager::::new(config.network()))); - let test_address = Address::dummy(config.network, 0); + let test_address = Address::dummy(config.network(), 0); - let mut client = DashSpvClient::new(config, network_manager, storage, wallet) + let client = DashSpvClient::new(config, network_manager, storage, wallet) .await .expect("client construction must succeed"); - // Enable mempool tracking to initialize mempool_filter - client - .enable_mempool_tracking(crate::client::config::MempoolStrategy::BloomFilter) - .await - .expect("enable mempool tracking must succeed"); - // Create a transaction that sends 10 Dash to the test address let tx = Transaction { version: 2, diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index cc3e9aee5..743bffbe4 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -234,7 +234,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { status.filter_headers = filter_height; status.chainlock_height = latest_chainlock; status.peer_count = 1; // TODO: Get actual peer count - status.network = format!("{:?}", self.config.network); + status.network = format!("{:?}", self.config.network()); }) .await; return; diff --git a/dash-spv/src/client/sync_coordinator.rs b/dash-spv/src/client/sync_coordinator.rs index 356e3c20c..32c6c21c8 100644 --- a/dash-spv/src/client/sync_coordinator.rs +++ b/dash-spv/src/client/sync_coordinator.rs @@ -233,7 +233,7 @@ impl DashSpvClient DashSpvClient DashSpvClient Result<(), Box> { //! // Create configuration for mainnet -//! let config = ClientConfig::mainnet() -//! .with_storage_path("/path/to/data".into()); +//! let config = ClientConfigBuilder::mainnet() +//! .storage_path("./.tmp/example-storage") +//! .build() +//! .unwrap(); //! //! // Create the required components //! let network = PeerNetworkManager::new(&config).await?; -//! let storage = DiskStorageManager::new("./.tmp/example-storage").await?; -//! let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); +//! let storage = DiskStorageManager::new(&config).await?; +//! let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); //! //! // Create and start the client //! let mut client = DashSpvClient::new(config.clone(), network, storage, wallet).await?; @@ -73,7 +75,7 @@ pub mod types; pub mod validation; // Re-export main types for convenience -pub use client::{ClientConfig, DashSpvClient}; +pub use client::{ClientConfig, ClientConfigBuilder, DashSpvClient, MempoolStrategy}; pub use error::{ LoggingError, LoggingResult, NetworkError, SpvError, StorageError, SyncError, ValidationError, }; diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 5566c5116..17b72500c 100644 --- a/dash-spv/src/main.rs +++ b/dash-spv/src/main.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use clap::{Arg, Command}; use dash_spv::terminal::TerminalGuard; -use dash_spv::{ClientConfig, DashSpvClient, LevelFilter, Network}; +use dash_spv::{ClientConfigBuilder, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use tokio_util::sync::CancellationToken; @@ -169,10 +169,10 @@ async fn run() -> Result<(), Box> { // Parse network let network_str = matches.get_one::("network").ok_or("Missing network argument")?; - let network = match network_str.as_str() { - "mainnet" => Network::Dash, - "testnet" => Network::Testnet, - "regtest" => Network::Regtest, + let mut cfg_builder = match network_str.as_str() { + "mainnet" => ClientConfigBuilder::mainnet(), + "testnet" => ClientConfigBuilder::testnet(), + "regtest" => ClientConfigBuilder::regtest(), n => return Err(format!("Invalid network: {}", n).into()), }; @@ -244,65 +244,65 @@ async fn run() -> Result<(), Box> { let _logging_guard = dash_spv::init_logging(logging_config)?; tracing::info!("Starting Dash SPV client"); - tracing::info!("Network: {:?}", network); + tracing::info!("Network: {:?}", network_str); tracing::info!("Data directory: {}", data_dir.display()); tracing::info!("Validation mode: {:?}", validation_mode); // Create configuration - let mut config = ClientConfig::new(network) - .with_storage_path(data_dir.clone()) - .with_validation_mode(validation_mode); - - // Add custom peers if specified - if let Some(peers) = matches.get_many::("peer") { - config.peers.clear(); - for peer in peers { - match peer.parse() { - Ok(addr) => config.add_peer(addr), - Err(e) => { - tracing::error!("Invalid peer address '{}': {}", peer, e); - process::exit(1); - } - }; - } - } + let config = cfg_builder.storage_path(data_dir.clone()).validation_mode(validation_mode); // Configure features if matches.get_flag("no-filters") { - config = config.without_filters(); + config.enable_filters(false); } if matches.get_flag("no-masternodes") { - config = config.without_masternodes(); + config.enable_masternodes(false); } if matches.get_flag("no-mempool") { - config.enable_mempool_tracking = false; + config.enable_mempool_tracking(false); } // Set start height if specified if let Some(start_height_str) = matches.get_one::("start-height") { if start_height_str == "now" { // Use a very high number to get the latest checkpoint - config.start_from_height = Some(u32::MAX); + config.start_from_height(u32::MAX); tracing::info!("Will start syncing from the latest available checkpoint"); } else { let start_height = start_height_str .parse::() .map_err(|e| format!("Invalid start height '{}': {}", start_height_str, e))?; - config.start_from_height = Some(start_height); + config.start_from_height(start_height); tracing::info!("Will start syncing from height: {}", start_height); } } // Validate configuration - if let Err(e) = config.validate() { - tracing::error!("Configuration error: {}", e); - process::exit(1); + let mut config = match config.build() { + Ok(config) => config, + Err(e) => { + tracing::error!("Configuration error: {}", e); + process::exit(1); + } + }; + + // Add custom peers if specified + if let Some(peers) = matches.get_many::("peer") { + for peer in peers { + match peer.parse() { + Ok(addr) => config.add_peer(addr), + Err(e) => { + tracing::error!("Invalid peer address '{}': {}", peer, e); + process::exit(1); + } + }; + } } tracing::info!("Sync strategy: Sequential"); // Create the wallet manager - let mut wallet_manager = WalletManager::::new(config.network); + let mut wallet_manager = WalletManager::::new(config.network()); let wallet_id = wallet_manager.create_wallet_from_mnemonic( mnemonic_phrase.as_str(), "", @@ -320,13 +320,7 @@ async fn run() -> Result<(), Box> { } }; - let path = if let Some(path) = &config.storage_path { - path.clone() - } else { - "./.tmp/main-exec-storage".into() - }; - - let storage_manager = match dash_spv::storage::DiskStorageManager::new(path.clone()).await { + let storage_manager = match dash_spv::storage::DiskStorageManager::new(&config).await { Ok(sm) => sm, Err(e) => { eprintln!("Failed to create disk storage manager: {}", e); @@ -348,7 +342,7 @@ async fn run() -> Result<(), Box> { } async fn run_client( - config: ClientConfig, + config: dash_spv::ClientConfig, network_manager: dash_spv::network::manager::PeerNetworkManager, storage_manager: S, wallet: Arc>>, @@ -381,7 +375,7 @@ async fn run_client( match TerminalGuard::new(ui.clone()) { Ok(guard) => { // Initial update with network info - let network_name = format!("{:?}", config.network); + let network_name = format!("{:?}", config.network()); let _ = ui .update_status(|status| { status.network = network_name; @@ -487,7 +481,7 @@ async fn run_client( for addr_str in addresses { match addr_str.parse::>() { Ok(addr) => { - let network = config.network; + let network = config.network(); let checked_addr = addr.require_network(network).map_err(|_| { format!("Address '{}' is not valid for network {:?}", addr_str, network) }); @@ -514,7 +508,7 @@ async fn run_client( // Add example addresses for testing if requested if matches.get_flag("add-example-addresses") { - let network = config.network; + let network = config.network(); let example_addresses = match network { dashcore::Network::Dash => vec![ // Some example mainnet addresses (these are from block explorers/faucets) diff --git a/dash-spv/src/mempool_filter.rs b/dash-spv/src/mempool_filter.rs index fc92161e5..f263f1270 100644 --- a/dash-spv/src/mempool_filter.rs +++ b/dash-spv/src/mempool_filter.rs @@ -7,8 +7,8 @@ use std::time::Duration; use dashcore::{Address, Network, Transaction, Txid}; use tokio::sync::RwLock; -use crate::client::config::MempoolStrategy; use crate::types::{MempoolState, UnconfirmedTransaction}; +use crate::MempoolStrategy; /// Filter for deciding which mempool transactions to fetch and track. pub struct MempoolFilter { diff --git a/dash-spv/src/network/handshake.rs b/dash-spv/src/network/handshake.rs index f738ce164..598ca9c6f 100644 --- a/dash-spv/src/network/handshake.rs +++ b/dash-spv/src/network/handshake.rs @@ -10,9 +10,9 @@ use dashcore::network::message_network::VersionMessage; use dashcore::Network; // Hash trait not needed in current implementation -use crate::client::config::MempoolStrategy; use crate::error::{NetworkError, NetworkResult}; use crate::network::peer::Peer; +use crate::MempoolStrategy; /// Handshake state. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/dash-spv/src/network/manager.rs b/dash-spv/src/network/manager.rs index 9f41d6566..9e36101e3 100644 --- a/dash-spv/src/network/manager.rs +++ b/dash-spv/src/network/manager.rs @@ -10,8 +10,13 @@ use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinSet; use tokio::time; -use crate::client::config::MempoolStrategy; -use crate::client::ClientConfig; +use crate::ClientConfig; +use crate::MempoolStrategy; +use dashcore::network::constants::ServiceFlags; +use dashcore::network::message::NetworkMessage; +use dashcore::Network; +use tokio_util::sync::CancellationToken; + use crate::error::{NetworkError, NetworkResult, SpvError as Error}; use crate::network::addrv2::AddrV2Handler; use crate::network::constants::*; @@ -24,11 +29,7 @@ use crate::network::reputation::{ use crate::network::{HandshakeManager, NetworkManager, Peer}; use crate::types::PeerInfo; use async_trait::async_trait; -use dashcore::network::constants::ServiceFlags; -use dashcore::network::message::NetworkMessage; use dashcore::network::message_headers2::CompressionState; -use dashcore::Network; -use tokio_util::sync::CancellationToken; /// Peer network manager pub struct PeerNetworkManager { @@ -81,8 +82,8 @@ impl PeerNetworkManager { let (message_tx, message_rx) = mpsc::channel(1000); let discovery = DnsDiscovery::new().await?; - let data_dir = config.storage_path.clone().unwrap_or_else(|| PathBuf::from(".")); - let peer_store = PeerStore::new(config.network, data_dir.clone()); + let data_dir = config.storage_path().clone(); + let peer_store = PeerStore::new(config.network(), data_dir.clone()); let reputation_manager = Arc::new(PeerReputationManager::new()); @@ -103,7 +104,7 @@ impl PeerNetworkManager { } // Determine exclusive mode: either explicitly requested or peers were provided - let exclusive_mode = config.restrict_to_configured_peers || !config.peers.is_empty(); + let exclusive_mode = config.restrict_to_configured_peers() || !config.peers().is_empty(); Ok(Self { pool: Arc::new(PeerPool::new()), @@ -111,19 +112,19 @@ impl PeerNetworkManager { addrv2_handler: Arc::new(AddrV2Handler::new()), peer_store: Arc::new(peer_store), reputation_manager, - network: config.network, + network: config.network(), shutdown_token: CancellationToken::new(), message_tx, message_rx: Arc::new(Mutex::new(message_rx)), tasks: Arc::new(Mutex::new(JoinSet::new())), - initial_peers: config.peers.clone(), + initial_peers: config.peers().clone(), peer_search_started: Arc::new(Mutex::new(None)), current_sync_peer: Arc::new(Mutex::new(None)), data_dir, - mempool_strategy: config.mempool_strategy, + mempool_strategy: config.mempool_strategy(), last_message_peer: Arc::new(Mutex::new(None)), peers_sent_headers2: Arc::new(Mutex::new(HashSet::new())), - user_agent: config.user_agent.clone(), + user_agent: config.user_agent().clone(), exclusive_mode, connected_peer_count: Arc::new(AtomicUsize::new(0)), headers2_disabled: Arc::new(Mutex::new(HashSet::new())), diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 2baaa6d54..7c4171843 100644 --- a/dash-spv/src/storage/mod.rs +++ b/dash-spv/src/storage/mod.rs @@ -32,7 +32,7 @@ use crate::storage::masternode::PersistentMasternodeStateStorage; use crate::storage::metadata::PersistentMetadataStorage; use crate::storage::transactions::PersistentTransactionStorage; use crate::types::{MempoolState, UnconfirmedTransaction}; -use crate::ChainState; +use crate::{ChainState, ClientConfig}; pub use crate::storage::blocks::BlockHeaderStorage; pub use crate::storage::chainstate::ChainStateStorage; @@ -94,10 +94,10 @@ pub struct DiskStorageManager { } impl DiskStorageManager { - pub async fn new(storage_path: impl Into + Send) -> StorageResult { + pub async fn new(config: &ClientConfig) -> StorageResult { use std::fs; - let storage_path = storage_path.into(); + let storage_path = config.storage_path().clone(); let lock_file = { let mut lock_file = storage_path.clone(); lock_file.set_extension("lock"); @@ -143,8 +143,16 @@ impl DiskStorageManager { pub async fn with_temp_dir() -> StorageResult { use tempfile::TempDir; + use crate::ClientConfigBuilder; + let temp_dir = TempDir::new()?; - Self::new(temp_dir.path()).await + Self::new( + &ClientConfigBuilder::testnet() + .storage_path(temp_dir.path()) + .build() + .expect("Valid configuration"), + ) + .await } /// Start the background worker saving data every 5 seconds @@ -394,16 +402,22 @@ impl masternode::MasternodeStateStorage for DiskStorageManager { #[cfg(test)] mod tests { + use crate::ClientConfigBuilder; + use super::*; use dashcore::Header as BlockHeader; - use tempfile::{tempdir, TempDir}; + use tempfile::TempDir; #[tokio::test] async fn test_store_load_headers() -> Result<(), Box> { // Create a temporary directory for the test - let temp_dir = TempDir::new()?; - let mut storage = - DiskStorageManager::new(temp_dir.path()).await.expect("Unable to create storage"); + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let config = ClientConfigBuilder::testnet() + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); + + let mut storage = DiskStorageManager::new(&config).await.expect("Unable to create storage"); let headers = BlockHeader::dummy_batch(0..60_000); @@ -433,8 +447,7 @@ mod tests { storage.shutdown().await; drop(storage); - let storage = - DiskStorageManager::new(temp_dir.path()).await.expect("Unable to open storage"); + let storage = DiskStorageManager::new(&config).await.expect("Unable to open storage"); let loaded_headers = storage.load_headers(49_999..50_002).await.unwrap(); assert_eq!(loaded_headers.len(), 3); @@ -445,12 +458,17 @@ mod tests { #[tokio::test] async fn test_checkpoint_storage_indexing() -> StorageResult<()> { - let temp_dir = tempdir().expect("Failed to create temp dir"); - let mut storage = DiskStorageManager::new(temp_dir.path()).await?; + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let config = ClientConfigBuilder::testnet() + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); + let mut storage = DiskStorageManager::new(&config).await?; // Create test headers starting from checkpoint height const CHECKPOINT_HEIGHT: u32 = 1_100_000; - let headers: Vec = BlockHeader::dummy_batch(0..100); + let headers: Vec = + BlockHeader::dummy_batch(CHECKPOINT_HEIGHT..CHECKPOINT_HEIGHT + 100); storage.store_headers_at_height(&headers, CHECKPOINT_HEIGHT).await?; @@ -459,7 +477,7 @@ mod tests { storage.shutdown().await; drop(storage); - let storage = DiskStorageManager::new(temp_dir.path()).await?; + let storage = DiskStorageManager::new(&config).await?; check_storage(&storage, &headers).await?; @@ -501,9 +519,13 @@ mod tests { #[tokio::test] async fn test_reverse_index_disk_storage() { let temp_dir = tempfile::tempdir().unwrap(); + let config = ClientConfigBuilder::regtest() + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); { - let mut storage = DiskStorageManager::new(temp_dir.path()).await.unwrap(); + let mut storage = DiskStorageManager::new(&config).await.unwrap(); // Create and store headers let headers = BlockHeader::dummy_batch(0..10); @@ -522,7 +544,7 @@ mod tests { // Test persistence - reload storage and verify index still works { - let storage = DiskStorageManager::new(&temp_dir.path()).await.unwrap(); + let storage = DiskStorageManager::new(&config).await.unwrap(); // The index should have been rebuilt from the loaded headers // We need to get the actual headers that were stored to test properly @@ -563,13 +585,15 @@ mod tests { lock_file.set_extension("lock"); lock_file }; + let config = + ClientConfigBuilder::regtest().storage_path(path).build().expect("Valid config"); - let mut storage1 = DiskStorageManager::new(&path).await.unwrap(); + let mut storage1 = DiskStorageManager::new(&config).await.unwrap(); assert!(lock_path.exists(), "Lock file should exist while storage is open"); storage1.clear().await.expect("Failed to clear the storage"); assert!(lock_path.exists(), "Lock file should exist after storage is cleared"); - let storage2 = DiskStorageManager::new(&path).await; + let storage2 = DiskStorageManager::new(&config).await; assert!(storage2.is_err(), "Second storage manager should fail"); // Lock file removed when storage drops @@ -577,7 +601,7 @@ mod tests { assert!(!lock_path.exists(), "Lock file should be removed after storage drops"); // Can reopen storage after previous one dropped - let storage3 = DiskStorageManager::new(&path).await; + let storage3 = DiskStorageManager::new(&config).await; assert!(storage3.is_ok(), "Should reopen after previous storage dropped"); } } diff --git a/dash-spv/src/sync/headers/manager.rs b/dash-spv/src/sync/headers/manager.rs index 950f258f2..e3707f027 100644 --- a/dash-spv/src/sync/headers/manager.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -12,8 +12,8 @@ use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; -use crate::sync::headers::validate_headers; use crate::types::{ChainState, HashedBlockHeader}; +use crate::validation::{BlockHeaderValidator, Validator}; use crate::ValidationMode; use std::sync::Arc; use tokio::sync::RwLock; @@ -66,7 +66,7 @@ impl HeaderSyncManager { // WalletState removed - wallet functionality is now handled externally // Create checkpoint manager based on network - let checkpoints = match config.network { + let checkpoints = match config.network() { dashcore::Network::Dash => mainnet_checkpoints(), dashcore::Network::Testnet => testnet_checkpoints(), _ => Vec::new(), // No checkpoints for other networks @@ -179,8 +179,8 @@ impl HeaderSyncManager { } } - if self.config.validation_mode != ValidationMode::None { - validate_headers(&cached_headers).map_err(|e| { + if self.config.validation_mode() != ValidationMode::None { + BlockHeaderValidator::new().validate(&cached_headers).map_err(|e| { let error = format!("Header validation failed: {}", e); tracing::error!(error); SyncError::Validation(error) @@ -297,7 +297,7 @@ impl HeaderSyncManager { // Normal sync from genesis let genesis_hash = self .config - .network + .network() .known_genesis_block_hash() .unwrap_or(BlockHash::from_byte_array([0; 32])); vec![genesis_hash] @@ -430,7 +430,7 @@ impl HeaderSyncManager { } else { // Use network genesis as fallback let genesis_hash = - self.config.network.known_genesis_block_hash().ok_or_else( + self.config.network().known_genesis_block_hash().ok_or_else( || SyncError::Storage("No known genesis hash".to_string()), )?; tracing::info!("Starting from network genesis: {}", genesis_hash); @@ -676,7 +676,7 @@ impl HeaderSyncManager { .map(|h| h.block_hash()) .ok_or_else(|| SyncError::MissingDependency("no tip header found".to_string()))? } else { - self.config.network.known_genesis_block_hash().ok_or_else(|| { + self.config.network().known_genesis_block_hash().ok_or_else(|| { SyncError::MissingDependency("no genesis block hash for network".to_string()) })? }; diff --git a/dash-spv/src/sync/headers/mod.rs b/dash-spv/src/sync/headers/mod.rs index 8005a84e9..830ce7958 100644 --- a/dash-spv/src/sync/headers/mod.rs +++ b/dash-spv/src/sync/headers/mod.rs @@ -1,7 +1,5 @@ //! Header synchronization with fork detection and reorganization handling. mod manager; -pub mod validation; pub use manager::{HeaderSyncManager, ReorgConfig}; -pub use validation::validate_headers; diff --git a/dash-spv/src/sync/manager.rs b/dash-spv/src/sync/manager.rs index 3b1d5d1fc..818d13de7 100644 --- a/dash-spv/src/sync/manager.rs +++ b/dash-spv/src/sync/manager.rs @@ -148,11 +148,6 @@ impl SyncManager Option { - self.config.start_from_height - } - /// Start the sequential sync process pub async fn start_sync(&mut self, network: &mut N, storage: &mut S) -> SyncResult { if self.current_phase.is_syncing() { @@ -295,7 +290,7 @@ impl SyncManager { impl MasternodeSyncManager { /// Create a new masternode sync manager. pub fn new(config: &ClientConfig) -> Self { - let (engine, mnlist_diffs) = if config.enable_masternodes { + let (engine, mnlist_diffs) = if config.enable_masternodes() { // Try to load embedded MNListDiff data for faster initial sync - if let Some(embedded) = super::embedded_data::get_embedded_diff(config.network) { + if let Some(embedded) = super::embedded_data::get_embedded_diff(config.network()) { tracing::info!( "📦 Using embedded MNListDiff for {} - starting from height {}", - config.network, + config.network(), embedded.target_height ); @@ -67,7 +67,7 @@ impl MasternodeSyncManager { match MasternodeListEngine::initialize_with_diff_to_height( embedded.diff.clone(), embedded.target_height, - config.network, + config.network(), ) { Ok(engine) => { // Store the embedded diff in our cache @@ -80,9 +80,10 @@ impl MasternodeSyncManager { "Failed to initialize engine with embedded diff: {}. Falling back to default.", e ); - let mut engine = MasternodeListEngine::default_for_network(config.network); + let mut engine = + MasternodeListEngine::default_for_network(config.network()); // Feed genesis block hash at height 0 - if let Some(genesis_hash) = config.network.known_genesis_block_hash() { + if let Some(genesis_hash) = config.network().known_genesis_block_hash() { engine.feed_block_height(0, genesis_hash); } (Some(engine), HashMap::new()) @@ -91,11 +92,11 @@ impl MasternodeSyncManager { } else { tracing::info!( "No embedded MNListDiff available for {} - starting from genesis", - config.network + config.network() ); - let mut engine = MasternodeListEngine::default_for_network(config.network); + let mut engine = MasternodeListEngine::default_for_network(config.network()); // Feed genesis block hash at height 0 - if let Some(genesis_hash) = config.network.known_genesis_block_hash() { + if let Some(genesis_hash) = config.network().known_genesis_block_hash() { engine.feed_block_height(0, genesis_hash); } (Some(engine), HashMap::new()) @@ -194,7 +195,7 @@ impl MasternodeSyncManager { storage: &S, ) -> Result { // Special case: Handle genesis block which isn't stored when syncing from checkpoints - if let Some(genesis_hash) = self.config.network.known_genesis_block_hash() { + if let Some(genesis_hash) = self.config.network().known_genesis_block_hash() { if *block_hash == genesis_hash { return Ok(0); } @@ -408,7 +409,7 @@ impl MasternodeSyncManager { } else { // First time - use genesis block let genesis_hash = - self.config.network.known_genesis_block_hash().ok_or_else(|| { + self.config.network().known_genesis_block_hash().ok_or_else(|| { SyncError::InvalidState("Genesis hash not available".to_string()) })?; tracing::debug!("Using genesis block as base: {}", genesis_hash); @@ -523,7 +524,7 @@ impl MasternodeSyncManager { { last_qrinfo_hash } else { - self.config.network.known_genesis_block_hash().ok_or_else(|| { + self.config.network().known_genesis_block_hash().ok_or_else(|| { SyncError::InvalidState("Genesis hash not available".to_string()) })? }; diff --git a/dash-spv/src/sync/post_sync.rs b/dash-spv/src/sync/post_sync.rs index c505ebf25..78d7710cf 100644 --- a/dash-spv/src/sync/post_sync.rs +++ b/dash-spv/src/sync/post_sync.rs @@ -148,7 +148,7 @@ impl SyncManager SyncManager state.last_height, @@ -267,7 +267,7 @@ impl SyncManager SyncManager 0 { storage @@ -316,7 +316,8 @@ impl SyncManager Ok(self.config.enable_masternodes), + } => Ok(self.config.enable_masternodes()), SyncPhase::DownloadingCFHeaders { .. - } => Ok(!self.config.enable_masternodes && self.config.enable_filters), + } => Ok(!self.config.enable_masternodes() && self.config.enable_filters()), SyncPhase::FullySynced { .. - } => Ok(!self.config.enable_masternodes && !self.config.enable_filters), + } => Ok(!self.config.enable_masternodes() && !self.config.enable_filters()), _ => Ok(false), } } @@ -86,10 +86,10 @@ impl TransitionManager { match next_phase { SyncPhase::DownloadingCFHeaders { .. - } => Ok(self.config.enable_filters), + } => Ok(self.config.enable_filters()), SyncPhase::FullySynced { .. - } => Ok(!self.config.enable_filters), + } => Ok(!self.config.enable_filters()), _ => Ok(false), } } @@ -194,7 +194,7 @@ impl TransitionManager { SyncPhase::DownloadingHeaders { .. } => { - if self.config.enable_masternodes { + if self.config.enable_masternodes() { let header_tip = storage.get_tip_height().await.unwrap_or(0); let mn_height = match storage.load_masternode_state().await { @@ -212,7 +212,7 @@ impl TransitionManager { requests_total: 0, requests_completed: 0, })) - } else if self.config.enable_filters { + } else if self.config.enable_filters() { self.create_cfheaders_phase(storage).await } else { self.create_fully_synced_phase(storage).await @@ -222,7 +222,7 @@ impl TransitionManager { SyncPhase::DownloadingMnList { .. } => { - if self.config.enable_filters { + if self.config.enable_filters() { self.create_cfheaders_phase(storage).await } else { self.create_fully_synced_phase(storage).await diff --git a/dash-spv/src/sync/headers/validation.rs b/dash-spv/src/validation/header.rs similarity index 59% rename from dash-spv/src/sync/headers/validation.rs rename to dash-spv/src/validation/header.rs index 761fe9331..8141e9992 100644 --- a/dash-spv/src/sync/headers/validation.rs +++ b/dash-spv/src/validation/header.rs @@ -1,53 +1,59 @@ -//! Header validation functionality. - use rayon::prelude::*; use std::time::Instant; use crate::error::{ValidationError, ValidationResult}; use crate::types::HashedBlockHeader; +use crate::validation::Validator; -/// Validate a chain of headers. -pub fn validate_headers(hashed_headers: &[HashedBlockHeader]) -> ValidationResult<()> { - let start = Instant::now(); - - // Check PoW of i and continuity of i-1 to i in parallel - hashed_headers.par_iter().enumerate().try_for_each(|(i, header)| { - // For the first header, skip chain continuity check since we don't have i-1 here - if i > 0 && header.header().prev_blockhash != *hashed_headers[i - 1].hash() { - return Err(ValidationError::InvalidHeaderChain(format!( - "Header {:?} does not connect to {:?}", - hashed_headers[i - 1], - header - ))); - } - // Check if PoW target is met - if !header.header().target().is_met_by(*header.hash()) { - return Err(ValidationError::InvalidProofOfWork); - } - Ok(()) - })?; +#[derive(Default)] +pub struct BlockHeaderValidator {} - tracing::trace!( - "Header chain validation passed for {} headers, duration: {:?}", - hashed_headers.len(), - start.elapsed(), - ); +impl BlockHeaderValidator { + pub fn new() -> Self { + Self {} + } +} + +impl Validator<&[HashedBlockHeader]> for BlockHeaderValidator { + fn validate(&self, hashed_headers: &[HashedBlockHeader]) -> ValidationResult<()> { + let start = Instant::now(); + + // Check PoW of i and continuity of i-1 to i in parallel + hashed_headers.par_iter().enumerate().try_for_each(|(i, header)| { + // For the first header, skip chain continuity check since we don't have i-1 here + if i > 0 && header.header().prev_blockhash != *hashed_headers[i - 1].hash() { + return Err(ValidationError::InvalidHeaderChain(format!( + "Header {:?} does not connect to {:?}", + hashed_headers[i - 1], + header + ))); + } + // Check if PoW target is met + if !header.header().target().is_met_by(*header.hash()) { + return Err(ValidationError::InvalidProofOfWork); + } + Ok(()) + })?; + + tracing::trace!( + "Header chain validation passed for {} headers, duration: {:?}", + hashed_headers.len(), + start.elapsed(), + ); - Ok(()) + Ok(()) + } } #[cfg(test)] mod tests { - use super::validate_headers; - use crate::error::ValidationError; - use crate::types::HashedBlockHeader; use dashcore::{ - block::{Header as BlockHeader, Version}, - blockdata::constants::genesis_block, - CompactTarget, Network, + block::Version, constants::genesis_block, CompactTarget, Header as BlockHeader, Network, }; use dashcore_hashes::Hash; + use super::*; + // Very easy target to pass PoW checks for continuity tests const MAX_TARGET: u32 = 0x2100ffff; @@ -64,17 +70,23 @@ mod tests { #[test] fn test_empty_headers() { - assert!(validate_headers(&[]).is_ok()); + let validator = BlockHeaderValidator::new(); + + assert!(validator.validate(&[]).is_ok()); } #[test] fn test_single_header() { + let validator = BlockHeaderValidator::new(); + let header = create_test_header(dashcore::BlockHash::all_zeros(), 0); - assert!(validate_headers(&[header]).is_ok()); + assert!(validator.validate(&[header]).is_ok()); } #[test] fn test_valid_chain() { + let validator = BlockHeaderValidator::new(); + let mut headers = vec![]; let mut prev_hash = dashcore::BlockHash::all_zeros(); @@ -84,22 +96,26 @@ mod tests { headers.push(header); } - assert!(validate_headers(&headers).is_ok()); + assert!(validator.validate(&headers).is_ok()); } #[test] fn test_broken_chain() { + let validator = BlockHeaderValidator::new(); + let header1 = create_test_header(dashcore::BlockHash::all_zeros(), 0); let header2 = create_test_header(*header1.hash(), 1); // header3 doesn't connect to header2 let header3 = create_test_header(dashcore::BlockHash::all_zeros(), 2); - let result = validate_headers(&[header1, header2, header3]); + let result = validator.validate(&[header1, header2, header3]); assert!(matches!(result, Err(ValidationError::InvalidHeaderChain(_)))); } #[test] fn test_invalid_pow() { + let validator = BlockHeaderValidator::new(); + let header = HashedBlockHeader::from(BlockHeader { version: Version::from_consensus(1), prev_blockhash: dashcore::BlockHash::all_zeros(), @@ -109,16 +125,18 @@ mod tests { nonce: 0, }); - let result = validate_headers(&[header]); + let result = validator.validate(&[header]); assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); } #[test] fn test_genesis_blocks() { + let validator = BlockHeaderValidator::new(); + for network in [Network::Dash, Network::Testnet, Network::Regtest] { let genesis = HashedBlockHeader::from(genesis_block(network).header); assert!( - validate_headers(&[genesis]).is_ok(), + validator.validate(&[genesis]).is_ok(), "Genesis block for {:?} should validate", network ); @@ -127,6 +145,8 @@ mod tests { #[test] fn test_invalid_pow_mid_chain() { + let validator = BlockHeaderValidator::new(); + let header1 = create_test_header(dashcore::BlockHash::all_zeros(), 0); let header2 = create_test_header(*header1.hash(), 1); @@ -142,7 +162,7 @@ mod tests { let header4 = create_test_header(*header3.hash(), 3); - let result = validate_headers(&[header1, header2, header3, header4]); + let result = validator.validate(&[header1, header2, header3, header4]); assert!(matches!(result, Err(ValidationError::InvalidProofOfWork))); } } diff --git a/dash-spv/src/validation/instantlock.rs b/dash-spv/src/validation/instantlock.rs index 0af3b4f9d..4e8be55e2 100644 --- a/dash-spv/src/validation/instantlock.rs +++ b/dash-spv/src/validation/instantlock.rs @@ -1,42 +1,29 @@ //! InstantLock validation functionality. -use dashcore::InstantLock; +use dashcore::{sml::masternode_list_engine::MasternodeListEngine, InstantLock}; use dashcore_hashes::Hash; -use crate::error::{ValidationError, ValidationResult}; +use crate::{ + error::{ValidationError, ValidationResult}, + validation::Validator, +}; -/// Validates InstantLock messages. -#[derive(Default)] -pub struct InstantLockValidator { - // Quorum manager is now passed as parameter to validate_signature - // to avoid circular dependencies and allow flexible usage +/// Validates InstantLock messages. Requires a masternode engine to verify +/// BLS signatures. Never accept InstantLocks from the network without full +/// signature verification. +pub struct InstantLockValidator<'a> { + masternode_engine: &'a MasternodeListEngine, } -impl InstantLockValidator { - /// Create a new InstantLock validator. - pub fn new() -> Self { - Self {} - } - +impl Validator<&InstantLock> for InstantLockValidator<'_> { /// Validate an InstantLock with full BLS signature verification. /// /// This performs complete validation including: /// - Structural validation (non-zero txid, signature, inputs) /// - BLS signature verification using cyclehash-based quorum selection (DIP 24) - /// - /// **Security Critical**: This method requires a masternode engine to verify - /// BLS signatures. Never accept InstantLocks from the network without full - /// signature verification. - pub fn validate( - &self, - instant_lock: &InstantLock, - masternode_engine: &dashcore::sml::masternode_list_engine::MasternodeListEngine, - ) -> ValidationResult<()> { - // Perform structural validation + fn validate(&self, instant_lock: &InstantLock) -> ValidationResult<()> { self.validate_structure(instant_lock)?; - - // Perform BLS signature verification (REQUIRED for security) - self.validate_signature(instant_lock, masternode_engine)?; + self.validate_signature(instant_lock)?; tracing::debug!( "InstantLock fully validated (structure + signature) for txid {}", @@ -45,11 +32,17 @@ impl InstantLockValidator { Ok(()) } +} - /// Validate InstantLock structure (without BLS signature verification). - /// - /// **WARNING**: This is insufficient for accepting network messages. - /// For production use, always call `validate()` with a masternode engine. +impl<'a> InstantLockValidator<'a> { + pub fn new(masternode_engine: &'a MasternodeListEngine) -> Self { + Self { + masternode_engine, + } + } + + /// This method is a helper for validating the structure of an InstanLock. + /// Don't call or expose this method directly, use `validate()` instead. fn validate_structure(&self, instant_lock: &InstantLock) -> ValidationResult<()> { // Check transaction ID is not zero (null txid) if instant_lock.txid == dashcore::Txid::all_zeros() { @@ -85,21 +78,14 @@ impl InstantLockValidator { Ok(()) } - /// Validate InstantLock signature using the masternode list engine. - /// - /// This properly uses the cyclehash to select the correct quorum according to DIP 24. - /// The MasternodeListEngine tracks rotated quorums per cycle and selects the specific - /// quorum based on the request_id. - fn validate_signature( - &self, - instant_lock: &InstantLock, - masternode_engine: &dashcore::sml::masternode_list_engine::MasternodeListEngine, - ) -> ValidationResult<()> { + /// This method is a helper for validating the signature of an InstanLock. + /// Don't call or expose this method directly, use `validate()` instead. + fn validate_signature(&self, instant_lock: &InstantLock) -> ValidationResult<()> { // Use the proper verification from the masternode engine which: // 1. Uses cyclehash to get the set of rotated quorums // 2. Uses request_id to select the specific quorum (DIP 24) // 3. Verifies the BLS signature with that quorum's public key - masternode_engine.verify_is_lock(instant_lock).map_err(|e| { + self.masternode_engine.verify_is_lock(instant_lock).map_err(|e| { ValidationError::InvalidSignature(format!( "InstantLock BLS signature verification failed: {}", e @@ -119,11 +105,14 @@ impl InstantLockValidator { #[cfg(test)] mod tests { use super::*; + use dashcore::Network; use dashcore_hashes::Hash; #[test] fn test_valid_instantlock() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); + let is_lock = InstantLock::dummy(0..3); // Structural validation only (for testing) @@ -132,7 +121,8 @@ mod tests { #[test] fn test_empty_inputs() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); let mut is_lock = InstantLock::dummy(0..3); is_lock.inputs.clear(); @@ -143,7 +133,8 @@ mod tests { #[test] fn test_empty_signature() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); let mut is_lock = InstantLock::dummy(0..3); is_lock.signature = dashcore::bls_sig_utils::BLSSignature::from([0; 96]); @@ -155,7 +146,8 @@ mod tests { #[test] fn test_null_txid() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); let mut is_lock = InstantLock::dummy(0..3); is_lock.txid = dashcore::Txid::all_zeros(); @@ -167,7 +159,8 @@ mod tests { #[test] fn test_null_input_txid() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); let mut is_lock = InstantLock::dummy(0..3); // Set the second input to have a null txid is_lock.inputs[1].txid = dashcore::Txid::all_zeros(); @@ -203,7 +196,8 @@ mod tests { #[test] fn test_edge_case_many_inputs() { - let validator = InstantLockValidator::new(); + let masternode_engine = MasternodeListEngine::default_for_network(Network::Testnet); + let validator = InstantLockValidator::new(&masternode_engine); // Create lock with many inputs let lock = InstantLock::dummy(0..100); diff --git a/dash-spv/src/validation/mod.rs b/dash-spv/src/validation/mod.rs index 90de43745..9ae730b37 100644 --- a/dash-spv/src/validation/mod.rs +++ b/dash-spv/src/validation/mod.rs @@ -1,5 +1,13 @@ //! Validation functionality for the Dash SPV client. +mod header; mod instantlock; +pub use header::BlockHeaderValidator; pub use instantlock::InstantLockValidator; + +use crate::error::ValidationResult; + +pub trait Validator { + fn validate(&self, data: T) -> ValidationResult<()>; +} diff --git a/dash-spv/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index f797520c3..d1c6d9a72 100644 --- a/dash-spv/tests/block_download_test.rs +++ b/dash-spv/tests/block_download_test.rs @@ -1,6 +1,7 @@ //! Tests for block downloading on filter match functionality. use dash_spv::test_utils::MockNetworkManager; +use dash_spv::ClientConfigBuilder; use std::collections::HashSet; use std::sync::Arc; use tempfile::TempDir; @@ -13,9 +14,12 @@ use dash_spv::{ }; fn create_test_config() -> ClientConfig { - ClientConfig::testnet() - .without_masternodes() - .with_validation_mode(dash_spv::types::ValidationMode::None) + ClientConfigBuilder::testnet() + .enable_masternodes(false) + .validation_mode(dash_spv::types::ValidationMode::None) + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config") } #[tokio::test] @@ -138,9 +142,7 @@ async fn test_sync_manager_integration() {} #[tokio::test] async fn test_filter_match_and_download_workflow() { let config = create_test_config(); - let _storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let _storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); diff --git a/dash-spv/tests/chainlock_simple_test.rs b/dash-spv/tests/chainlock_simple_test.rs index 14e68d81a..8a066691b 100644 --- a/dash-spv/tests/chainlock_simple_test.rs +++ b/dash-spv/tests/chainlock_simple_test.rs @@ -1,9 +1,10 @@ //! Simple integration test for ChainLock validation flow -use dash_spv::client::{ClientConfig, DashSpvClient}; +use dash_spv::client::DashSpvClient; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; +use dash_spv::ClientConfigBuilder; use dashcore::Network; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -27,30 +28,26 @@ async fn test_chainlock_validation_flow() { // Create temp directory for storage let temp_dir = TempDir::new().unwrap(); - let storage_path = temp_dir.path().to_path_buf(); // Create client config with masternodes enabled let network = Network::Dash; let enable_masternodes = true; - let config = ClientConfig { - network, - enable_filters: false, - enable_masternodes, - validation_mode: ValidationMode::Basic, - storage_path: Some(storage_path), - peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config - ..Default::default() - }; + let config = ClientConfigBuilder::mainnet() + .enable_filters(false) + .enable_masternodes(true) + .validation_mode(ValidationMode::Basic) + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = - DiskStorageManager::new(config.storage_path.clone().unwrap()).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); // Create the SPV client let client = @@ -78,28 +75,24 @@ async fn test_chainlock_manager_initialization() { // Create temp directory for storage let temp_dir = TempDir::new().unwrap(); - let storage_path = temp_dir.path().to_path_buf(); // Create client config - let config = ClientConfig { - network: Network::Dash, - enable_filters: false, - enable_masternodes: false, - validation_mode: ValidationMode::Basic, - storage_path: Some(storage_path), - peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config - ..Default::default() - }; + let config = ClientConfigBuilder::mainnet() + .enable_filters(false) + .enable_masternodes(false) + .validation_mode(ValidationMode::Basic) + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = - DiskStorageManager::new(config.storage_path.clone().unwrap()).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); // Create the SPV client let client = diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index f1fe6a85c..80ac10518 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -6,15 +6,15 @@ use tempfile::TempDir; use tokio::sync::Mutex; use dash_spv::{ - client::ClientConfig, error::NetworkResult, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, + ClientConfigBuilder, }; use dashcore::{ block::Header as BlockHeader, hash_types::FilterHeader, network::message::NetworkMessage, - BlockHash, Network, + BlockHash, }; use dashcore_hashes::Hash; @@ -109,14 +109,15 @@ impl NetworkManager for MockNetworkManager { #[tokio::test] async fn test_filter_sync_at_tip_edge_case() { - let config = ClientConfig::new(Network::Dash); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + let received_heights = Arc::new(Mutex::new(HashSet::new())); - let mut filter_sync: FilterSyncManager = - FilterSyncManager::new(&config, received_heights); + let mut filter_sync = FilterSyncManager::new(&config, received_heights); - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); // Set up storage with headers and filter headers at the same height (tip) @@ -153,14 +154,15 @@ async fn test_filter_sync_at_tip_edge_case() { #[tokio::test] async fn test_no_invalid_getcfheaders_at_tip() { - let config = ClientConfig::new(Network::Dash); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + let received_heights = Arc::new(Mutex::new(HashSet::new())); - let mut filter_sync: FilterSyncManager = - FilterSyncManager::new(&config, received_heights); + let mut filter_sync = FilterSyncManager::new(&config, received_heights); - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); // Create a scenario where we're one block behind diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index 60b748294..6ac8aaeef 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -9,19 +9,19 @@ //! are calculated, stored, or verified across multiple batches. use dash_spv::{ - client::ClientConfig, error::{NetworkError, NetworkResult, SyncError}, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, types::PeerInfo, + ClientConfigBuilder, }; use dashcore::{ block::{Header as BlockHeader, Version}, hash_types::{FilterHash, FilterHeader}, network::message::NetworkMessage, network::message_filter::CFHeaders, - BlockHash, Network, + BlockHash, }; use dashcore_hashes::{sha256d, Hash}; use std::collections::HashSet; @@ -175,12 +175,14 @@ async fn test_filter_header_verification_failure_reproduction() { println!("=== Testing Filter Header Chain Verification Failure ==="); // Create storage and sync manager - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); - let config = ClientConfig::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -339,12 +341,14 @@ async fn test_overlapping_batches_from_different_peers() { // The system should handle this gracefully, but currently it crashes. // This test will FAIL until we implement the fix. - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); - let config = ClientConfig::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -515,12 +519,14 @@ async fn test_filter_header_verification_overlapping_batches() { // This test simulates what happens when we receive overlapping filter header batches // due to recovery/retry mechanisms or multiple peers - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); - let config = ClientConfig::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -613,12 +619,14 @@ async fn test_filter_header_verification_race_condition_simulation() { // This test simulates the race condition that might occur when multiple // filter header requests are in flight simultaneously - let mut storage = DiskStorageManager::new(TempDir::new().unwrap().path().to_path_buf()) - .await - .expect("Failed to create tmp storage"); + let config = ClientConfigBuilder::mainnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); - let config = ClientConfig::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); diff --git a/dash-spv/tests/handshake_test.rs b/dash-spv/tests/handshake_test.rs index 302cc3bf7..fa267c00c 100644 --- a/dash-spv/tests/handshake_test.rs +++ b/dash-spv/tests/handshake_test.rs @@ -3,9 +3,9 @@ use std::net::SocketAddr; use std::time::Duration; -use dash_spv::client::config::MempoolStrategy; use dash_spv::network::{HandshakeManager, NetworkManager, Peer, PeerNetworkManager}; -use dash_spv::{ClientConfig, Network}; +use dash_spv::Network; +use dash_spv::{ClientConfigBuilder, MempoolStrategy}; #[tokio::test] async fn test_handshake_with_mainnet_peer() { @@ -73,7 +73,7 @@ async fn test_handshake_timeout() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_network_manager_creation() { - let config = ClientConfig::new(Network::Dash); + let config = ClientConfigBuilder::mainnet().build().expect("Valid config"); let network = PeerNetworkManager::new(&config).await; assert!(network.is_ok(), "Network manager creation should succeed"); diff --git a/dash-spv/tests/header_sync_test.rs b/dash-spv/tests/header_sync_test.rs index 8714e4b26..811f0d8f8 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -1,13 +1,14 @@ //! Integration tests for header synchronization functionality. use dash_spv::{ - client::{ClientConfig, DashSpvClient}, + client::DashSpvClient, network::PeerNetworkManager, storage::{BlockHeaderStorage, ChainStateStorage, DiskStorageManager}, sync::{HeaderSyncManager, ReorgConfig}, types::{ChainState, ValidationMode}, + ClientConfigBuilder, }; -use dashcore::{block::Header as BlockHeader, block::Version, Network}; +use dashcore::{block::Header as BlockHeader, block::Version}; use dashcore_hashes::Hash; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -22,20 +23,21 @@ async fn test_header_sync_with_client_integration() { let _ = env_logger::try_init(); // Test header sync integration with the full client - let config = ClientConfig::new(Network::Dash).with_validation_mode(ValidationMode::Basic); + let config = ClientConfigBuilder::mainnet() + .validation_mode(ValidationMode::Basic) + .storage_path(TempDir::new().expect("Failed to create tmp dir").path()) + .build() + .expect("Valid config"); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.expect("Failed to create network manager"); // Create storage manager - let storage_manager = - DiskStorageManager::new(TempDir::new().expect("Failed to create tmp dir").path()) - .await - .expect("Failed to create tmp storage"); + let storage_manager = DiskStorageManager::new(&config).await.expect("Failed to create storage"); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); let client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await; assert!(client.is_ok(), "Client creation should succeed"); @@ -88,21 +90,21 @@ fn create_test_header_chain_from(start: usize, count: usize) -> Vec #[tokio::test] async fn test_prepare_sync(sync_base_height: u32, header_count: usize) { let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let mut storage = - DiskStorageManager::new(temp_dir.path()).await.expect("Failed to create storage"); + let config = + ClientConfigBuilder::regtest().storage_path(temp_dir.path()).build().expect("Valid config"); + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create storage"); let headers = create_test_header_chain(header_count); let expected_tip_hash = headers.last().unwrap().block_hash(); // Create and store chain state - let mut chain_state = ChainState::new_for_network(Network::Dash); + let mut chain_state = ChainState::new_for_network(config.network()); chain_state.sync_base_height = sync_base_height; storage.store_chain_state(&chain_state).await.expect("Failed to store chain state"); storage.store_headers(&headers).await.expect("Failed to store headers"); // Create HeaderSyncManager and load from storage - let config = ClientConfig::new(Network::Dash); - let chain_state_arc = Arc::new(RwLock::new(ChainState::new_for_network(Network::Dash))); + let chain_state_arc = Arc::new(RwLock::new(ChainState::new_for_network(config.network()))); let mut header_sync = HeaderSyncManager::::new( &config, ReorgConfig::default(), diff --git a/dash-spv/tests/peer_test.rs b/dash-spv/tests/peer_test.rs index 82521ae4e..a347c0eb7 100644 --- a/dash-spv/tests/peer_test.rs +++ b/dash-spv/tests/peer_test.rs @@ -1,5 +1,6 @@ //! Integration tests for peer networking +use dash_spv::ClientConfigBuilder; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -14,16 +15,18 @@ use dash_spv::types::ValidationMode; use dashcore::Network; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; + /// Create a test configuration with the given network -fn create_test_config(network: Network, data_dir: Option) -> ClientConfig { - let mut config = ClientConfig::new(network); - config.storage_path = data_dir.map(|d| d.path().to_path_buf()); - config.validation_mode = ValidationMode::Basic; - config.enable_filters = false; - config.enable_masternodes = false; - config.max_peers = 3; - config.peers = vec![]; // Will be populated by DNS discovery - config +fn create_test_config(network: Network) -> ClientConfig { + ClientConfigBuilder::default() + .network(network) + .storage_path(TempDir::new().unwrap().path()) + .validation_mode(ValidationMode::Basic) + .enable_filters(false) + .enable_masternodes(false) + .max_peers(3) + .build() + .expect("Valid config") } #[tokio::test] @@ -31,18 +34,16 @@ fn create_test_config(network: Network, data_dir: Option) -> ClientConf async fn test_peer_connection() { let _ = env_logger::builder().is_test(true).try_init(); - let temp_dir = TempDir::new().unwrap(); - let temp_path = temp_dir.path().to_path_buf(); - let config = create_test_config(Network::Testnet, Some(temp_dir)); + let config = create_test_config(Network::Testnet); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = DiskStorageManager::new(temp_path).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); let mut client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); @@ -75,24 +76,24 @@ async fn test_peer_connection() { async fn test_peer_persistence() { let _ = env_logger::builder().is_test(true).try_init(); - let temp_dir = TempDir::new().unwrap(); - let temp_path = temp_dir.path().to_path_buf(); + let config = create_test_config(Network::Testnet); // First run: connect and save peers { - let config = create_test_config(Network::Testnet, Some(temp_dir)); - // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = DiskStorageManager::new(temp_path.clone()).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = + Arc::new(RwLock::new(WalletManager::::new(config.network()))); let mut client = - DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); + DashSpvClient::new(config.clone(), network_manager, storage_manager, wallet) + .await + .unwrap(); client.start().await.unwrap(); time::sleep(Duration::from_secs(5)).await; @@ -105,17 +106,15 @@ async fn test_peer_persistence() { // Second run: should load saved peers { - let mut config = create_test_config(Network::Testnet, None); - config.storage_path = Some(temp_path.clone()); - // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - reuse same path - let storage_manager = DiskStorageManager::new(temp_path).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = + Arc::new(RwLock::new(WalletManager::::new(config.network()))); let mut client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); @@ -141,21 +140,21 @@ async fn test_peer_persistence() { async fn test_peer_disconnection() { let _ = env_logger::builder().is_test(true).try_init(); - let temp_dir = TempDir::new().unwrap(); - let temp_path = temp_dir.path().to_path_buf(); - let mut config = create_test_config(Network::Regtest, Some(temp_dir)); + let mut config = create_test_config(Network::Regtest); // Add manual test peers (would need actual regtest nodes running) - config.peers = vec!["127.0.0.1:19899".parse().unwrap(), "127.0.0.1:19898".parse().unwrap()]; + config + .add_peer("127.0.0.1:19899".parse().unwrap()) + .add_peer("127.0.0.1:19898".parse().unwrap()); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = DiskStorageManager::new(temp_path).await.unwrap(); + let storage_manager = DiskStorageManager::new(&config).await.unwrap(); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); let client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); @@ -177,23 +176,20 @@ async fn test_max_peer_limit() { let _ = env_logger::builder().is_test(true).try_init(); - let temp_dir = TempDir::new().unwrap(); - let mut config = create_test_config(Network::Testnet, Some(temp_dir)); + let mut config = create_test_config(Network::Testnet); // Add at least one peer to avoid "No peers specified" error - config.peers = vec!["127.0.0.1:19999".parse().unwrap()]; + config.add_peer("127.0.0.1:19999".parse().unwrap()); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager let storage_manager = - DiskStorageManager::new(TempDir::new().expect("Failed to create tmp dir").path()) - .await - .expect("Failed to create tmp storage"); + DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); let _client = DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap(); diff --git a/dash-spv/tests/smart_fetch_integration_test.rs b/dash-spv/tests/smart_fetch_integration_test.rs index cc3f279bb..154d2d5d7 100644 --- a/dash-spv/tests/smart_fetch_integration_test.rs +++ b/dash-spv/tests/smart_fetch_integration_test.rs @@ -1,4 +1,4 @@ -use dash_spv::client::ClientConfig; +use dash_spv::ClientConfigBuilder; use dashcore::network::message_sml::MnListDiff; use dashcore::sml::llmq_type::network::NetworkLLMQExt; use dashcore::sml::llmq_type::{DKGWindow, LLMQType}; @@ -28,7 +28,7 @@ async fn test_smart_fetch_basic_dkg_windows() { #[tokio::test] async fn test_smart_fetch_state_initialization() { // Create a simple config for testing - let config = ClientConfig::new(Network::Testnet); + let config = ClientConfigBuilder::testnet().build().expect("Valid config"); // Test that we can create the sync manager // Note: We can't access private fields, but we can verify the structure exists diff --git a/dash-spv/tests/test_handshake_logic.rs b/dash-spv/tests/test_handshake_logic.rs index 091ace062..184229b5c 100644 --- a/dash-spv/tests/test_handshake_logic.rs +++ b/dash-spv/tests/test_handshake_logic.rs @@ -1,7 +1,7 @@ //! Unit tests for handshake logic -use dash_spv::client::config::MempoolStrategy; use dash_spv::network::{HandshakeManager, HandshakeState}; +use dash_spv::MempoolStrategy; use dashcore::Network; #[test] diff --git a/dash-spv/tests/wallet_integration_test.rs b/dash-spv/tests/wallet_integration_test.rs index 8dd8d5c1b..125d4361e 100644 --- a/dash-spv/tests/wallet_integration_test.rs +++ b/dash-spv/tests/wallet_integration_test.rs @@ -8,26 +8,28 @@ use tokio::sync::RwLock; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{ClientConfig, DashSpvClient}; +use dash_spv::{ClientConfigBuilder, DashSpvClient}; use dashcore::Network; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; /// Create a test SPV client with memory storage for integration testing. async fn create_test_client( ) -> DashSpvClient, PeerNetworkManager, DiskStorageManager> { - let config = ClientConfig::testnet().without_filters().without_masternodes(); + let config = ClientConfigBuilder::testnet() + .enable_filters(false) + .storage_path(TempDir::new().unwrap().path()) + .enable_masternodes(true) + .build() + .expect("Valid config"); // Create network manager let network_manager = PeerNetworkManager::new(&config).await.unwrap(); // Create storage manager - let storage_manager = - DiskStorageManager::new(TempDir::new().expect("Failed to create tmp dir").path()) - .await - .expect("Failed to create tmp storage"); + let storage_manager = DiskStorageManager::new(&config).await.expect("Failed to create storage"); // Create wallet manager - let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); + let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network()))); DashSpvClient::new(config, network_manager, storage_manager, wallet).await.unwrap() }