From 732d4f2ab8f3ad4854b2968d88f654e9542868aa Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 9 Jan 2026 20:24:13 +0000 Subject: [PATCH 01/12] config storage_path is no longer an option and the DiskStorageManager is built using the config --- dash-spv-ffi/src/client.rs | 27 +----------- dash-spv-ffi/src/config.rs | 10 +---- dash-spv-ffi/tests/test_wallet_manager.rs | 15 ++++++- dash-spv/benches/storage.rs | 10 +++-- dash-spv/examples/filter_sync.rs | 6 ++- dash-spv/examples/simple_sync.rs | 3 +- dash-spv/examples/spv_with_wallet.rs | 9 ++-- dash-spv/src/client/config.rs | 17 +++++--- dash-spv/src/client/config_test.rs | 8 +--- dash-spv/src/client/mod.rs | 31 +++++++------- dash-spv/src/lib.rs | 4 +- dash-spv/src/network/manager.rs | 2 +- dash-spv/src/storage/mod.rs | 39 ++++++++++-------- dash-spv/tests/block_download_test.rs | 5 +-- dash-spv/tests/chainlock_simple_test.rs | 12 ++---- dash-spv/tests/edge_case_filter_sync_test.rs | 20 ++++----- .../tests/filter_header_verification_test.rs | 26 ++++++------ dash-spv/tests/header_sync_test.rs | 13 +++--- dash-spv/tests/peer_test.rs | 41 ++++++++----------- dash-spv/tests/wallet_integration_test.rs | 10 ++--- 20 files changed, 138 insertions(+), 170 deletions(-) diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 8848d188b..47ee04178 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -147,35 +147,12 @@ 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); diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 2e9133e08..ffb8d8477 100644 --- a/dash-spv-ffi/src/config.rs +++ b/dash-spv-ffi/src/config.rs @@ -77,7 +77,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.storage_path = path_str.into(); FFIErrorCode::Success as i32 } Err(e) => { @@ -331,13 +331,7 @@ 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 diff --git a/dash-spv-ffi/tests/test_wallet_manager.rs b/dash-spv-ffi/tests/test_wallet_manager.rs index d43da9506..26b71e249 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -11,7 +11,8 @@ 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() { @@ -20,6 +21,12 @@ mod tests { let config = dash_spv_ffi_config_testnet(); assert!(!config.is_null()); + let temp_dir = TempDir::new().unwrap(); + dash_spv_ffi_config_set_data_dir( + config, + CString::new(temp_dir.path().to_str().unwrap()).unwrap().as_ptr(), + ); + // Create a client let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); @@ -51,6 +58,12 @@ mod tests { let config = dash_spv_ffi_config_testnet(); assert!(!config.is_null()); + let temp_dir = TempDir::new().unwrap(); + dash_spv_ffi_config_set_data_dir( + config, + CString::new(temp_dir.path().to_str().unwrap()).unwrap().as_ptr(), + ); + let client = dash_spv_ffi_client_new(config); assert!(!client.is_null()); diff --git a/dash-spv/benches/storage.rs b/dash-spv/benches/storage.rs index 5677e6ddc..44452c303 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, + ClientConfig, Hash, }; use dashcore::{block::Version, BlockHash, CompactTarget, Header}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -34,7 +34,9 @@ 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 = + ClientConfig::testnet().with_storage_path(TempDir::new().unwrap().path()); + DiskStorageManager::new(&config).await.unwrap() }, |a| async { let mut storage = a.await; @@ -47,10 +49,10 @@ fn bench_disk_storage(c: &mut Criterion) { ) }); - let temp_dir = TempDir::new().unwrap(); + let config = ClientConfig::testnet().with_storage_path(TempDir::new().unwrap().path()); 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..ece8f0c5e 100644 --- a/dash-spv/examples/filter_sync.rs +++ b/dash-spv/examples/filter_sync.rs @@ -22,13 +22,15 @@ async fn main() -> Result<(), Box> { )?; // Create configuration with filter support - let config = ClientConfig::mainnet().without_masternodes(); // Skip masternode sync for this example + let config = ClientConfig::mainnet() + .with_storage_path("./.tmp/filter-sync-example-storage") + .without_masternodes(); // Skip masternode sync for this example // 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))); diff --git a/dash-spv/examples/simple_sync.rs b/dash-spv/examples/simple_sync.rs index 373e30cc7..004545545 100644 --- a/dash-spv/examples/simple_sync.rs +++ b/dash-spv/examples/simple_sync.rs @@ -17,6 +17,7 @@ async fn main() -> Result<(), Box> { // Create a simple configuration let config = ClientConfig::mainnet() + .with_storage_path("./.tmp/simple-sync-example-storage") .without_filters() // Skip filter sync for this example .without_masternodes(); // Skip masternode sync for this example @@ -24,7 +25,7 @@ async fn main() -> Result<(), Box> { 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))); diff --git a/dash-spv/examples/spv_with_wallet.rs b/dash-spv/examples/spv_with_wallet.rs index f6d4a42da..12f4a7e96 100644 --- a/dash-spv/examples/spv_with_wallet.rs +++ b/dash-spv/examples/spv_with_wallet.rs @@ -17,16 +17,15 @@ 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 = ClientConfig::testnet() + .with_storage_path("./.tmp/spv-with-wallet-example-storage") + .with_validation_mode(dash_spv::ValidationMode::Full); // 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))); diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index 0505b93e1..0815990a5 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -34,8 +34,8 @@ pub struct ClientConfig { /// If no peers are configured, no outbound connections will be made. pub restrict_to_configured_peers: bool, - /// Optional path for persistent storage. - pub storage_path: Option, + /// Path for persistent storage. Defaults to ./dash-spv-storage + pub storage_path: PathBuf, /// Validation mode. pub validation_mode: ValidationMode, @@ -80,7 +80,7 @@ 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, @@ -136,8 +136,8 @@ impl ClientConfig { } /// Set storage path. - pub fn with_storage_path(mut self, path: PathBuf) -> Self { - self.storage_path = Some(path); + pub fn with_storage_path(mut self, path: impl Into) -> Self { + self.storage_path = path.into(); self } @@ -206,6 +206,13 @@ impl ClientConfig { ); } + std::fs::create_dir_all(&self.storage_path).map_err(|e| { + format!( + "A valid storage path must be provided to the ClientConfig {:?}: {e}", + self.storage_path + ) + })?; + Ok(()) } diff --git a/dash-spv/src/client/config_test.rs b/dash-spv/src/client/config_test.rs index 6c564799e..cbf234071 100644 --- a/dash-spv/src/client/config_test.rs +++ b/dash-spv/src/client/config_test.rs @@ -55,7 +55,7 @@ mod tests { .with_mempool_persistence(true) .with_start_height(100000); - assert_eq!(config.storage_path, Some(path)); + assert_eq!(config.storage_path, path); assert_eq!(config.validation_mode, ValidationMode::Basic); // Mempool settings @@ -88,12 +88,6 @@ mod tests { 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 { diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index bd6be889d..5532fa67a 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -64,12 +64,14 @@ mod message_handler_test; #[cfg(test)] mod tests { use super::{ClientConfig, DashSpvClient}; + use crate::client::config::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,13 +82,11 @@ 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 = ClientConfig::mainnet() + .without_filters() + .without_masternodes() + .with_mempool_tracking(MempoolStrategy::FetchAll) + .with_storage_path(TempDir::new().unwrap().path()); let network_manager = MockNetworkManager::new(); let storage = @@ -108,17 +108,14 @@ 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 = ClientConfig::testnet() + .without_filters() + .without_masternodes() + .with_mempool_tracking(MempoolStrategy::FetchAll) + .with_storage_path(TempDir::new().unwrap().path()); let network_manager = MockNetworkManager::new(); - let storage = - DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); + 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); diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index 472784574..a40357b94 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -26,11 +26,11 @@ //! async fn main() -> Result<(), Box> { //! // Create configuration for mainnet //! let config = ClientConfig::mainnet() -//! .with_storage_path("/path/to/data".into()); +//! .with_storage_path("./.tmp/example-storage"); //! //! // Create the required components //! let network = PeerNetworkManager::new(&config).await?; -//! let storage = DiskStorageManager::new("./.tmp/example-storage").await?; +//! let storage = DiskStorageManager::new(&config).await?; //! let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); //! //! // Create and start the client diff --git a/dash-spv/src/network/manager.rs b/dash-spv/src/network/manager.rs index 9f41d6566..c4a7da009 100644 --- a/dash-spv/src/network/manager.rs +++ b/dash-spv/src/network/manager.rs @@ -81,7 +81,7 @@ 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 data_dir = config.storage_path.clone(); let peer_store = PeerStore::new(config.network, data_dir.clone()); let reputation_manager = Arc::new(PeerReputationManager::new()); diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 2baaa6d54..a4ecce9bf 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"); @@ -144,7 +144,7 @@ impl DiskStorageManager { use tempfile::TempDir; let temp_dir = TempDir::new()?; - Self::new(temp_dir.path()).await + Self::new(&ClientConfig::testnet().with_storage_path(temp_dir.path())).await } /// Start the background worker saving data every 5 seconds @@ -396,14 +396,14 @@ impl masternode::MasternodeStateStorage for DiskStorageManager { mod tests { 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 config = ClientConfig::testnet().with_storage_path(temp_dir.path()); + let mut storage = DiskStorageManager::new(&config).await.expect("Unable to create storage"); let headers = BlockHeader::dummy_batch(0..60_000); @@ -433,8 +433,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 +444,14 @@ 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 = ClientConfig::testnet().with_storage_path(temp_dir.path()); + 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 +460,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 +502,10 @@ mod tests { #[tokio::test] async fn test_reverse_index_disk_storage() { let temp_dir = tempfile::tempdir().unwrap(); + let config = ClientConfig::regtest().with_storage_path(temp_dir.path()); { - 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 +524,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 +565,14 @@ mod tests { lock_file.set_extension("lock"); lock_file }; + let config = ClientConfig::regtest().with_storage_path(path); - 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 +580,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/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index f797520c3..b374e330d 100644 --- a/dash-spv/tests/block_download_test.rs +++ b/dash-spv/tests/block_download_test.rs @@ -16,6 +16,7 @@ fn create_test_config() -> ClientConfig { ClientConfig::testnet() .without_masternodes() .with_validation_mode(dash_spv::types::ValidationMode::None) + .with_storage_path(TempDir::new().unwrap().path()) } #[tokio::test] @@ -138,9 +139,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..c768d0a0d 100644 --- a/dash-spv/tests/chainlock_simple_test.rs +++ b/dash-spv/tests/chainlock_simple_test.rs @@ -27,7 +27,6 @@ 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; @@ -37,7 +36,7 @@ async fn test_chainlock_validation_flow() { enable_filters: false, enable_masternodes, validation_mode: ValidationMode::Basic, - storage_path: Some(storage_path), + storage_path: temp_dir.path().to_path_buf(), peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config ..Default::default() }; @@ -46,8 +45,7 @@ async fn test_chainlock_validation_flow() { 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))); @@ -78,7 +76,6 @@ 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 { @@ -86,7 +83,7 @@ async fn test_chainlock_manager_initialization() { enable_filters: false, enable_masternodes: false, validation_mode: ValidationMode::Basic, - storage_path: Some(storage_path), + storage_path: temp_dir.path().to_path_buf(), peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config ..Default::default() }; @@ -95,8 +92,7 @@ async fn test_chainlock_manager_initialization() { 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))); diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index f1fe6a85c..00508bbca 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -109,14 +109,12 @@ impl NetworkManager for MockNetworkManager { #[tokio::test] async fn test_filter_sync_at_tip_edge_case() { - let config = ClientConfig::new(Network::Dash); + let config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + 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 +151,12 @@ 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 = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + 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..dcf5da65e 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -175,9 +175,9 @@ 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 = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); let config = ClientConfig::new(Network::Dash); @@ -339,9 +339,9 @@ 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 = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + + let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); let config = ClientConfig::new(Network::Dash); @@ -515,12 +515,11 @@ 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 = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + + 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 +612,11 @@ 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 = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + + 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/header_sync_test.rs b/dash-spv/tests/header_sync_test.rs index 8714e4b26..e6561bf66 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -22,17 +22,16 @@ 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 = ClientConfig::new(Network::Dash) + .with_validation_mode(ValidationMode::Basic) + .with_storage_path(TempDir::new().expect("Failed to create tmp dir").path()); // 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))); @@ -88,8 +87,8 @@ 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 = ClientConfig::regtest().with_storage_path(temp_dir.path()); + 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(); diff --git a/dash-spv/tests/peer_test.rs b/dash-spv/tests/peer_test.rs index 82521ae4e..5a6029d00 100644 --- a/dash-spv/tests/peer_test.rs +++ b/dash-spv/tests/peer_test.rs @@ -15,9 +15,11 @@ 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 { +fn create_test_config(network: Network) -> ClientConfig { let mut config = ClientConfig::new(network); - config.storage_path = data_dir.map(|d| d.path().to_path_buf()); + + config.storage_path = TempDir::new().unwrap().path().to_path_buf(); + config.validation_mode = ValidationMode::Basic; config.enable_filters = false; config.enable_masternodes = false; @@ -31,15 +33,13 @@ 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))); @@ -75,24 +75,23 @@ 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 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,14 +104,11 @@ 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))); @@ -141,9 +137,7 @@ 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()]; @@ -152,7 +146,7 @@ async fn test_peer_disconnection() { 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))); @@ -177,8 +171,7 @@ 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()]; @@ -188,9 +181,7 @@ async fn test_max_peer_limit() { // 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))); diff --git a/dash-spv/tests/wallet_integration_test.rs b/dash-spv/tests/wallet_integration_test.rs index 8dd8d5c1b..768e9cfd7 100644 --- a/dash-spv/tests/wallet_integration_test.rs +++ b/dash-spv/tests/wallet_integration_test.rs @@ -15,16 +15,16 @@ 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 = ClientConfig::testnet() + .without_filters() + .with_storage_path(TempDir::new().unwrap().path()) + .without_masternodes(); // 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))); From c60ae8c091f64426766d846d547e014fdf5464c5 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 15:23:17 +0000 Subject: [PATCH 02/12] main.rs udpated --- dash-spv/src/main.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 5566c5116..967974536 100644 --- a/dash-spv/src/main.rs +++ b/dash-spv/src/main.rs @@ -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); From 7fe7a07a85975ced0b8da1384eb30f9970ce479f Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 19 Jan 2026 04:12:13 -0800 Subject: [PATCH 03/12] refactor: unify validation with a `Validator` trait (#355) --- dash-spv/src/client/chainlock.rs | 5 +- dash-spv/src/sync/headers/manager.rs | 4 +- dash-spv/src/sync/headers/mod.rs | 2 - .../validation.rs => validation/header.rs} | 102 +++++++++++------- dash-spv/src/validation/instantlock.rs | 88 +++++++-------- dash-spv/src/validation/mod.rs | 8 ++ 6 files changed, 115 insertions(+), 94 deletions(-) rename dash-spv/src/{sync/headers/validation.rs => validation/header.rs} (59%) 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 HeaderSyncManager { } if self.config.validation_mode != ValidationMode::None { - validate_headers(&cached_headers).map_err(|e| { + BlockHeaderValidator::new().validate(&cached_headers).map_err(|e| { let error = format!("Header validation failed: {}", e); tracing::error!(error); SyncError::Validation(error) 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/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<()>; +} From 27d4a2697b6c0d844178ff263256f4669c22364b Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 16:16:35 +0000 Subject: [PATCH 04/12] ClientConfig renamed to Config --- dash-spv-ffi/src/config.rs | 56 +++++++++---------- dash-spv/benches/storage.rs | 7 +-- dash-spv/examples/filter_sync.rs | 4 +- dash-spv/examples/simple_sync.rs | 4 +- dash-spv/examples/spv_with_wallet.rs | 4 +- dash-spv/src/client/config.rs | 7 +-- dash-spv/src/client/config_test.rs | 20 +++---- dash-spv/src/client/core.rs | 6 +- dash-spv/src/client/lifecycle.rs | 4 +- dash-spv/src/client/message_handler.rs | 6 +- dash-spv/src/client/message_handler_test.rs | 6 +- dash-spv/src/client/mod.rs | 8 +-- dash-spv/src/client/status_display.rs | 8 +-- dash-spv/src/lib.rs | 2 +- dash-spv/src/main.rs | 6 +- dash-spv/src/network/manager.rs | 4 +- dash-spv/src/storage/mod.rs | 14 ++--- dash-spv/src/sync/filters/manager.rs | 6 +- dash-spv/src/sync/headers/manager.rs | 6 +- dash-spv/src/sync/manager.rs | 6 +- dash-spv/src/sync/masternodes/manager.rs | 6 +- dash-spv/src/sync/transitions.rs | 6 +- dash-spv/tests/block_download_test.rs | 7 ++- dash-spv/tests/chainlock_simple_test.rs | 6 +- dash-spv/tests/edge_case_filter_sync_test.rs | 6 +- .../tests/filter_header_verification_test.rs | 14 ++--- dash-spv/tests/handshake_test.rs | 4 +- dash-spv/tests/header_sync_test.rs | 8 +-- dash-spv/tests/peer_test.rs | 6 +- .../tests/smart_fetch_integration_test.rs | 4 +- dash-spv/tests/wallet_integration_test.rs | 4 +- 31 files changed, 127 insertions(+), 128 deletions(-) diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index ffb8d8477..ccc64126e 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::{Config, ValidationMode}; use key_wallet_ffi::FFINetwork; use std::ffi::CStr; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; @@ -32,7 +32,7 @@ pub struct FFIClientConfig { #[no_mangle] pub extern "C" fn dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClientConfig { - let config = ClientConfig::new(network.into()); + let config = Config::new(network.into()); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -42,7 +42,7 @@ pub extern "C" fn dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClient #[no_mangle] pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { - let config = ClientConfig::mainnet(); + let config = Config::mainnet(); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -52,7 +52,7 @@ pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { #[no_mangle] pub extern "C" fn dash_spv_ffi_config_testnet() -> *mut FFIClientConfig { - let config = ClientConfig::testnet(); + let config = Config::testnet(); let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; Box::into_raw(Box::new(FFIClientConfig { inner, @@ -74,7 +74,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_data_dir( null_check!(config); null_check!(path); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; match CStr::from_ptr(path).to_str() { Ok(path_str) => { config.storage_path = path_str.into(); @@ -99,7 +99,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_validation_mode( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.validation_mode = mode.into(); FFIErrorCode::Success as i32 } @@ -116,7 +116,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_peers( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.max_peers = max_peers; FFIErrorCode::Success as i32 } @@ -146,7 +146,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( null_check!(config); null_check!(addr); - let cfg = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let cfg = unsafe { &mut *((*config).inner as *mut Config) }; let default_port = match cfg.network { dashcore::Network::Dash => 9999, dashcore::Network::Testnet => 19999, @@ -218,7 +218,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_user_agent( 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 *((*config).inner as *mut ClientConfig) }; + let cfg = unsafe { &mut *((*config).inner as *mut Config) }; cfg.user_agent = Some(agent_str.to_string()); FFIErrorCode::Success as i32 } @@ -241,7 +241,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_relay_transactions( ) -> i32 { null_check!(config); - let _config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let _config = unsafe { &mut *((*config).inner as *mut Config) }; // relay_transactions not directly settable in current ClientConfig FFIErrorCode::Success as i32 } @@ -258,7 +258,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_filter_load( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.enable_filters = load_filters; FFIErrorCode::Success as i32 } @@ -274,7 +274,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_restrict_to_configured_peers( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.restrict_to_configured_peers = restrict_peers; FFIErrorCode::Success as i32 } @@ -291,7 +291,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_masternode_sync_enabled( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.enable_masternodes = enable; FFIErrorCode::Success as i32 } @@ -309,7 +309,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_network( return FFINetwork::Dash; } - let config = unsafe { &*((*config).inner as *const ClientConfig) }; + let config = unsafe { &*((*config).inner as *const Config) }; config.network.into() } @@ -330,7 +330,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir( }; } - let config = unsafe { &*((*config).inner as *const ClientConfig) }; + let config = unsafe { &*((*config).inner as *const Config) }; FFIString::new(&config.storage_path.to_string_lossy()) } @@ -347,18 +347,18 @@ pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIClientConfi let cfg = Box::from_raw(config); // Free inner ClientConfig if present if !cfg.inner.is_null() { - let _ = Box::from_raw(cfg.inner as *mut ClientConfig); + let _ = Box::from_raw(cfg.inner as *mut Config); } } } impl FFIClientConfig { - pub fn get_inner(&self) -> &ClientConfig { - unsafe { &*(self.inner as *const ClientConfig) } + pub fn get_inner(&self) -> &Config { + unsafe { &*(self.inner as *const Config) } } - pub fn clone_inner(&self) -> ClientConfig { - unsafe { (*(self.inner as *const ClientConfig)).clone() } + pub fn clone_inner(&self) -> Config { + unsafe { (*(self.inner as *const Config)).clone() } } } @@ -391,7 +391,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_tracking( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.enable_mempool_tracking = enable; FFIErrorCode::Success as i32 } @@ -408,7 +408,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_mempool_strategy( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.mempool_strategy = strategy.into(); FFIErrorCode::Success as i32 } @@ -425,7 +425,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_max_mempool_transactions( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.max_mempool_transactions = max_transactions as usize; FFIErrorCode::Success as i32 } @@ -442,7 +442,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_fetch_mempool_transactions( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.fetch_mempool_transactions = fetch; FFIErrorCode::Success as i32 } @@ -459,7 +459,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_persist_mempool( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.persist_mempool = persist; FFIErrorCode::Success as i32 } @@ -477,7 +477,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_tracking( return false; } - let config = unsafe { &*((*config).inner as *const ClientConfig) }; + let config = unsafe { &*((*config).inner as *const Config) }; config.enable_mempool_tracking } @@ -494,7 +494,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( return FFIMempoolStrategy::FetchAll; } - let config = unsafe { &*((*config).inner as *const ClientConfig) }; + let config = unsafe { &*((*config).inner as *const Config) }; config.mempool_strategy.into() } @@ -512,7 +512,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_start_from_height( ) -> i32 { null_check!(config); - let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + let config = unsafe { &mut *((*config).inner as *mut Config) }; config.start_from_height = Some(height); FFIErrorCode::Success as i32 } diff --git a/dash-spv/benches/storage.rs b/dash-spv/benches/storage.rs index 44452c303..367b70775 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}, - ClientConfig, Hash, + Config, Hash, }; use dashcore::{block::Version, BlockHash, CompactTarget, Header}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -34,8 +34,7 @@ fn bench_disk_storage(c: &mut Criterion) { c.bench_function("storage/disk/store", |b| { b.to_async(&rt).iter_batched( || async { - let config = - ClientConfig::testnet().with_storage_path(TempDir::new().unwrap().path()); + let config = Config::testnet().with_storage_path(TempDir::new().unwrap().path()); DiskStorageManager::new(&config).await.unwrap() }, |a| async { @@ -49,7 +48,7 @@ fn bench_disk_storage(c: &mut Criterion) { ) }); - let config = ClientConfig::testnet().with_storage_path(TempDir::new().unwrap().path()); + let config = Config::testnet().with_storage_path(TempDir::new().unwrap().path()); let mut storage = rt.block_on(async { let mut storage = DiskStorageManager::new(&config).await.unwrap(); diff --git a/dash-spv/examples/filter_sync.rs b/dash-spv/examples/filter_sync.rs index ece8f0c5e..a2920ae00 100644 --- a/dash-spv/examples/filter_sync.rs +++ b/dash-spv/examples/filter_sync.rs @@ -2,7 +2,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{init_console_logging, ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::{init_console_logging, Config, DashSpvClient, LevelFilter}; use dashcore::Address; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -22,7 +22,7 @@ async fn main() -> Result<(), Box> { )?; // Create configuration with filter support - let config = ClientConfig::mainnet() + let config = Config::mainnet() .with_storage_path("./.tmp/filter-sync-example-storage") .without_masternodes(); // Skip masternode sync for this example diff --git a/dash-spv/examples/simple_sync.rs b/dash-spv/examples/simple_sync.rs index 004545545..95ca38e16 100644 --- a/dash-spv/examples/simple_sync.rs +++ b/dash-spv/examples/simple_sync.rs @@ -2,7 +2,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{init_console_logging, ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::{init_console_logging, Config, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box> { let _logging_guard = init_console_logging(LevelFilter::INFO)?; // Create a simple configuration - let config = ClientConfig::mainnet() + let config = Config::mainnet() .with_storage_path("./.tmp/simple-sync-example-storage") .without_filters() // Skip filter sync for this example .without_masternodes(); // Skip masternode sync for this example diff --git a/dash-spv/examples/spv_with_wallet.rs b/dash-spv/examples/spv_with_wallet.rs index 12f4a7e96..43b668a11 100644 --- a/dash-spv/examples/spv_with_wallet.rs +++ b/dash-spv/examples/spv_with_wallet.rs @@ -4,7 +4,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{ClientConfig, DashSpvClient, LevelFilter}; +use dash_spv::{Config, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use std::sync::Arc; @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box> { let _logging_guard = dash_spv::init_console_logging(LevelFilter::INFO)?; // Create SPV client configuration - let config = ClientConfig::testnet() + let config = Config::testnet() .with_storage_path("./.tmp/spv-with-wallet-example-storage") .with_validation_mode(dash_spv::ValidationMode::Full); diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index 0815990a5..f01bb3dd0 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -19,8 +19,7 @@ pub enum MempoolStrategy { /// Configuration for the Dash SPV client. #[derive(Debug, Clone)] -#[repr(C)] -pub struct ClientConfig { +pub struct Config { /// Network to connect to. pub network: Network, @@ -74,7 +73,7 @@ pub struct ClientConfig { pub start_from_height: Option, } -impl Default for ClientConfig { +impl Default for Config { fn default() -> Self { Self { network: Network::Dash, @@ -97,7 +96,7 @@ impl Default for ClientConfig { } } -impl ClientConfig { +impl Config { /// Create a new configuration for the given network. pub fn new(network: Network) -> Self { Self { diff --git a/dash-spv/src/client/config_test.rs b/dash-spv/src/client/config_test.rs index cbf234071..e2daf8660 100644 --- a/dash-spv/src/client/config_test.rs +++ b/dash-spv/src/client/config_test.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use crate::client::config::{ClientConfig, MempoolStrategy}; + use crate::client::config::{Config, MempoolStrategy}; use crate::types::ValidationMode; use dashcore::Network; use std::net::SocketAddr; @@ -10,7 +10,7 @@ mod tests { #[test] fn test_default_config() { - let config = ClientConfig::default(); + let config = Config::default(); assert_eq!(config.network, Network::Dash); assert!(config.peers.is_empty()); @@ -29,15 +29,15 @@ mod tests { #[test] fn test_network_specific_configs() { - let mainnet = ClientConfig::mainnet(); + let mainnet = Config::mainnet(); assert_eq!(mainnet.network, Network::Dash); assert!(mainnet.peers.is_empty()); // Should use DNS discovery - let testnet = ClientConfig::testnet(); + let testnet = Config::testnet(); assert_eq!(testnet.network, Network::Testnet); assert!(testnet.peers.is_empty()); // Should use DNS discovery - let regtest = ClientConfig::regtest(); + let regtest = Config::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"); @@ -47,7 +47,7 @@ mod tests { fn test_builder_pattern() { let path = PathBuf::from("/test/storage"); - let config = ClientConfig::mainnet() + let config = Config::mainnet() .with_storage_path(path.clone()) .with_validation_mode(ValidationMode::Basic) .with_mempool_tracking(MempoolStrategy::BloomFilter) @@ -68,7 +68,7 @@ mod tests { #[test] fn test_add_peer() { - let mut config = ClientConfig::default(); + let mut config = Config::default(); let addr1: SocketAddr = "1.2.3.4:9999".parse().unwrap(); let addr2: SocketAddr = "5.6.7.8:9999".parse().unwrap(); @@ -82,7 +82,7 @@ mod tests { #[test] fn test_disable_features() { - let config = ClientConfig::default().without_filters().without_masternodes(); + let config = Config::default().without_filters().without_masternodes(); assert!(!config.enable_filters); assert!(!config.enable_masternodes); @@ -90,7 +90,7 @@ mod tests { #[test] fn test_validation_invalid_max_peers() { - let config = ClientConfig { + let config = Config { max_peers: 0, ..Default::default() }; @@ -102,7 +102,7 @@ mod tests { #[test] fn test_validation_invalid_mempool_config() { - let config = ClientConfig { + let config = Config { enable_mempool_tracking: true, max_mempool_transactions: 0, ..Default::default() diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index b41a7aeae..6ad4e9c01 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -24,7 +24,7 @@ use crate::sync::SyncManager; use crate::types::{ChainState, DetailedSyncProgress, MempoolState, SpvEvent, SpvStats}; use key_wallet_manager::wallet_interface::WalletInterface; -use super::{ClientConfig, StatusDisplay}; +use super::{Config, StatusDisplay}; /// Main Dash SPV client with generic trait-based architecture. /// @@ -98,7 +98,7 @@ use super::{ClientConfig, StatusDisplay}; /// /// The generic design is an intentional, beneficial architectural choice for a library. pub struct DashSpvClient { - pub(super) config: ClientConfig, + pub(super) config: Config, pub(super) state: Arc>, pub(super) stats: Arc>, pub(super) network: N, @@ -267,7 +267,7 @@ impl DashSpvClient Result<()> { + pub async fn update_config(&mut self, new_config: Config) -> Result<()> { // Validate new configuration new_config.validate().map_err(SpvError::Config)?; diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index ce061bc8d..1bc8df180 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -23,12 +23,12 @@ use dashcore::network::constants::NetworkExt; use dashcore_hashes::Hash; use key_wallet_manager::wallet_interface::WalletInterface; -use super::{ClientConfig, DashSpvClient}; +use super::{Config, DashSpvClient}; impl DashSpvClient { /// Create a new SPV client with the given configuration, network, storage, and wallet. pub async fn new( - config: ClientConfig, + config: Config, network: N, storage: S, wallet: Arc>, diff --git a/dash-spv/src/client/message_handler.rs b/dash-spv/src/client/message_handler.rs index 75ee4dfb0..61f1fc1c2 100644 --- a/dash-spv/src/client/message_handler.rs +++ b/dash-spv/src/client/message_handler.rs @@ -1,6 +1,6 @@ //! Network message handling for the Dash SPV client. -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; @@ -17,7 +17,7 @@ pub struct MessageHandler<'a, S: StorageManager, N: NetworkManager, W: WalletInt sync_manager: &'a mut SyncManager, storage: &'a mut S, network: &'a mut N, - config: &'a ClientConfig, + config: &'a Config, mempool_filter: &'a Option>, mempool_state: &'a Arc>, event_tx: &'a tokio::sync::mpsc::UnboundedSender, @@ -30,7 +30,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle sync_manager: &'a mut SyncManager, storage: &'a mut S, network: &'a mut N, - config: &'a ClientConfig, + config: &'a Config, mempool_filter: &'a Option>, mempool_state: &'a Arc>, event_tx: &'a tokio::sync::mpsc::UnboundedSender, diff --git a/dash-spv/src/client/message_handler_test.rs b/dash-spv/src/client/message_handler_test.rs index 63ef8b434..39a73ca47 100644 --- a/dash-spv/src/client/message_handler_test.rs +++ b/dash-spv/src/client/message_handler_test.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use crate::client::{ClientConfig, MessageHandler}; + use crate::client::{Config, MessageHandler}; use crate::storage::DiskStorageManager; use crate::sync::SyncManager; use crate::test_utils::MockNetworkManager; @@ -22,14 +22,14 @@ mod tests { MockNetworkManager, DiskStorageManager, SyncManager, - ClientConfig, + Config, Arc>, mpsc::UnboundedSender, ) { let network = MockNetworkManager::new(); let storage = DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); - let config = ClientConfig::default(); + let config = Config::default(); let stats = Arc::new(RwLock::new(SpvStats::default())); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); let (event_tx, _event_rx) = mpsc::unbounded_channel(); diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 5532fa67a..09b1f3ac2 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -48,7 +48,7 @@ mod sync_coordinator; mod transactions; // Re-export public types from extracted modules -pub use config::ClientConfig; +pub use config::Config; pub use message_handler::MessageHandler; pub use status_display::StatusDisplay; @@ -63,7 +63,7 @@ mod message_handler_test; #[cfg(test)] mod tests { - use super::{ClientConfig, DashSpvClient}; + use super::{Config, DashSpvClient}; use crate::client::config::MempoolStrategy; use crate::storage::DiskStorageManager; use crate::{test_utils::MockNetworkManager, types::UnconfirmedTransaction}; @@ -82,7 +82,7 @@ mod tests { #[tokio::test] async fn client_exposes_shared_wallet_manager() { - let config = ClientConfig::mainnet() + let config = Config::mainnet() .without_filters() .without_masternodes() .with_mempool_tracking(MempoolStrategy::FetchAll) @@ -108,7 +108,7 @@ 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::testnet() + let config = Config::testnet() .without_filters() .without_masternodes() .with_mempool_tracking(MempoolStrategy::FetchAll) diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index cc3e9aee5..516e08b26 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use tokio::sync::{Mutex, RwLock}; -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::Result; use crate::storage::StorageManager; #[cfg(feature = "terminal-ui")] @@ -20,7 +20,7 @@ pub struct StatusDisplay<'a, S: StorageManager, W: WalletInterface> { #[cfg(feature = "terminal-ui")] terminal_ui: &'a Option>, #[allow(dead_code)] - config: &'a ClientConfig, + config: &'a Config, } impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { @@ -32,7 +32,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { storage: Arc>, wallet: Option<&'a Arc>>, terminal_ui: &'a Option>, - config: &'a ClientConfig, + config: &'a Config, ) -> Self { Self { state, @@ -52,7 +52,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { storage: Arc>, wallet: Option<&'a Arc>>, _terminal_ui: &'a Option<()>, - config: &'a ClientConfig, + config: &'a Config, ) -> Self { Self { state, diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index a40357b94..1bf38db1e 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -73,7 +73,7 @@ pub mod types; pub mod validation; // Re-export main types for convenience -pub use client::{ClientConfig, DashSpvClient}; +pub use client::{Config, DashSpvClient}; 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 967974536..7f73d8157 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::{Config, DashSpvClient, LevelFilter, Network}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; use tokio_util::sync::CancellationToken; @@ -249,7 +249,7 @@ async fn run() -> Result<(), Box> { tracing::info!("Validation mode: {:?}", validation_mode); // Create configuration - let mut config = ClientConfig::new(network) + let mut config = Config::new(network) .with_storage_path(data_dir.clone()) .with_validation_mode(validation_mode); @@ -342,7 +342,7 @@ async fn run() -> Result<(), Box> { } async fn run_client( - config: ClientConfig, + config: Config, network_manager: dash_spv::network::manager::PeerNetworkManager, storage_manager: S, wallet: Arc>>, diff --git a/dash-spv/src/network/manager.rs b/dash-spv/src/network/manager.rs index c4a7da009..597724a1f 100644 --- a/dash-spv/src/network/manager.rs +++ b/dash-spv/src/network/manager.rs @@ -11,7 +11,7 @@ use tokio::task::JoinSet; use tokio::time; use crate::client::config::MempoolStrategy; -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{NetworkError, NetworkResult, SpvError as Error}; use crate::network::addrv2::AddrV2Handler; use crate::network::constants::*; @@ -77,7 +77,7 @@ pub struct PeerNetworkManager { impl PeerNetworkManager { /// Create a new peer network manager - pub async fn new(config: &ClientConfig) -> Result { + pub async fn new(config: &Config) -> Result { let (message_tx, message_rx) = mpsc::channel(1000); let discovery = DnsDiscovery::new().await?; diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index a4ecce9bf..0c0e1ccee 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, ClientConfig}; +use crate::{ChainState, Config}; pub use crate::storage::blocks::BlockHeaderStorage; pub use crate::storage::chainstate::ChainStateStorage; @@ -94,7 +94,7 @@ pub struct DiskStorageManager { } impl DiskStorageManager { - pub async fn new(config: &ClientConfig) -> StorageResult { + pub async fn new(config: &Config) -> StorageResult { use std::fs; let storage_path = config.storage_path.clone(); @@ -144,7 +144,7 @@ impl DiskStorageManager { use tempfile::TempDir; let temp_dir = TempDir::new()?; - Self::new(&ClientConfig::testnet().with_storage_path(temp_dir.path())).await + Self::new(&Config::testnet().with_storage_path(temp_dir.path())).await } /// Start the background worker saving data every 5 seconds @@ -402,7 +402,7 @@ mod tests { async fn test_store_load_headers() -> Result<(), Box> { // Create a temporary directory for the test let temp_dir = TempDir::new()?; - let config = ClientConfig::testnet().with_storage_path(temp_dir.path()); + let config = Config::testnet().with_storage_path(temp_dir.path()); let mut storage = DiskStorageManager::new(&config).await.expect("Unable to create storage"); let headers = BlockHeader::dummy_batch(0..60_000); @@ -445,7 +445,7 @@ mod tests { #[tokio::test] async fn test_checkpoint_storage_indexing() -> StorageResult<()> { let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let config = ClientConfig::testnet().with_storage_path(temp_dir.path()); + let config = Config::testnet().with_storage_path(temp_dir.path()); let mut storage = DiskStorageManager::new(&config).await?; // Create test headers starting from checkpoint height @@ -502,7 +502,7 @@ mod tests { #[tokio::test] async fn test_reverse_index_disk_storage() { let temp_dir = tempfile::tempdir().unwrap(); - let config = ClientConfig::regtest().with_storage_path(temp_dir.path()); + let config = Config::regtest().with_storage_path(temp_dir.path()); { let mut storage = DiskStorageManager::new(&config).await.unwrap(); @@ -565,7 +565,7 @@ mod tests { lock_file.set_extension("lock"); lock_file }; - let config = ClientConfig::regtest().with_storage_path(path); + let config = Config::regtest().with_storage_path(path); let mut storage1 = DiskStorageManager::new(&config).await.unwrap(); assert!(lock_path.exists(), "Lock file should exist while storage is open"); diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index 1ae55501f..0bfc54851 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -3,7 +3,7 @@ //! This module contains the FilterSyncManager struct and high-level coordination logic //! that delegates to specialized sub-modules for headers, downloads, matching, etc. -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -41,7 +41,7 @@ use super::types::*; pub struct FilterSyncManager { pub(super) _phantom_s: std::marker::PhantomData, pub(super) _phantom_n: std::marker::PhantomData, - pub(super) _config: ClientConfig, + pub(super) _config: Config, /// Whether filter header sync is currently in progress pub(super) syncing_filter_headers: bool, /// Current height being synced for filter headers @@ -88,7 +88,7 @@ pub struct FilterSyncManager { impl FilterSyncManager { /// Verify that the received compact filter hashes to the expected filter header - pub fn new(config: &ClientConfig, received_filter_heights: SharedFilterHeights) -> Self { + pub fn new(config: &Config, received_filter_heights: SharedFilterHeights) -> Self { Self { _config: config.clone(), syncing_filter_headers: false, diff --git a/dash-spv/src/sync/headers/manager.rs b/dash-spv/src/sync/headers/manager.rs index 4498c16d9..3e0dd3d85 100644 --- a/dash-spv/src/sync/headers/manager.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -8,7 +8,7 @@ use dashcore_hashes::Hash; use crate::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, CheckpointManager}; use crate::chain::{ChainTip, ChainTipManager, ChainWork}; -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -45,7 +45,7 @@ impl Default for ReorgConfig { pub struct HeaderSyncManager { _phantom_s: std::marker::PhantomData, _phantom_n: std::marker::PhantomData, - config: ClientConfig, + config: Config, tip_manager: ChainTipManager, checkpoint_manager: CheckpointManager, reorg_config: ReorgConfig, @@ -59,7 +59,7 @@ pub struct HeaderSyncManager { impl HeaderSyncManager { /// Create a new header sync manager pub fn new( - config: &ClientConfig, + config: &Config, reorg_config: ReorgConfig, chain_state: Arc>, ) -> SyncResult { diff --git a/dash-spv/src/sync/manager.rs b/dash-spv/src/sync/manager.rs index 3b1d5d1fc..cdfbc51b4 100644 --- a/dash-spv/src/sync/manager.rs +++ b/dash-spv/src/sync/manager.rs @@ -2,7 +2,7 @@ use super::phases::{PhaseTransition, SyncPhase}; use super::transitions::TransitionManager; -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::SyncResult; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -76,7 +76,7 @@ pub struct SyncManager pub(super) masternode_sync: MasternodeSyncManager, /// Configuration - pub(super) config: ClientConfig, + pub(super) config: Config, /// Phase transition history pub(super) phase_history: Vec, @@ -103,7 +103,7 @@ pub struct SyncManager impl SyncManager { /// Create a new sequential sync manager pub fn new( - config: &ClientConfig, + config: &Config, received_filter_heights: SharedFilterHeights, wallet: Arc>, chain_state: Arc>, diff --git a/dash-spv/src/sync/masternodes/manager.rs b/dash-spv/src/sync/masternodes/manager.rs index 38b35918c..0cd506fc5 100644 --- a/dash-spv/src/sync/masternodes/manager.rs +++ b/dash-spv/src/sync/masternodes/manager.rs @@ -14,7 +14,7 @@ use dashcore::{ use std::collections::HashMap; use std::time::{Duration, Instant}; -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -23,7 +23,7 @@ use crate::storage::StorageManager; pub struct MasternodeSyncManager { _phantom_s: std::marker::PhantomData, _phantom_n: std::marker::PhantomData, - config: ClientConfig, + config: Config, engine: Option, // Simple caches matching dash-evo-tool pattern @@ -53,7 +53,7 @@ pub struct MasternodeSyncManager { impl MasternodeSyncManager { /// Create a new masternode sync manager. - pub fn new(config: &ClientConfig) -> Self { + pub fn new(config: &Config) -> Self { 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) { diff --git a/dash-spv/src/sync/transitions.rs b/dash-spv/src/sync/transitions.rs index e8ce58e93..8be43f322 100644 --- a/dash-spv/src/sync/transitions.rs +++ b/dash-spv/src/sync/transitions.rs @@ -1,6 +1,6 @@ //! Phase transition logic for sequential sync -use crate::client::ClientConfig; +use crate::client::Config; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -11,12 +11,12 @@ use std::time::Instant; /// Manages phase transitions and validation pub struct TransitionManager { - config: ClientConfig, + config: Config, } impl TransitionManager { /// Create a new transition manager - pub fn new(config: &ClientConfig) -> Self { + pub fn new(config: &Config) -> Self { Self { config: config.clone(), } diff --git a/dash-spv/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index b374e330d..8b03d560f 100644 --- a/dash-spv/tests/block_download_test.rs +++ b/dash-spv/tests/block_download_test.rs @@ -9,11 +9,12 @@ use tokio::sync::Mutex; use dashcore::block::Block; use dash_spv::{ - client::ClientConfig, storage::DiskStorageManager, sync::FilterSyncManager, types::FilterMatch, + client::Config, storage::DiskStorageManager, sync::FilterSyncManager, + types::FilterMatch, }; -fn create_test_config() -> ClientConfig { - ClientConfig::testnet() +fn create_test_config() -> Config { + Config::testnet() .without_masternodes() .with_validation_mode(dash_spv::types::ValidationMode::None) .with_storage_path(TempDir::new().unwrap().path()) diff --git a/dash-spv/tests/chainlock_simple_test.rs b/dash-spv/tests/chainlock_simple_test.rs index c768d0a0d..506af6b52 100644 --- a/dash-spv/tests/chainlock_simple_test.rs +++ b/dash-spv/tests/chainlock_simple_test.rs @@ -1,6 +1,6 @@ //! Simple integration test for ChainLock validation flow -use dash_spv::client::{ClientConfig, DashSpvClient}; +use dash_spv::client::{Config, DashSpvClient}; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; @@ -31,7 +31,7 @@ async fn test_chainlock_validation_flow() { // Create client config with masternodes enabled let network = Network::Dash; let enable_masternodes = true; - let config = ClientConfig { + let config = Config { network, enable_filters: false, enable_masternodes, @@ -78,7 +78,7 @@ async fn test_chainlock_manager_initialization() { let temp_dir = TempDir::new().unwrap(); // Create client config - let config = ClientConfig { + let config = Config { network: Network::Dash, enable_filters: false, enable_masternodes: false, diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index 00508bbca..bf3555802 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -6,7 +6,7 @@ use tempfile::TempDir; use tokio::sync::Mutex; use dash_spv::{ - client::ClientConfig, + client::Config, error::NetworkResult, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, @@ -109,7 +109,7 @@ impl NetworkManager for MockNetworkManager { #[tokio::test] async fn test_filter_sync_at_tip_edge_case() { - let config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync = FilterSyncManager::new(&config, received_heights); @@ -151,7 +151,7 @@ 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).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync = FilterSyncManager::new(&config, received_heights); diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index dcf5da65e..d897e063a 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -9,7 +9,7 @@ //! are calculated, stored, or verified across multiple batches. use dash_spv::{ - client::ClientConfig, + client::Config, error::{NetworkError, NetworkResult, SyncError}, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, @@ -175,12 +175,12 @@ async fn test_filter_header_verification_failure_reproduction() { println!("=== Testing Filter Header Chain Verification Failure ==="); // Create storage and sync manager - let config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); 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 config = Config::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -339,12 +339,12 @@ 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 config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); 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 config = Config::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -515,7 +515,7 @@ 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 config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); @@ -612,7 +612,7 @@ 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 config = ClientConfig::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create tmp storage"); let mut network = MockNetworkManager::new(); diff --git a/dash-spv/tests/handshake_test.rs b/dash-spv/tests/handshake_test.rs index 302cc3bf7..d55e7ddcf 100644 --- a/dash-spv/tests/handshake_test.rs +++ b/dash-spv/tests/handshake_test.rs @@ -5,7 +5,7 @@ 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::{Config, Network}; #[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 = Config::new(Network::Dash); 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 e6561bf66..32fe40239 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -1,7 +1,7 @@ //! Integration tests for header synchronization functionality. use dash_spv::{ - client::{ClientConfig, DashSpvClient}, + client::{Config, DashSpvClient}, network::PeerNetworkManager, storage::{BlockHeaderStorage, ChainStateStorage, DiskStorageManager}, sync::{HeaderSyncManager, ReorgConfig}, @@ -22,7 +22,7 @@ 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) + let config = Config::new(Network::Dash) .with_validation_mode(ValidationMode::Basic) .with_storage_path(TempDir::new().expect("Failed to create tmp dir").path()); @@ -87,7 +87,7 @@ 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 config = ClientConfig::regtest().with_storage_path(temp_dir.path()); + let config = Config::regtest().with_storage_path(temp_dir.path()); let mut storage = DiskStorageManager::new(&config).await.expect("Failed to create storage"); let headers = create_test_header_chain(header_count); @@ -100,7 +100,7 @@ async fn test_prepare_sync(sync_base_height: u32, header_count: usize) { storage.store_headers(&headers).await.expect("Failed to store headers"); // Create HeaderSyncManager and load from storage - let config = ClientConfig::new(Network::Dash); + let config = Config::new(Network::Dash); let chain_state_arc = Arc::new(RwLock::new(ChainState::new_for_network(Network::Dash))); let mut header_sync = HeaderSyncManager::::new( &config, diff --git a/dash-spv/tests/peer_test.rs b/dash-spv/tests/peer_test.rs index 5a6029d00..d4016dafa 100644 --- a/dash-spv/tests/peer_test.rs +++ b/dash-spv/tests/peer_test.rs @@ -7,7 +7,7 @@ use tempfile::TempDir; use tokio::sync::RwLock; use tokio::time; -use dash_spv::client::{ClientConfig, DashSpvClient}; +use dash_spv::client::{Config, DashSpvClient}; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; @@ -15,8 +15,8 @@ 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) -> ClientConfig { - let mut config = ClientConfig::new(network); +fn create_test_config(network: Network) -> Config { + let mut config = Config::new(network); config.storage_path = TempDir::new().unwrap().path().to_path_buf(); diff --git a/dash-spv/tests/smart_fetch_integration_test.rs b/dash-spv/tests/smart_fetch_integration_test.rs index cc3f279bb..ee8b5f4ab 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::client::Config; 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 = Config::new(Network::Testnet); // 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/wallet_integration_test.rs b/dash-spv/tests/wallet_integration_test.rs index 768e9cfd7..3516e1d05 100644 --- a/dash-spv/tests/wallet_integration_test.rs +++ b/dash-spv/tests/wallet_integration_test.rs @@ -8,14 +8,14 @@ use tokio::sync::RwLock; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{ClientConfig, DashSpvClient}; +use dash_spv::{Config, 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() + let config = Config::testnet() .without_filters() .with_storage_path(TempDir::new().unwrap().path()) .without_masternodes(); From 980769b22678df39d6ebfdd9f1e178b875db995b Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 16:23:41 +0000 Subject: [PATCH 05/12] added needed bon and getset deps --- dash-spv/Cargo.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index 6f03bf239..291fc32b8 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" } +bon = { version = "3.8.2" } + # BLS signatures blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "0c34a7a488a0bd1c9a9a2196e793b303ad35c900" } From 10792b0192dd38f5ef76bdc966c0f47a63604cd9 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 17:56:52 +0000 Subject: [PATCH 06/12] Builder impleemnted succesfully --- dash-spv/Cargo.toml | 2 +- dash-spv/src/client/config.rs | 319 ++++++++++++++++++----------- dash-spv/src/client/config_test.rs | 117 ----------- dash-spv/src/client/mod.rs | 3 - 4 files changed, 195 insertions(+), 246 deletions(-) delete mode 100644 dash-spv/src/client/config_test.rs diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index 291fc32b8..40ccfc4a2 100644 --- a/dash-spv/Cargo.toml +++ b/dash-spv/Cargo.toml @@ -18,7 +18,7 @@ key-wallet-manager = { path = "../key-wallet-manager" } # Quality-of-life dependencies: # fewer lines of code, fewer regrets getset = { version = "0.1.6" } -bon = { version = "3.8.2" } +derive_builder = { version = "0.20.2" } # BLS signatures blsful = { git = "https://github.com/dashpay/agora-blsful", rev = "0c34a7a488a0bd1c9a9a2196e793b303ad35c900" } diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index f01bb3dd0..c6a653932 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -1,9 +1,11 @@ //! Configuration management for the Dash SPV client. -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use dashcore::Network; +use derive_builder::Builder; +use getset::{CopyGetters, Getters}; // Serialization removed due to complex Address types use crate::types::ValidationMode; @@ -18,59 +20,78 @@ pub enum MempoolStrategy { } /// Configuration for the Dash SPV client. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Builder, Getters, CopyGetters)] +#[builder(setter(strip_option))] +#[builder(field(private))] +#[builder(default)] +#[builder(build_fn(validate = "Self::validate", error = "String"))] pub struct Config { /// Network to connect to. - pub network: Network, + #[getset(get_copy = "pub")] + network: Network, /// List of peer addresses to connect to. - pub peers: Vec, + #[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 = "pub")] + restrict_to_configured_peers: bool, /// Path for persistent storage. Defaults to ./dash-spv-storage - pub storage_path: PathBuf, + #[getset(get = "pub")] + 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 Config { @@ -85,7 +106,6 @@ impl Default for Config { enable_masternodes: true, max_peers: 8, user_agent: None, - // Mempool defaults enable_mempool_tracking: true, mempool_strategy: MempoolStrategy::FetchAll, max_mempool_transactions: 1000, @@ -96,143 +116,192 @@ impl Default for Config { } } -impl Config { - /// Create a new configuration for the given network. - pub fn new(network: Network) -> Self { - Self { - network, - peers: Self::default_peers_for_network(network), - restrict_to_configured_peers: false, - ..Self::default() - } +impl ConfigBuilder { + pub fn mainnet() -> ConfigBuilder { + let mut builder = Self::default(); + builder.network(Network::Dash); + builder } - /// Create a configuration for mainnet. - pub fn mainnet() -> Self { - Self::new(Network::Dash) + pub fn testnet() -> ConfigBuilder { + let mut builder = Self::default(); + builder.network(Network::Testnet); + builder } - /// Create a configuration for testnet. - pub fn testnet() -> Self { - Self::new(Network::Testnet) + pub fn devnet() -> ConfigBuilder { + let mut builder = Self::default(); + builder.network(Network::Devnet); + builder } - /// Create a configuration for regtest. - pub fn regtest() -> Self { - Self::new(Network::Regtest) - } + pub fn regtest() -> ConfigBuilder { + let peers = vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 19899)]; - /// Add a peer address. - pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { - self.peers.push(address); - self + let mut builder = Self::default(); + builder.network(Network::Regtest).peers(peers); + builder } - /// 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; - self - } + pub fn validate(&self) -> Result<(), String> { + match self.max_peers { + Some(max_peers) if max_peers == 0 => { + return Err("max_peers must be > 0".to_string()); + } + _ => {} + } - /// Set storage path. - pub fn with_storage_path(mut self, path: impl Into) -> Self { - self.storage_path = path.into(); - self - } + // Validación de mempool + match (self.enable_mempool_tracking, self.max_mempool_transactions) { + (Some(true), Some(0)) => { + return Err( + "max_mempool_transactions must be > 0 when mempool tracking is enabled" + .to_string(), + ); + } + _ => {} + } - /// Set validation mode. - pub fn with_validation_mode(mut self, mode: ValidationMode) -> Self { - self.validation_mode = mode; - self - } + match &self.storage_path { + Some(path) => { + std::fs::create_dir_all(path).map_err(|e| { + format!("A valid storage path must be provided: {:?}: {e}", path) + })?; + } + None => {} + } - /// Disable filters. - pub fn without_filters(mut self) -> Self { - self.enable_filters = false; - self - } + 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() + ); + } + _ => {} + } - /// Disable masternodes. - pub fn without_masternodes(mut self) -> Self { - self.enable_masternodes = false; - self + Ok(()) } +} - /// 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()); +impl Config { + pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { + self.peers.push(address); self } +} - /// Enable mempool tracking with specified strategy. - pub fn with_mempool_tracking(mut self, strategy: MempoolStrategy) -> Self { - self.enable_mempool_tracking = true; - self.mempool_strategy = strategy; - self +#[cfg(test)] +mod tests { + use crate::client::config::{Config, ConfigBuilder, MempoolStrategy}; + use crate::types::ValidationMode; + use dashcore::Network; + use std::net::SocketAddr; + use std::path::PathBuf; + + #[test] + fn test_default_config() { + let config = Config::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()); } - /// Set maximum number of mempool transactions to track. - pub fn with_max_mempool_transactions(mut self, max: usize) -> Self { - self.max_mempool_transactions = max; - self - } + #[test] + fn test_network_specific_configs() { + let mainnet = ConfigBuilder::mainnet().build().expect("Valid configuration"); + assert_eq!(mainnet.network(), Network::Dash); + assert!(mainnet.peers().is_empty()); // Should use DNS discovery - /// Enable or disable mempool persistence. - pub fn with_mempool_persistence(mut self, enabled: bool) -> Self { - self.persist_mempool = enabled; - self + let testnet = ConfigBuilder::testnet().build().expect("Valid configuration"); + assert_eq!(testnet.network(), Network::Testnet); + assert!(testnet.peers().is_empty()); // Should use DNS discovery + + let regtest = ConfigBuilder::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"); } - /// Set the starting height for synchronization. - pub fn with_start_height(mut self, height: u32) -> Self { - self.start_from_height = Some(height); - self + #[test] + fn test_builder_pattern() { + let path = PathBuf::from("/test/storage"); + + let config = ConfigBuilder::default() + .storage_path(path.clone()) + .validation_mode(ValidationMode::Basic) + .enable_mempool_tracking(true) + .mempool_strategy(MempoolStrategy::BloomFilter) + .max_mempool_transactions(500) + .persist_mempool(true) + .start_from_height(100000) + .build() + .expect("Valid configuration"); + + assert_eq!(*config.storage_path(), 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)); } - /// Validate the configuration. - pub fn validate(&self) -> Result<(), String> { - // Note: Empty peers list is now valid - DNS discovery will be used automatically + #[test] + fn test_add_peer() { + let mut config = ConfigBuilder::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(); - if self.max_peers == 0 { - return Err("max_peers must be > 0".to_string()); - } + config.add_peer(addr1); + config.add_peer(addr2); - // 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() - ); - } + assert_eq!(config.peers().len(), 2); + assert_eq!(config.peers()[0], addr1); + assert_eq!(config.peers()[1], addr2); + } - std::fs::create_dir_all(&self.storage_path).map_err(|e| { - format!( - "A valid storage path must be provided to the ClientConfig {:?}: {e}", - self.storage_path - ) - })?; + #[test] + fn test_disable_features() { + let config = ConfigBuilder::testnet() + .enable_filters(false) + .enable_masternodes(false) + .build() + .expect("Valid configuration"); - Ok(()) + assert!(!config.enable_filters()); + assert!(!config.enable_masternodes()); } - /// 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_validation_invalid_max_peers() { + let result = ConfigBuilder::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 = ConfigBuilder::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 e2daf8660..000000000 --- a/dash-spv/src/client/config_test.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Unit tests for client configuration - -#[cfg(test)] -mod tests { - use crate::client::config::{Config, MempoolStrategy}; - use crate::types::ValidationMode; - use dashcore::Network; - use std::net::SocketAddr; - use std::path::PathBuf; - - #[test] - fn test_default_config() { - let config = Config::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 = Config::mainnet(); - assert_eq!(mainnet.network, Network::Dash); - assert!(mainnet.peers.is_empty()); // Should use DNS discovery - - let testnet = Config::testnet(); - assert_eq!(testnet.network, Network::Testnet); - assert!(testnet.peers.is_empty()); // Should use DNS discovery - - let regtest = Config::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 = Config::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, 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 = Config::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 = Config::default().without_filters().without_masternodes(); - - assert!(!config.enable_filters); - assert!(!config.enable_masternodes); - } - - #[test] - fn test_validation_invalid_max_peers() { - let config = Config { - 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 = Config { - 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/mod.rs b/dash-spv/src/client/mod.rs index 09b1f3ac2..5b776edc4 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -55,9 +55,6 @@ 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; From 12b01dd55a33d09fdf4c9407447163191004d4e1 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 19:44:29 +0000 Subject: [PATCH 07/12] update codebase to use new Config API and made config module private --- dash-spv-ffi/FFI_API.md | 18 +- dash-spv-ffi/README.md | 4 +- dash-spv-ffi/dash_spv_ffi.h | 6 +- dash-spv-ffi/examples/basic_usage.c | 12 +- dash-spv-ffi/examples/wallet_manager_usage.rs | 3 +- dash-spv-ffi/include/dash_spv_ffi.h | 258 ++++---- dash-spv-ffi/src/bin/ffi_cli.rs | 38 +- dash-spv-ffi/src/client.rs | 53 +- dash-spv-ffi/src/config.rs | 591 ++++++++++-------- dash-spv-ffi/src/types.rs | 2 +- dash-spv-ffi/tests/c_tests/test_basic.c | 8 +- .../tests/c_tests/test_configuration.c | 32 +- .../tests/c_tests/test_event_draining.c | 2 +- dash-spv-ffi/tests/c_tests/test_integration.c | 4 +- .../tests/integration/test_full_workflow.rs | 4 +- dash-spv-ffi/tests/security/test_security.rs | 6 +- dash-spv-ffi/tests/test_client.rs | 19 +- dash-spv-ffi/tests/test_config.rs | 151 ----- dash-spv-ffi/tests/test_event_callbacks.rs | 51 +- dash-spv-ffi/tests/test_wallet_manager.rs | 20 +- .../tests/unit/test_async_operations.rs | 13 +- .../tests/unit/test_client_lifecycle.rs | 16 +- dash-spv-ffi/tests/unit/test_configuration.rs | 155 ++--- .../tests/unit/test_error_handling.rs | 9 +- .../tests/unit/test_memory_management.rs | 11 +- dash-spv/benches/storage.rs | 12 +- dash-spv/examples/filter_sync.rs | 12 +- dash-spv/examples/simple_sync.rs | 15 +- dash-spv/examples/spv_with_wallet.rs | 13 +- dash-spv/src/client/config.rs | 5 +- dash-spv/src/client/core.rs | 9 +- dash-spv/src/client/lifecycle.rs | 29 +- dash-spv/src/client/mempool.rs | 34 +- dash-spv/src/client/message_handler.rs | 8 +- dash-spv/src/client/message_handler_test.rs | 6 +- dash-spv/src/client/mod.rs | 52 +- dash-spv/src/client/sync_coordinator.rs | 6 +- dash-spv/src/lib.rs | 2 +- dash-spv/src/mempool_filter.rs | 2 +- dash-spv/src/network/handshake.rs | 2 +- dash-spv/src/network/manager.rs | 27 +- dash-spv/src/storage/mod.rs | 28 +- dash-spv/src/sync/headers/manager.rs | 10 +- dash-spv/src/sync/manager.rs | 7 +- dash-spv/src/sync/masternodes/manager.rs | 25 +- dash-spv/src/sync/post_sync.rs | 11 +- dash-spv/src/sync/transitions.rs | 16 +- dash-spv/tests/block_download_test.rs | 14 +- dash-spv/tests/chainlock_simple_test.rs | 39 +- dash-spv/tests/edge_case_filter_sync_test.rs | 14 +- .../tests/filter_header_verification_test.rs | 26 +- dash-spv/tests/handshake_test.rs | 6 +- dash-spv/tests/header_sync_test.rs | 23 +- dash-spv/tests/peer_test.rs | 39 +- .../tests/smart_fetch_integration_test.rs | 4 +- dash-spv/tests/test_handshake_logic.rs | 2 +- dash-spv/tests/wallet_integration_test.rs | 14 +- 57 files changed, 958 insertions(+), 1040 deletions(-) delete mode 100644 dash-spv-ffi/tests/test_config.rs diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index d87b92a76..cc95ee9e5 100644 --- a/dash-spv-ffi/FFI_API.md +++ b/dash-spv-ffi/FFI_API.md @@ -52,7 +52,7 @@ Functions: 25 | `dash_spv_ffi_config_set_filter_load` | Sets whether to load bloom filters # Safety - `config` must be a valid... | config | | `dash_spv_ffi_config_set_masternode_sync_enabled` | Enables or disables masternode synchronization # Safety - `config` must be... | config | | `dash_spv_ffi_config_set_max_mempool_transactions` | Sets the maximum number of mempool transactions to track # Safety -... | config | -| `dash_spv_ffi_config_set_max_peers` | Sets the maximum number of peers to connect to # Safety - `config` must be... | config | +| `dash_spv_ffi_config_builder_set_max_peers` | Sets the maximum number of peers to connect to # Safety - `config` must be... | config | | `dash_spv_ffi_config_set_mempool_strategy` | Sets the mempool synchronization strategy # Safety - `config` must be a... | config | | `dash_spv_ffi_config_set_mempool_tracking` | Enables or disables mempool tracking # Safety - `config` must be a valid... | config | | `dash_spv_ffi_config_set_persist_mempool` | Sets whether to persist mempool state to disk # Safety - `config` must be a... | config | @@ -60,8 +60,8 @@ Functions: 25 | `dash_spv_ffi_config_set_restrict_to_configured_peers` | Restrict connections strictly to configured peers (disable DNS discovery and... | config | | `dash_spv_ffi_config_set_start_from_height` | Sets the starting block height for synchronization # Safety - `config` must... | config | | `dash_spv_ffi_config_set_user_agent` | Sets the user agent string to advertise in the P2P handshake # Safety -... | config | -| `dash_spv_ffi_config_set_validation_mode` | Sets the validation mode for the SPV client # Safety - `config` must be a... | config | -| `dash_spv_ffi_config_set_worker_threads` | Sets the number of Tokio worker threads for the FFI runtime (0 = auto) #... | config | +| `dash_spv_ffi_config_builder_set_validation_mode` | Sets the validation mode for the SPV client # Safety - `config` 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_testnet` | No description | config | ### Synchronization @@ -440,10 +440,10 @@ Sets the maximum number of mempool transactions to track # Safety - `config` mu --- -#### `dash_spv_ffi_config_set_max_peers` +#### `dash_spv_ffi_config_builder_set_max_peers` ```c -dash_spv_ffi_config_set_max_peers(config: *mut FFIClientConfig, max_peers: u32,) -> i32 +dash_spv_ffi_config_builder_set_max_peers(config: *mut FFIClientConfig, max_peers: u32,) -> i32 ``` **Description:** @@ -568,10 +568,10 @@ Sets the user agent string to advertise in the P2P handshake # Safety - `config --- -#### `dash_spv_ffi_config_set_validation_mode` +#### `dash_spv_ffi_config_builder_set_validation_mode` ```c -dash_spv_ffi_config_set_validation_mode(config: *mut FFIClientConfig, mode: FFIValidationMode,) -> i32 +dash_spv_ffi_config_builder_set_validation_mode(config: *mut FFIClientConfig, mode: FFIValidationMode,) -> i32 ``` **Description:** @@ -584,10 +584,10 @@ Sets the validation mode for the SPV client # Safety - `config` must be a valid --- -#### `dash_spv_ffi_config_set_worker_threads` +#### `dash_spv_ffi_config_builder_set_worker_threads` ```c -dash_spv_ffi_config_set_worker_threads(config: *mut FFIClientConfig, threads: u32,) -> i32 +dash_spv_ffi_config_builder_set_worker_threads(config: *mut FFIClientConfig, threads: u32,) -> i32 ``` **Description:** 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..638235551 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"); + FFIConfig* 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; } + FFIConfig* 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..21467dc5d 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; /** @@ -61,11 +61,10 @@ typedef struct FFIArray { uintptr_t elem_align; } FFIArray; -typedef struct FFIClientConfig { +typedef struct FFIConfig { void *inner; uint32_t worker_threads; - -} FFIClientConfig; +} FFIConfig; typedef struct FFIString { char *ptr; @@ -171,6 +170,11 @@ typedef struct FFIWalletManager { uint8_t _private[0]; } FFIWalletManager; +typedef struct FFIConfigBuilder { + void *inner; + uint32_t worker_threads; +} FFIConfigBuilder; + /** * Handle for Core SDK that can be passed to Platform SDK */ @@ -283,7 +287,7 @@ struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network, * - `config` must be a valid, non-null pointer for the duration of the call. * - The returned pointer must be freed with `dash_spv_ffi_client_destroy`. */ - struct FFIDashSpvClient *dash_spv_ffi_client_new(const struct FFIClientConfig *config) ; + struct FFIDashSpvClient *dash_spv_ffi_client_new(const struct FFIConfig *config) ; /** * Drain pending events and invoke configured callbacks (non-blocking). @@ -303,7 +307,7 @@ struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network, */ int32_t dash_spv_ffi_client_update_config(struct FFIDashSpvClient *client, - const struct FFIClientConfig *config) + const struct FFIConfig *config) ; /** @@ -482,17 +486,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,261 +527,282 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli */ void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ; - struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ; + struct FFIConfigBuilder *dash_spv_ffi_config_builder_mainnet(void) ; - struct FFIClientConfig *dash_spv_ffi_config_mainnet(void) ; + struct FFIConfigBuilder *dash_spv_ffi_config_builder_testnet(void) ; - struct FFIClientConfig *dash_spv_ffi_config_testnet(void) ; + struct FFIConfigBuilder *dash_spv_ffi_config_builder_devnet(void) ; + + struct FFIConfigBuilder *dash_spv_ffi_config_builder_regtest(void) ; /** * Sets the data directory for storing blockchain data * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_data_dir(struct FFIClientConfig *config, - const char *path) +int32_t dash_spv_ffi_config_builder_set_storage_path(struct FFIConfigBuilder *builder, + const char *path) ; /** * Sets the validation mode for the SPV client * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_validation_mode(struct FFIClientConfig *config, - enum DashSpvValidationMode mode) +int32_t dash_spv_ffi_config_builder_set_validation_mode(struct FFIConfigBuilder *builder, + enum DashSpvValidationMode mode) ; /** * Sets the maximum number of peers to connect to * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_max_peers(struct FFIClientConfig *config, - uint32_t max_peers) -; - -/** - * 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 - */ - -int32_t dash_spv_ffi_config_add_peer(struct FFIClientConfig *config, - const char *addr) +int32_t dash_spv_ffi_config_builder_set_max_peers(struct FFIConfigBuilder *builder, + uint32_t max_peers) ; /** * Sets the user agent string to advertise in the P2P handshake * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_user_agent(struct FFIClientConfig *config, - const char *user_agent) +int32_t dash_spv_ffi_config_builder_set_user_agent(struct FFIConfigBuilder *builder, + const char *user_agent) ; /** * Sets whether to relay transactions (currently a no-op) * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_relay_transactions(struct FFIClientConfig *config, - bool _relay) +int32_t dash_spv_ffi_config_builder_set_relay_transactions(struct FFIConfigBuilder *builder, + bool _relay) ; /** * Sets whether to load bloom filters * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_filter_load(struct FFIClientConfig *config, - bool load_filters) +int32_t dash_spv_ffi_config_builder_set_filter_load(struct FFIConfigBuilder *builder, + bool load_filters) ; /** * Restrict connections strictly to configured peers (disable DNS discovery and peer store) * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `builder` must be a valid pointer to an FFIConfigBuilder */ -int32_t dash_spv_ffi_config_set_restrict_to_configured_peers(struct FFIClientConfig *config, - bool restrict_peers) +int32_t dash_spv_ffi_config_builder_set_restrict_to_configured_peers(struct FFIConfigBuilder *builder, + bool restrict_peers) ; /** * Enables or disables masternode synchronization * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_masternode_sync_enabled(struct FFIClientConfig *config, - bool enable) +int32_t dash_spv_ffi_config_builder_set_masternode_sync_enabled(struct FFIConfigBuilder *builder, + bool enable) ; /** - * Gets the network type from the configuration + * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig or null - * - If null, returns FFINetwork::Dash as default + * - `config` must be a valid pointer to an FFIConfig */ - FFINetwork dash_spv_ffi_config_get_network(const struct FFIClientConfig *config) ; + +int32_t dash_spv_ffi_config_builder_set_worker_threads(struct FFIConfigBuilder *builder, + uint32_t threads) +; /** - * Gets the data directory path from the configuration + * Enables or disables mempool tracking * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig or null - * - If null or no data directory is set, returns an FFIString with null pointer - * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call */ - struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIClientConfig *config) ; + +int32_t dash_spv_ffi_config_builder_set_mempool_tracking(struct FFIConfigBuilder *builder, + bool enable) +; /** - * Destroys an FFIClientConfig and frees its memory + * Sets the mempool synchronization strategy * * # 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 + * - `builder` must be a valid pointer to an FFIConfigBuilder + * - The caller must ensure the config pointer remains valid for the duration of this call */ -void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) +int32_t dash_spv_ffi_config_builder_set_mempool_strategy(struct FFIConfigBuilder *builder, + enum FFIMempoolStrategy strategy) ; /** - * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) + * Sets the maximum number of mempool transactions to track * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig + * - `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_set_worker_threads(struct FFIClientConfig *config, uint32_t threads) ; + +int32_t dash_spv_ffi_config_builder_set_max_mempool_transactions(struct FFIConfigBuilder *builder, + uint32_t max_transactions) +; /** - * Enables or disables mempool tracking + * Sets whether to fetch full mempool transaction data * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_mempool_tracking(struct FFIClientConfig *config, - bool enable) +int32_t dash_spv_ffi_config_builder_set_fetch_mempool_transactions(struct FFIConfigBuilder *builder, + bool fetch) ; /** - * Sets the mempool synchronization strategy + * Sets whether to persist mempool state to disk * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_mempool_strategy(struct FFIClientConfig *config, - enum FFIMempoolStrategy strategy) +int32_t dash_spv_ffi_config_builder_set_persist_mempool(struct FFIConfigBuilder *builder, + bool persist) ; /** - * Sets the maximum number of mempool transactions to track + * Sets the starting block height for synchronization * * # Safety - * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_max_mempool_transactions(struct FFIClientConfig *config, - uint32_t max_transactions) +int32_t dash_spv_ffi_config_builder_set_start_from_height(struct FFIConfigBuilder *builder, + uint32_t height) ; /** - * Sets whether to fetch full mempool transaction data + * Gets ownership of the builder and returns the built configuration destroying the builder in the process * * # Safety - * - `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 + * - `builder` must be a valid pointer to an FFIConfigBuilder or null + * - If null, returns default configuration */ -int32_t dash_spv_ffi_config_set_fetch_mempool_transactions(struct FFIClientConfig *config, - bool fetch) +struct FFIConfig *dash_spv_ffi_config_builder_build(struct FFIConfigBuilder *builder) ; /** - * Sets whether to persist mempool state to disk + * Destroys an FFIConfigBuilder and frees its memory * * # Safety - * - `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 + * - `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 */ -int32_t dash_spv_ffi_config_set_persist_mempool(struct FFIClientConfig *config, - bool persist) +void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) ; +/** + * 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 FFIConfig + * - `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 FFIConfig *config, const char *addr) ; + +/** + * Gets the network type from the configuration + * + * # Safety + * - `config` must be a valid pointer to an FFIConfig or null + * - If null, returns FFINetwork::Dash as default + */ + FFINetwork dash_spv_ffi_config_get_network(const struct FFIConfig *config) ; + +/** + * Gets the data directory path from the configuration + * + * # Safety + * - `config` must be a valid pointer to an FFIConfig or null + * - If null or no data directory is set, returns an FFIString with null pointer + * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` + */ + struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIConfig *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) ; + bool dash_spv_ffi_config_get_mempool_tracking(const struct FFIConfig *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 */ - -enum FFIMempoolStrategy dash_spv_ffi_config_get_mempool_strategy(const struct FFIClientConfig *config) -; + enum FFIMempoolStrategy dash_spv_ffi_config_get_mempool_strategy(const struct FFIConfig *config) ; /** - * Sets the starting block height for synchronization + * Destroys an FFIConfig and frees its memory * * # Safety - * - `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 + * - `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 instance */ - -int32_t dash_spv_ffi_config_set_start_from_height(struct FFIClientConfig *config, - uint32_t height) -; + void dash_spv_ffi_config_destroy(struct FFIConfig *config) ; const char *dash_spv_ffi_get_last_error(void) ; 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 47ee04178..50165521d 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, + null_check, set_last_error, FFIConfig, FFIDetailedSyncProgress, FFIErrorCode, + FFIEventCallbacks, FFISpvStats, FFISyncProgress, FFIWalletManager, }; // Import wallet types from key-wallet-ffi use key_wallet_ffi::FFIWalletManager as KeyWalletFFIWalletManager; @@ -128,7 +128,7 @@ pub struct FFIDashSpvClient { /// - The returned pointer must be freed with `dash_spv_ffi_client_destroy`. #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_client_new( - config: *const FFIClientConfig, + config: *const FFIConfig, ) -> *mut FFIDashSpvClient { null_check!(config, std::ptr::null_mut()); @@ -155,7 +155,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_new( 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) { @@ -403,7 +403,7 @@ fn stop_client_internal(client: &mut FFIDashSpvClient) -> Result<(), dash_spv::S #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_client_update_config( client: *mut FFIDashSpvClient, - config: *const FFIClientConfig, + config: *const FFIConfig, ) -> i32 { null_check!(client); null_check!(config); @@ -1230,49 +1230,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 ccc64126e..564cc3c1a 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::{Config, ValidationMode}; +use dash_spv::{Config, ConfigBuilder, ValidationMode}; use key_wallet_ffi::FFINetwork; use std::ffi::CStr; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; @@ -23,61 +23,79 @@ impl From for ValidationMode { } #[repr(C)] -pub struct FFIClientConfig { +pub struct FFIConfig { // Opaque pointer to avoid exposing internal ClientConfig in generated C headers inner: *mut std::ffi::c_void, // Tokio runtime worker thread count (0 = auto) pub worker_threads: u32, } +impl From for FFIConfig { + fn from(config: Config) -> Self { + let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; + Self { + inner, + worker_threads: 0, + } + } +} + +#[repr(C)] +pub struct FFIConfigBuilder { + // 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 FFIConfigBuilder { + fn from(builder: ConfigBuilder) -> 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 FFIConfigBuilder { + Box::into_raw(Box::new(ConfigBuilder::mainnet().into())) +} + #[no_mangle] -pub extern "C" fn dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClientConfig { - let config = Config::new(network.into()); - let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; - Box::into_raw(Box::new(FFIClientConfig { - inner, - worker_threads: 0, - })) +pub extern "C" fn dash_spv_ffi_config_builder_testnet() -> *mut FFIConfigBuilder { + Box::into_raw(Box::new(ConfigBuilder::testnet().into())) } #[no_mangle] -pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { - let config = Config::mainnet(); - let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; - Box::into_raw(Box::new(FFIClientConfig { - inner, - worker_threads: 0, - })) +pub extern "C" fn dash_spv_ffi_config_builder_devnet() -> *mut FFIConfigBuilder { + Box::into_raw(Box::new(ConfigBuilder::devnet().into())) } #[no_mangle] -pub extern "C" fn dash_spv_ffi_config_testnet() -> *mut FFIClientConfig { - let config = Config::testnet(); - let inner = Box::into_raw(Box::new(config)) as *mut std::ffi::c_void; - Box::into_raw(Box::new(FFIClientConfig { - inner, - worker_threads: 0, - })) +pub extern "C" fn dash_spv_ffi_config_builder_regtest() -> *mut FFIConfigBuilder { + Box::into_raw(Box::new(ConfigBuilder::regtest().into())) } /// Sets the data directory for storing blockchain data /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_data_dir( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_storage_path( + builder: *mut FFIConfigBuilder, path: *const c_char, ) -> i32 { - null_check!(config); + null_check!(builder); null_check!(path); - let config = unsafe { &mut *((*config).inner as *mut Config) }; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; match CStr::from_ptr(path).to_str() { Ok(path_str) => { - config.storage_path = path_str.into(); + builder.storage_path(path_str); FFIErrorCode::Success as i32 } Err(e) => { @@ -90,136 +108,57 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_data_dir( /// Sets the validation mode for the SPV client /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_validation_mode( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_validation_mode( + builder: *mut FFIConfigBuilder, mode: FFIValidationMode, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.validation_mode = mode.into(); + let config = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + config.validation_mode(mode.into()); FFIErrorCode::Success as i32 } /// Sets the maximum number of peers to connect to /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_max_peers( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_peers( + builder: *mut FFIConfigBuilder, max_peers: u32, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.max_peers = max_peers; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.max_peers(max_peers); FFIErrorCode::Success as i32 } -// Note: dash-spv doesn't have min_peers, only max_peers - -/// 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 -#[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( - config: *mut FFIClientConfig, - addr: *const c_char, -) -> i32 { - null_check!(config); - null_check!(addr); - - let cfg = unsafe { &mut *((*config).inner as *mut Config) }; - let default_port = match cfg.network { - dashcore::Network::Dash => 9999, - dashcore::Network::Testnet => 19999, - dashcore::Network::Regtest => 19899, - dashcore::Network::Devnet => 29999, - _ => 9999, - }; - - let addr_str = match CStr::from_ptr(addr).to_str() { - Ok(s) => s.trim(), - Err(e) => { - set_last_error(&format!("Invalid UTF-8 in address: {}", e)); - return FFIErrorCode::InvalidArgument as i32; - } - }; - - // 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); - return FFIErrorCode::Success as i32; - } - - // If not, must be a hostname - reject empty or missing hostname - if addr_str.is_empty() || addr_str.starts_with(':') { - set_last_error("Empty or missing hostname"); - return FFIErrorCode::InvalidArgument as i32; - } - - let addr_with_port = if addr_str.contains(':') { - addr_str.to_string() - } else { - format!("{}:{}", addr_str, default_port) - }; - - match addr_with_port.to_socket_addrs() { - Ok(mut iter) => match iter.next() { - Some(sock) => { - cfg.peers.push(sock); - FFIErrorCode::Success as i32 - } - None => { - set_last_error(&format!("Failed to resolve address: {}", addr_str)); - FFIErrorCode::InvalidArgument as i32 - } - }, - Err(e) => { - set_last_error(&format!("Invalid address {} ({})", addr_str, e)); - FFIErrorCode::InvalidArgument as i32 - } - } -} - /// Sets the user agent string to advertise in the P2P handshake /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_user_agent( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_user_agent( + builder: *mut FFIConfigBuilder, user_agent: *const c_char, ) -> i32 { - null_check!(config); + 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 *((*config).inner as *mut Config) }; - cfg.user_agent = Some(agent_str.to_string()); + let cfg = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + cfg.user_agent(agent_str.to_string()); FFIErrorCode::Success as i32 } Err(e) => { @@ -232,16 +171,16 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_user_agent( /// Sets whether to relay transactions (currently a no-op) /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_relay_transactions( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_relay_transactions( + builder: *mut FFIConfigBuilder, _relay: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let _config = unsafe { &mut *((*config).inner as *mut Config) }; + let _builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; // relay_transactions not directly settable in current ClientConfig FFIErrorCode::Success as i32 } @@ -249,270 +188,380 @@ pub unsafe extern "C" fn dash_spv_ffi_config_set_relay_transactions( /// Sets whether to load bloom filters /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_filter_load( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_filter_load( + builder: *mut FFIConfigBuilder, load_filters: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.enable_filters = load_filters; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.enable_filters(load_filters); FFIErrorCode::Success as i32 } /// Restrict connections strictly to configured peers (disable DNS discovery and peer store) /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `builder` must be a valid pointer to an FFIConfigBuilder #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_set_restrict_to_configured_peers( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_restrict_to_configured_peers( + builder: *mut FFIConfigBuilder, restrict_peers: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.restrict_to_configured_peers = restrict_peers; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.restrict_to_configured_peers(restrict_peers); FFIErrorCode::Success as i32 } /// Enables or disables masternode synchronization /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_masternode_sync_enabled( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_masternode_sync_enabled( + builder: *mut FFIConfigBuilder, enable: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.enable_masternodes = enable; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.enable_masternodes(enable); FFIErrorCode::Success as i32 } -/// Gets the network type from the configuration -/// -/// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig or null -/// - If null, returns FFINetwork::Dash as default -#[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_get_network( - config: *const FFIClientConfig, -) -> FFINetwork { - if config.is_null() { - return FFINetwork::Dash; - } - - let config = unsafe { &*((*config).inner as *const Config) }; - config.network.into() -} - -/// Gets the data directory path from the configuration -/// -/// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig or null -/// - If null or no data directory is set, returns an FFIString with null pointer -/// - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` -#[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir( - config: *const FFIClientConfig, -) -> FFIString { - if config.is_null() { - return FFIString { - ptr: std::ptr::null_mut(), - length: 0, - }; - } - - let config = unsafe { &*((*config).inner as *const Config) }; - 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 -/// - 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] -pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) { - if !config.is_null() { - // Reclaim outer struct - let cfg = Box::from_raw(config); - // Free inner ClientConfig if present - if !cfg.inner.is_null() { - let _ = Box::from_raw(cfg.inner as *mut Config); - } - } -} - -impl FFIClientConfig { - pub fn get_inner(&self) -> &Config { - unsafe { &*(self.inner as *const Config) } - } - - pub fn clone_inner(&self) -> Config { - unsafe { (*(self.inner as *const Config)).clone() } - } -} - /// Sets the number of Tokio worker threads for the FFI runtime (0 = auto) /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig +/// - `config` must be a valid pointer to an FFIConfig #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_set_worker_threads( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_worker_threads( + builder: *mut FFIConfigBuilder, threads: u32, ) -> i32 { - null_check!(config); - let cfg = &mut *config; + null_check!(builder); + let cfg = &mut *builder; cfg.worker_threads = threads; FFIErrorCode::Success as i32 } -// Mempool configuration functions - /// Enables or disables mempool tracking /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_mempool_tracking( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_tracking( + builder: *mut FFIConfigBuilder, enable: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.enable_mempool_tracking = enable; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.enable_mempool_tracking(enable); FFIErrorCode::Success as i32 } /// Sets the mempool synchronization strategy /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_mempool_strategy( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_strategy( + builder: *mut FFIConfigBuilder, strategy: FFIMempoolStrategy, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.mempool_strategy = strategy.into(); + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.mempool_strategy(strategy.into()); FFIErrorCode::Success as i32 } /// Sets the maximum number of mempool transactions to track /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_max_mempool_transactions( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_mempool_transactions( + builder: *mut FFIConfigBuilder, max_transactions: u32, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.max_mempool_transactions = max_transactions as usize; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.max_mempool_transactions(max_transactions as usize); FFIErrorCode::Success as i32 } /// Sets whether to fetch full mempool transaction data /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_fetch_mempool_transactions( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_fetch_mempool_transactions( + builder: *mut FFIConfigBuilder, fetch: bool, ) -> i32 { - null_check!(config); + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.fetch_mempool_transactions = fetch; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + builder.fetch_mempool_transactions(fetch); FFIErrorCode::Success as i32 } /// Sets whether to persist mempool state to disk /// /// # Safety -/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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_set_persist_mempool( - config: *mut FFIClientConfig, +pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_persist_mempool( + builder: *mut FFIConfigBuilder, persist: bool, ) -> i32 { - null_check!(config); + null_check!(builder); + + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + 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 FFIConfigBuilder, + height: u32, +) -> i32 { + null_check!(builder); - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.persist_mempool = persist; + let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + 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 FFIConfigBuilder, +) -> *mut FFIConfig { + if builder.is_null() { + return Box::into_raw(Box::new(Config::default().into())); + } + + let ffi_builder = Box::from_raw(builder); + let builder = Box::from_raw(ffi_builder.inner as *mut ConfigBuilder); + + match builder.build() { + Ok(config) => Box::into_raw(Box::new(config.into())), + 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 FFIConfigBuilder) { + if !builder.is_null() { + let builder = Box::from_raw(builder); + if !builder.inner.is_null() { + let _ = Box::from_raw(builder.inner as *mut ConfigBuilder); + } + } +} + +/// 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 FFIConfig +/// - `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] +pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( + config: *mut FFIConfig, + addr: *const c_char, +) -> i32 { + null_check!(config); + null_check!(addr); + + let cfg = unsafe { &mut *((*config).inner as *mut Config) }; + let default_port = match cfg.network() { + dashcore::Network::Dash => 9999, + dashcore::Network::Testnet => 19999, + dashcore::Network::Regtest => 19899, + dashcore::Network::Devnet => 29999, + _ => 9999, + }; + + let addr_str = match CStr::from_ptr(addr).to_str() { + Ok(s) => s.trim(), + Err(e) => { + set_last_error(&format!("Invalid UTF-8 in address: {}", e)); + return FFIErrorCode::InvalidArgument as i32; + } + }; + + // 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.add_peer(sock); + return FFIErrorCode::Success as i32; + } + + // If not, must be a hostname - reject empty or missing hostname + if addr_str.is_empty() || addr_str.starts_with(':') { + set_last_error("Empty or missing hostname"); + return FFIErrorCode::InvalidArgument as i32; + } + + let addr_with_port = if addr_str.contains(':') { + addr_str.to_string() + } else { + format!("{}:{}", addr_str, default_port) + }; + + match addr_with_port.to_socket_addrs() { + Ok(mut iter) => match iter.next() { + Some(sock) => { + cfg.add_peer(sock); + FFIErrorCode::Success as i32 + } + None => { + set_last_error(&format!("Failed to resolve address: {}", addr_str)); + FFIErrorCode::InvalidArgument as i32 + } + }, + Err(e) => { + set_last_error(&format!("Invalid address {} ({})", addr_str, e)); + FFIErrorCode::InvalidArgument as i32 + } + } +} + +/// Gets the network type from the configuration +/// +/// # Safety +/// - `config` must be a valid pointer to an FFIConfig or null +/// - If null, returns FFINetwork::Dash as default +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_get_network(config: *const FFIConfig) -> FFINetwork { + if config.is_null() { + return FFINetwork::Dash; + } + + let config = unsafe { &*((*config).inner as *const Config) }; + config.network().into() +} + +/// Gets the data directory path from the configuration +/// +/// # Safety +/// - `config` must be a valid pointer to an FFIConfig or null +/// - If null or no data directory is set, returns an FFIString with null pointer +/// - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` +#[no_mangle] +pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir(config: *const FFIConfig) -> FFIString { + if config.is_null() { + return FFIString { + ptr: std::ptr::null_mut(), + length: 0, + }; + } + + let config = unsafe { &*((*config).inner as *const Config) }; + FFIString::new(&config.storage_path().to_string_lossy()) +} + /// 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( - config: *const FFIClientConfig, + config: *const FFIConfig, ) -> bool { if config.is_null() { return false; } let config = unsafe { &*((*config).inner as *const Config) }; - 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( - config: *const FFIClientConfig, + config: *const FFIConfig, ) -> FFIMempoolStrategy { if config.is_null() { return FFIMempoolStrategy::FetchAll; } let config = unsafe { &*((*config).inner as *const Config) }; - config.mempool_strategy.into() + config.mempool_strategy().into() } -// Checkpoint sync configuration functions - -/// Sets the starting block height for synchronization +/// Destroys an FFIConfig and frees its memory /// /// # Safety -/// - `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 +/// - `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 instance #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_set_start_from_height( - config: *mut FFIClientConfig, - height: u32, -) -> i32 { - null_check!(config); +pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIConfig) { + if !config.is_null() { + // Reclaim outer struct + let cfg = Box::from_raw(config); + // Free inner ClientConfig if present + if !cfg.inner.is_null() { + let _ = Box::from_raw(cfg.inner as *mut Config); + } + } +} - let config = unsafe { &mut *((*config).inner as *mut Config) }; - config.start_from_height = Some(height); - FFIErrorCode::Success as i32 +impl FFIConfig { + pub fn get_inner(&self) -> &Config { + unsafe { &*(self.inner as *const Config) } + } + + pub fn clone_inner(&self) -> Config { + unsafe { (*(self.inner as *const Config)).clone() } + } } 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..e7a95cfaf 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); @@ -93,7 +93,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(config, 10); assert_ne!(result, FFIErrorCode::Success as i32); } } @@ -106,7 +106,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..456cf1976 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; @@ -33,15 +32,16 @@ mod tests { *data.completion_called.lock().unwrap() = true; } - fn create_test_config() -> (*mut FFIClientConfig, TempDir) { + fn create_test_config() -> (*mut FFIConfig, 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 26b71e249..78f676309 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -18,16 +18,17 @@ mod tests { 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_set_data_dir( - config, + 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()); @@ -55,15 +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_set_data_dir( - config, + 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()); @@ -74,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..341b00715 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}; @@ -59,15 +58,17 @@ mod tests { } } - fn create_test_client() -> (*mut FFIDashSpvClient, *mut FFIClientConfig, TempDir) { + fn create_test_client() -> (*mut FFIDashSpvClient, *mut FFIConfig, 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..a81320fa0 100644 --- a/dash-spv-ffi/tests/unit/test_client_lifecycle.rs +++ b/dash-spv-ffi/tests/unit/test_client_lifecycle.rs @@ -6,20 +6,20 @@ #[cfg(test)] mod tests { use crate::*; - use key_wallet_ffi::FFINetwork; use serial_test::serial; use std::ffi::CString; use std::thread; use std::time::Duration; use tempfile::TempDir; - fn create_test_config_with_dir() -> (*mut FFIClientConfig, TempDir) { + fn create_test_config_with_dir() -> (*mut FFIConfig, 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..f67f025b5 100644 --- a/dash-spv-ffi/tests/unit/test_configuration.rs +++ b/dash-spv-ffi/tests/unit/test_configuration.rs @@ -5,36 +5,22 @@ 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 config = dash_spv_ffi_config_builder_build(builder); + assert!(!config.is_null()); + let retrieved = dash_spv_ffi_config_get_data_dir(config); if !retrieved.ptr.is_null() { let path_str = FFIString::from_ptr(retrieved.ptr).unwrap(); @@ -50,7 +36,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 +93,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 +112,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 +135,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 +164,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 +222,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 +238,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 +248,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 +278,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 +297,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 +331,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 +362,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 +383,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 +416,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 +439,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/benches/storage.rs b/dash-spv/benches/storage.rs index 367b70775..4288aa02d 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}, - Config, Hash, + ConfigBuilder, Hash, }; use dashcore::{block::Version, BlockHash, CompactTarget, Header}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -34,7 +34,10 @@ fn bench_disk_storage(c: &mut Criterion) { c.bench_function("storage/disk/store", |b| { b.to_async(&rt).iter_batched( || async { - let config = Config::testnet().with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::testnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); DiskStorageManager::new(&config).await.unwrap() }, |a| async { @@ -48,7 +51,10 @@ fn bench_disk_storage(c: &mut Criterion) { ) }); - let config = Config::testnet().with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::testnet() + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config"); let mut storage = rt.block_on(async { let mut storage = DiskStorageManager::new(&config).await.unwrap(); diff --git a/dash-spv/examples/filter_sync.rs b/dash-spv/examples/filter_sync.rs index a2920ae00..41400f50e 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, Config, DashSpvClient, LevelFilter}; +use dash_spv::ConfigBuilder; +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,9 +23,10 @@ async fn main() -> Result<(), Box> { )?; // Create configuration with filter support - let config = Config::mainnet() - .with_storage_path("./.tmp/filter-sync-example-storage") - .without_masternodes(); // Skip masternode sync for this example + let config = ConfigBuilder::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?; @@ -33,7 +35,7 @@ async fn main() -> Result<(), Box> { 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 95ca38e16..d88820cfe 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, Config, DashSpvClient, LevelFilter}; +use dash_spv::ConfigBuilder; +use dash_spv::{init_console_logging, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -16,11 +17,11 @@ async fn main() -> Result<(), Box> { let _logging_guard = init_console_logging(LevelFilter::INFO)?; // Create a simple configuration - let config = Config::mainnet() - .with_storage_path("./.tmp/simple-sync-example-storage") - .without_filters() // Skip filter sync for this example - .without_masternodes(); // Skip masternode sync for this example - + let config = ConfigBuilder::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?; @@ -28,7 +29,7 @@ async fn main() -> Result<(), Box> { 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 43b668a11..43fe3f960 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::{Config, DashSpvClient, LevelFilter}; +use dash_spv::ConfigBuilder; +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,9 +18,11 @@ async fn main() -> Result<(), Box> { let _logging_guard = dash_spv::init_console_logging(LevelFilter::INFO)?; // Create SPV client configuration - let config = Config::testnet() - .with_storage_path("./.tmp/spv-with-wallet-example-storage") - .with_validation_mode(dash_spv::ValidationMode::Full); + let config = ConfigBuilder::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?; @@ -28,7 +31,7 @@ async fn main() -> Result<(), Box> { 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/config.rs b/dash-spv/src/client/config.rs index c6a653932..f60d4873d 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -39,11 +39,12 @@ pub struct Config { /// 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. - #[getset(get = "pub")] + #[getset(get_copy = "pub")] restrict_to_configured_peers: bool, /// Path for persistent storage. Defaults to ./dash-spv-storage #[getset(get = "pub")] + #[builder(setter(into))] storage_path: PathBuf, /// Validation mode. @@ -143,7 +144,7 @@ impl ConfigBuilder { builder } - pub fn validate(&self) -> Result<(), String> { + fn validate(&self) -> Result<(), String> { match self.max_peers { Some(max_peers) if max_peers == 0 => { return Err("max_peers must be > 0".to_string()); diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index 6ad4e9c01..5abd84d36 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 1bc8df180..78a86ffcb 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 39a73ca47..ee135412b 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 5b776edc4..84c17fc53 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,7 +46,7 @@ mod sync_coordinator; mod transactions; // Re-export public types from extracted modules -pub use config::Config; +pub use config::{Config, ConfigBuilder, MempoolStrategy}; pub use message_handler::MessageHandler; pub use status_display::StatusDisplay; @@ -60,8 +58,8 @@ mod message_handler_test; #[cfg(test)] mod tests { - use super::{Config, DashSpvClient}; - use crate::client::config::MempoolStrategy; + use super::DashSpvClient; + use crate::client::config::{ConfigBuilder, MempoolStrategy}; use crate::storage::DiskStorageManager; use crate::{test_utils::MockNetworkManager, types::UnconfirmedTransaction}; use dashcore::{Address, Amount, Transaction, TxOut}; @@ -79,16 +77,20 @@ mod tests { #[tokio::test] async fn client_exposes_shared_wallet_manager() { - let config = Config::mainnet() - .without_filters() - .without_masternodes() - .with_mempool_tracking(MempoolStrategy::FetchAll) - .with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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 @@ -105,28 +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 = Config::testnet() - .without_filters() - .without_masternodes() - .with_mempool_tracking(MempoolStrategy::FetchAll) - .with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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::new(&config).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 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/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 StorageResult { use std::fs; - let storage_path = config.storage_path.clone(); + 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::ConfigBuilder; + let temp_dir = TempDir::new()?; - Self::new(&Config::testnet().with_storage_path(temp_dir.path())).await + Self::new( + &ConfigBuilder::testnet() + .storage_path(temp_dir.path()) + .build() + .expect("Valid configuration"), + ) + .await } /// Start the background worker saving data every 5 seconds @@ -394,6 +402,8 @@ impl masternode::MasternodeStateStorage for DiskStorageManager { #[cfg(test)] mod tests { + use crate::ConfigBuilder; + use super::*; use dashcore::Header as BlockHeader; use tempfile::TempDir; @@ -401,8 +411,10 @@ mod tests { #[tokio::test] async fn test_store_load_headers() -> Result<(), Box> { // Create a temporary directory for the test - let temp_dir = TempDir::new()?; - let config = Config::testnet().with_storage_path(temp_dir.path()); + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let config = + ConfigBuilder::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); @@ -445,7 +457,8 @@ mod tests { #[tokio::test] async fn test_checkpoint_storage_indexing() -> StorageResult<()> { let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let config = Config::testnet().with_storage_path(temp_dir.path()); + let config = + ConfigBuilder::testnet().storage_path(temp_dir.path()).build().expect("Valid config"); let mut storage = DiskStorageManager::new(&config).await?; // Create test headers starting from checkpoint height @@ -502,7 +515,8 @@ mod tests { #[tokio::test] async fn test_reverse_index_disk_storage() { let temp_dir = tempfile::tempdir().unwrap(); - let config = Config::regtest().with_storage_path(temp_dir.path()); + let config = + ConfigBuilder::regtest().storage_path(temp_dir.path()).build().expect("Valid config"); { let mut storage = DiskStorageManager::new(&config).await.unwrap(); @@ -565,7 +579,7 @@ mod tests { lock_file.set_extension("lock"); lock_file }; - let config = Config::regtest().with_storage_path(path); + let config = ConfigBuilder::regtest().storage_path(path).build().expect("Valid config"); let mut storage1 = DiskStorageManager::new(&config).await.unwrap(); assert!(lock_path.exists(), "Lock file should exist while storage is open"); diff --git a/dash-spv/src/sync/headers/manager.rs b/dash-spv/src/sync/headers/manager.rs index 3e0dd3d85..335b790f5 100644 --- a/dash-spv/src/sync/headers/manager.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -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,7 +179,7 @@ impl HeaderSyncManager { } } - if self.config.validation_mode != ValidationMode::None { + 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); @@ -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/manager.rs b/dash-spv/src/sync/manager.rs index cdfbc51b4..45bf928a6 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: &Config) -> 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/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index 8b03d560f..1486ce7af 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::ConfigBuilder; use std::collections::HashSet; use std::sync::Arc; use tempfile::TempDir; @@ -9,15 +10,16 @@ use tokio::sync::Mutex; use dashcore::block::Block; use dash_spv::{ - client::Config, storage::DiskStorageManager, sync::FilterSyncManager, - types::FilterMatch, + client::Config, storage::DiskStorageManager, sync::FilterSyncManager, types::FilterMatch, }; fn create_test_config() -> Config { - Config::testnet() - .without_masternodes() - .with_validation_mode(dash_spv::types::ValidationMode::None) - .with_storage_path(TempDir::new().unwrap().path()) + ConfigBuilder::testnet() + .enable_masternodes(false) + .validation_mode(dash_spv::types::ValidationMode::None) + .storage_path(TempDir::new().unwrap().path()) + .build() + .expect("Valid config") } #[tokio::test] diff --git a/dash-spv/tests/chainlock_simple_test.rs b/dash-spv/tests/chainlock_simple_test.rs index 506af6b52..28d7ce4cb 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::{Config, DashSpvClient}; +use dash_spv::client::DashSpvClient; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; +use dash_spv::ConfigBuilder; use dashcore::Network; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -31,15 +32,13 @@ async fn test_chainlock_validation_flow() { // Create client config with masternodes enabled let network = Network::Dash; let enable_masternodes = true; - let config = Config { - network, - enable_filters: false, - enable_masternodes, - validation_mode: ValidationMode::Basic, - storage_path: temp_dir.path().to_path_buf(), - peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config - ..Default::default() - }; + let config = ConfigBuilder::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(); @@ -48,7 +47,7 @@ async fn test_chainlock_validation_flow() { 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,15 +77,13 @@ async fn test_chainlock_manager_initialization() { let temp_dir = TempDir::new().unwrap(); // Create client config - let config = Config { - network: Network::Dash, - enable_filters: false, - enable_masternodes: false, - validation_mode: ValidationMode::Basic, - storage_path: temp_dir.path().to_path_buf(), - peers: vec!["127.0.0.1:9999".parse().unwrap()], // Dummy peer to satisfy config - ..Default::default() - }; + let config = ConfigBuilder::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(); @@ -95,7 +92,7 @@ async fn test_chainlock_manager_initialization() { 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 bf3555802..3b9c9aea8 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::Config, error::NetworkResult, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, + ConfigBuilder, }; use dashcore::{ block::Header as BlockHeader, hash_types::FilterHeader, network::message::NetworkMessage, - BlockHash, Network, + BlockHash, }; use dashcore_hashes::Hash; @@ -109,7 +109,10 @@ impl NetworkManager for MockNetworkManager { #[tokio::test] async fn test_filter_sync_at_tip_edge_case() { - let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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::new(&config, received_heights); @@ -151,7 +154,10 @@ async fn test_filter_sync_at_tip_edge_case() { #[tokio::test] async fn test_no_invalid_getcfheaders_at_tip() { - let config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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::new(&config, received_heights); diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index d897e063a..c99a6895a 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::Config, error::{NetworkError, NetworkResult, SyncError}, network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, types::PeerInfo, + ConfigBuilder, }; 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 config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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 = Config::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 config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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 = Config::new(Network::Dash); let received_heights = Arc::new(Mutex::new(HashSet::new())); let mut filter_sync: FilterSyncManager = FilterSyncManager::new(&config, received_heights); @@ -515,7 +519,10 @@ 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 config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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(); @@ -612,7 +619,10 @@ 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 config = Config::new(Network::Dash).with_storage_path(TempDir::new().unwrap().path()); + let config = ConfigBuilder::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(); diff --git a/dash-spv/tests/handshake_test.rs b/dash-spv/tests/handshake_test.rs index d55e7ddcf..ba4aec77b 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::{Config, Network}; +use dash_spv::Network; +use dash_spv::{ConfigBuilder, 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 = Config::new(Network::Dash); + let config = ConfigBuilder::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 32fe40239..dfdc7e191 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::{Config, DashSpvClient}, + client::DashSpvClient, network::PeerNetworkManager, storage::{BlockHeaderStorage, ChainStateStorage, DiskStorageManager}, sync::{HeaderSyncManager, ReorgConfig}, types::{ChainState, ValidationMode}, + ConfigBuilder, }; -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,9 +23,11 @@ async fn test_header_sync_with_client_integration() { let _ = env_logger::try_init(); // Test header sync integration with the full client - let config = Config::new(Network::Dash) - .with_validation_mode(ValidationMode::Basic) - .with_storage_path(TempDir::new().expect("Failed to create tmp dir").path()); + let config = ConfigBuilder::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 = @@ -34,7 +37,7 @@ async fn test_header_sync_with_client_integration() { 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"); @@ -87,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 config = Config::regtest().with_storage_path(temp_dir.path()); + let config = + ConfigBuilder::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 = Config::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 d4016dafa..7fbc45d40 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::ConfigBuilder; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -14,18 +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) -> Config { - let mut config = Config::new(network); - - config.storage_path = TempDir::new().unwrap().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 + ConfigBuilder::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] @@ -42,7 +43,7 @@ async fn test_peer_connection() { 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(); @@ -86,7 +87,8 @@ async fn test_peer_persistence() { 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.clone(), network_manager, storage_manager, wallet) @@ -111,7 +113,8 @@ async fn test_peer_persistence() { 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(); @@ -140,7 +143,9 @@ async fn test_peer_disconnection() { 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(); @@ -149,7 +154,7 @@ async fn test_peer_disconnection() { 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(); @@ -174,7 +179,7 @@ async fn test_max_peer_limit() { 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(); @@ -184,7 +189,7 @@ async fn test_max_peer_limit() { 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 ee8b5f4ab..b76e3ed58 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::Config; +use dash_spv::ConfigBuilder; 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 = Config::new(Network::Testnet); + let config = ConfigBuilder::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 3516e1d05..78be539d6 100644 --- a/dash-spv/tests/wallet_integration_test.rs +++ b/dash-spv/tests/wallet_integration_test.rs @@ -8,17 +8,19 @@ use tokio::sync::RwLock; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{Config, DashSpvClient}; +use dash_spv::{ConfigBuilder, 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 = Config::testnet() - .without_filters() - .with_storage_path(TempDir::new().unwrap().path()) - .without_masternodes(); + let config = ConfigBuilder::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(); @@ -27,7 +29,7 @@ async fn create_test_client( 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() } From 9ceddc36abf2ae6b7fb53496b327af4ed2b8c5a6 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Fri, 16 Jan 2026 23:47:08 +0000 Subject: [PATCH 08/12] tests and clippypass --- dash-spv-ffi/FFI_API.md | 302 ++++++++++-------- dash-spv-ffi/dash_spv_ffi.h | 2 +- dash-spv-ffi/include/dash_spv_ffi.h | 2 +- dash-spv-ffi/src/config.rs | 4 +- dash-spv-ffi/tests/c_tests/test_basic.c | 2 +- dash-spv-ffi/tests/unit/test_configuration.rs | 13 +- dash-spv/src/client/config.rs | 57 +--- dash-spv/src/client/status_display.rs | 2 +- dash-spv/src/lib.rs | 10 +- dash-spv/src/main.rs | 72 ++--- 10 files changed, 221 insertions(+), 245 deletions(-) diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index cc95ee9e5..0f76e06f9 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**: 69 ## 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,35 +33,38 @@ Functions: 4 ### Configuration -Functions: 25 +Functions: 28 | 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_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_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 FFIConfig and frees its memory # Safety - `builder` must be a... | config | | `dash_spv_ffi_config_get_mempool_strategy` | Gets the mempool synchronization strategy # Safety - `config` must be a... | config | | `dash_spv_ffi_config_get_mempool_tracking` | Gets whether mempool tracking is enabled # Safety - `config` must be a... | config | | `dash_spv_ffi_config_get_network` | Gets the network type from the configuration # Safety - `config` must be a... | config | -| `dash_spv_ffi_config_mainnet` | No description | config | -| `dash_spv_ffi_config_new` | No description | config | -| `dash_spv_ffi_config_set_data_dir` | Sets the data directory for storing blockchain data # Safety - `config`... | config | -| `dash_spv_ffi_config_set_fetch_mempool_transactions` | Sets whether to fetch full mempool transaction data # Safety - `config`... | config | -| `dash_spv_ffi_config_set_filter_load` | Sets whether to load bloom filters # Safety - `config` must be a valid... | config | -| `dash_spv_ffi_config_set_masternode_sync_enabled` | Enables or disables masternode synchronization # Safety - `config` must be... | config | -| `dash_spv_ffi_config_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 - `config` must be... | config | -| `dash_spv_ffi_config_set_mempool_strategy` | Sets the mempool synchronization strategy # Safety - `config` must be a... | config | -| `dash_spv_ffi_config_set_mempool_tracking` | Enables or disables mempool tracking # Safety - `config` must be a valid... | config | -| `dash_spv_ffi_config_set_persist_mempool` | Sets whether to persist mempool state to disk # Safety - `config` must be a... | config | -| `dash_spv_ffi_config_set_relay_transactions` | Sets whether to relay transactions (currently a no-op) # Safety - `config`... | config | -| `dash_spv_ffi_config_set_restrict_to_configured_peers` | Restrict connections strictly to configured peers (disable DNS discovery and... | config | -| `dash_spv_ffi_config_set_start_from_height` | Sets the starting block height for synchronization # Safety - `config` must... | config | -| `dash_spv_ffi_config_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 - `config` 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_testnet` | No description | config | +| `dash_spv_ffi_config_get_storage_path` | Gets the data directory path from the configuration # Safety - `config`... | config | ### Synchronization @@ -95,14 +97,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 @@ -181,7 +175,7 @@ Destroy the client and free associated resources. # Safety - `client` must be e #### `dash_spv_ffi_client_new` ```c -dash_spv_ffi_client_new(config: *const FFIClientConfig,) -> *mut FFIDashSpvClient +dash_spv_ffi_client_new(config: *const FFIConfig,) -> *mut FFIDashSpvClient ``` **Description:** @@ -231,7 +225,7 @@ Stop the SPV client. # Safety - `client` must be a valid, non-null pointer to a #### `dash_spv_ffi_client_update_config` ```c -dash_spv_ffi_client_update_config(client: *mut FFIDashSpvClient, config: *const FFIClientConfig,) -> i32 +dash_spv_ffi_client_update_config(client: *mut FFIDashSpvClient, config: *const FFIConfig,) -> i32 ``` **Description:** @@ -247,365 +241,407 @@ Update the running client's configuration. # Safety - `client` must be a valid #### `dash_spv_ffi_config_add_peer` ```c -dash_spv_ffi_config_add_peer(config: *mut FFIClientConfig, addr: *const c_char,) -> i32 +dash_spv_ffi_config_add_peer(config: *mut FFIConfig, addr: *const c_char,) -> i32 ``` **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 FFIConfig - `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 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 - `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_destroy` +#### `dash_spv_ffi_config_builder_build` ```c -dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) -> () +dash_spv_ffi_config_builder_build(builder: *mut FFIConfigBuilder,) -> *mut FFIConfig ``` **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 +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:** -- `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 +- `builder` must be a valid pointer to an FFIConfigBuilder or null - If null, returns default configuration **Module:** `config` --- -#### `dash_spv_ffi_config_get_data_dir` +#### `dash_spv_ffi_config_builder_destroy` ```c -dash_spv_ffi_config_get_data_dir(config: *const FFIClientConfig,) -> FFIString +dash_spv_ffi_config_builder_destroy(builder: *mut FFIConfigBuilder) -> () ``` **Description:** -Gets the data directory path from the configuration # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` +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:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` +- `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_get_mempool_strategy` +#### `dash_spv_ffi_config_builder_devnet` ```c -dash_spv_ffi_config_get_mempool_strategy(config: *const FFIClientConfig,) -> FFIMempoolStrategy +dash_spv_ffi_config_builder_devnet() -> *mut FFIConfigBuilder ``` -**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 +**Module:** `config` -**Safety:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default +--- + +#### `dash_spv_ffi_config_builder_mainnet` + +```c +dash_spv_ffi_config_builder_mainnet() -> *mut FFIConfigBuilder +``` **Module:** `config` --- -#### `dash_spv_ffi_config_get_mempool_tracking` +#### `dash_spv_ffi_config_builder_regtest` + +```c +dash_spv_ffi_config_builder_regtest() -> *mut FFIConfigBuilder +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_builder_set_fetch_mempool_transactions` ```c -dash_spv_ffi_config_get_mempool_tracking(config: *const FFIClientConfig,) -> bool +dash_spv_ffi_config_builder_set_fetch_mempool_transactions(builder: *mut FFIConfigBuilder, fetch: bool,) -> i32 ``` **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 +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:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns false as default +- `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_get_network` +#### `dash_spv_ffi_config_builder_set_filter_load` ```c -dash_spv_ffi_config_get_network(config: *const FFIClientConfig,) -> FFINetwork +dash_spv_ffi_config_builder_set_filter_load(builder: *mut FFIConfigBuilder, load_filters: bool,) -> i32 ``` **Description:** -Gets the network type from the configuration # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFINetwork::Dash as default +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:** -- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFINetwork::Dash as default +- `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_mainnet` +#### `dash_spv_ffi_config_builder_set_masternode_sync_enabled` ```c -dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig +dash_spv_ffi_config_builder_set_masternode_sync_enabled(builder: *mut FFIConfigBuilder, 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_new` +#### `dash_spv_ffi_config_builder_set_max_mempool_transactions` ```c -dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClientConfig +dash_spv_ffi_config_builder_set_max_mempool_transactions(builder: *mut FFIConfigBuilder, 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_set_data_dir` +#### `dash_spv_ffi_config_builder_set_max_peers` ```c -dash_spv_ffi_config_set_data_dir(config: *mut FFIClientConfig, path: *const c_char,) -> i32 +dash_spv_ffi_config_builder_set_max_peers(builder: *mut FFIConfigBuilder, max_peers: u32,) -> i32 ``` **Description:** -Sets the data directory for storing blockchain data # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `path` must be a valid null-terminated C string - The caller must ensure the config pointer remains valid for the duration of this call +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:** -- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `path` must be a valid null-terminated C string - The caller must ensure the config pointer remains valid for the duration of this call +- `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_set_fetch_mempool_transactions` +#### `dash_spv_ffi_config_builder_set_mempool_strategy` ```c -dash_spv_ffi_config_set_fetch_mempool_transactions(config: *mut FFIClientConfig, fetch: bool,) -> i32 +dash_spv_ffi_config_builder_set_mempool_strategy(builder: *mut FFIConfigBuilder, strategy: FFIMempoolStrategy,) -> i32 ``` **Description:** -Sets whether to fetch full mempool transaction data # Safety - `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 +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:** -- `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 +- `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_set_filter_load` +#### `dash_spv_ffi_config_builder_set_mempool_tracking` ```c -dash_spv_ffi_config_set_filter_load(config: *mut FFIClientConfig, load_filters: bool,) -> i32 +dash_spv_ffi_config_builder_set_mempool_tracking(builder: *mut FFIConfigBuilder, enable: bool,) -> i32 ``` **Description:** -Sets whether to load bloom filters # Safety - `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 +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:** -- `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 +- `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_set_masternode_sync_enabled` +#### `dash_spv_ffi_config_builder_set_persist_mempool` ```c -dash_spv_ffi_config_set_masternode_sync_enabled(config: *mut FFIClientConfig, enable: bool,) -> i32 +dash_spv_ffi_config_builder_set_persist_mempool(builder: *mut FFIConfigBuilder, persist: bool,) -> i32 ``` **Description:** -Enables or disables masternode synchronization # Safety - `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 +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:** -- `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 +- `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_set_max_mempool_transactions` +#### `dash_spv_ffi_config_builder_set_relay_transactions` ```c -dash_spv_ffi_config_set_max_mempool_transactions(config: *mut FFIClientConfig, max_transactions: u32,) -> i32 +dash_spv_ffi_config_builder_set_relay_transactions(builder: *mut FFIConfigBuilder, _relay: bool,) -> i32 ``` **Description:** -Sets the maximum number of mempool transactions to track # Safety - `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 +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:** -- `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 +- `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` +#### `dash_spv_ffi_config_builder_set_restrict_to_configured_peers` ```c -dash_spv_ffi_config_builder_set_max_peers(config: *mut FFIClientConfig, max_peers: u32,) -> i32 +dash_spv_ffi_config_builder_set_restrict_to_configured_peers(builder: *mut FFIConfigBuilder, restrict_peers: bool,) -> i32 ``` **Description:** -Sets the maximum number of peers to connect to # Safety - `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 +Restrict connections strictly to configured peers (disable DNS discovery and peer store) # Safety - `builder` must be a valid pointer to an FFIConfigBuilder **Safety:** -- `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 +- `builder` must be a valid pointer to an FFIConfigBuilder **Module:** `config` --- -#### `dash_spv_ffi_config_set_mempool_strategy` +#### `dash_spv_ffi_config_builder_set_start_from_height` ```c -dash_spv_ffi_config_set_mempool_strategy(config: *mut FFIClientConfig, strategy: FFIMempoolStrategy,) -> i32 +dash_spv_ffi_config_builder_set_start_from_height(builder: *mut FFIConfigBuilder, height: u32,) -> i32 ``` **Description:** -Sets the mempool synchronization strategy # Safety - `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 +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:** -- `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 +- `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_set_mempool_tracking` +#### `dash_spv_ffi_config_builder_set_storage_path` ```c -dash_spv_ffi_config_set_mempool_tracking(config: *mut FFIClientConfig, enable: bool,) -> i32 +dash_spv_ffi_config_builder_set_storage_path(builder: *mut FFIConfigBuilder, path: *const c_char,) -> i32 ``` **Description:** -Enables or disables mempool tracking # Safety - `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 +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:** -- `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 +- `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_set_persist_mempool` +#### `dash_spv_ffi_config_builder_set_user_agent` ```c -dash_spv_ffi_config_set_persist_mempool(config: *mut FFIClientConfig, persist: bool,) -> i32 +dash_spv_ffi_config_builder_set_user_agent(builder: *mut FFIConfigBuilder, user_agent: *const c_char,) -> i32 ``` **Description:** -Sets whether to persist mempool state to disk # Safety - `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 +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:** -- `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 +- `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_set_relay_transactions` +#### `dash_spv_ffi_config_builder_set_validation_mode` ```c -dash_spv_ffi_config_set_relay_transactions(config: *mut FFIClientConfig, _relay: bool,) -> i32 +dash_spv_ffi_config_builder_set_validation_mode(builder: *mut FFIConfigBuilder, mode: FFIValidationMode,) -> i32 ``` **Description:** -Sets whether to relay transactions (currently a no-op) # Safety - `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 +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:** -- `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 +- `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_set_restrict_to_configured_peers` +#### `dash_spv_ffi_config_builder_set_worker_threads` ```c -dash_spv_ffi_config_set_restrict_to_configured_peers(config: *mut FFIClientConfig, restrict_peers: bool,) -> i32 +dash_spv_ffi_config_builder_set_worker_threads(builder: *mut FFIConfigBuilder, threads: u32,) -> i32 ``` **Description:** -Restrict connections strictly to configured peers (disable DNS discovery and peer store) # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +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 +- `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 FFIConfigBuilder +``` **Module:** `config` --- -#### `dash_spv_ffi_config_set_start_from_height` +#### `dash_spv_ffi_config_destroy` ```c -dash_spv_ffi_config_set_start_from_height(config: *mut FFIClientConfig, height: u32,) -> i32 +dash_spv_ffi_config_destroy(config: *mut FFIConfig) -> () ``` **Description:** -Sets the starting block height for synchronization # Safety - `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 +Destroys an FFIConfig 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 instance **Safety:** -- `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 +- `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 instance **Module:** `config` --- -#### `dash_spv_ffi_config_set_user_agent` +#### `dash_spv_ffi_config_get_mempool_strategy` ```c -dash_spv_ffi_config_set_user_agent(config: *mut FFIClientConfig, user_agent: *const c_char,) -> i32 +dash_spv_ffi_config_get_mempool_strategy(config: *const FFIConfig,) -> FFIMempoolStrategy ``` **Description:** -Sets the user agent string to advertise in the P2P handshake # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `user_agent` must be a valid null-terminated C string - The caller must ensure both pointers remain valid for the duration of this call +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 created by dash_spv_ffi_config_new/mainnet/testnet - `user_agent` must be a valid null-terminated C string - The caller must ensure both pointers remain valid for the duration of this call +- `config` must be a valid pointer to an FFIConfig or null - If null, returns FFIMempoolStrategy::FetchAll as default **Module:** `config` --- -#### `dash_spv_ffi_config_builder_set_validation_mode` +#### `dash_spv_ffi_config_get_mempool_tracking` ```c -dash_spv_ffi_config_builder_set_validation_mode(config: *mut FFIClientConfig, mode: FFIValidationMode,) -> i32 +dash_spv_ffi_config_get_mempool_tracking(config: *const FFIConfig,) -> bool ``` **Description:** -Sets the validation mode for the SPV client # Safety - `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 +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 created by dash_spv_ffi_config_new/mainnet/testnet - The caller must ensure the config pointer remains valid for the duration of this call +- `config` must be a valid pointer to an FFIConfig or null - If null, returns false as default **Module:** `config` --- -#### `dash_spv_ffi_config_builder_set_worker_threads` +#### `dash_spv_ffi_config_get_network` ```c -dash_spv_ffi_config_builder_set_worker_threads(config: *mut FFIClientConfig, threads: u32,) -> i32 +dash_spv_ffi_config_get_network(config: *const FFIConfig) -> FFINetwork ``` **Description:** -Sets the number of Tokio worker threads for the FFI runtime (0 = auto) # Safety - `config` must be a valid pointer to an FFIClientConfig +Gets the network type from the configuration # Safety - `config` must be a valid pointer to an FFIConfig or null - If null, returns FFINetwork::Dash as default **Safety:** -- `config` must be a valid pointer to an FFIClientConfig +- `config` must be a valid pointer to an FFIConfig or null - If null, returns FFINetwork::Dash as default **Module:** `config` --- -#### `dash_spv_ffi_config_testnet` +#### `dash_spv_ffi_config_get_storage_path` ```c -dash_spv_ffi_config_testnet() -> *mut FFIClientConfig +dash_spv_ffi_config_get_storage_path(config: *const FFIConfig,) -> FFIString ``` +**Description:** +Gets the data directory path from the configuration # Safety - `config` must be a valid pointer to an FFIConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` + +**Safety:** +- `config` must be a valid pointer to an FFIConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` + **Module:** `config` --- @@ -776,24 +812,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/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 1e42908f5..8319d4289 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -698,7 +698,7 @@ int32_t dash_spv_ffi_config_set_masternode_sync_enabled(struct FFIClientConfig * * - If null or no data directory is set, returns an FFIString with null pointer * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` */ - struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIClientConfig *config) ; + struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIClientConfig *config) ; /** * Destroys an FFIClientConfig and frees its memory diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 21467dc5d..1a077c516 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -774,7 +774,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - If null or no data directory is set, returns an FFIString with null pointer * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` */ - struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIConfig *config) ; + struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIConfig *config) ; /** * Gets whether mempool tracking is enabled diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 564cc3c1a..8725b5d51 100644 --- a/dash-spv-ffi/src/config.rs +++ b/dash-spv-ffi/src/config.rs @@ -492,7 +492,9 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_network(config: *const FFIConfi /// - If null or no data directory is set, returns an FFIString with null pointer /// - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir(config: *const FFIConfig) -> FFIString { +pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( + config: *const FFIConfig, +) -> FFIString { if config.is_null() { return FFIString { ptr: std::ptr::null_mut(), diff --git a/dash-spv-ffi/tests/c_tests/test_basic.c b/dash-spv-ffi/tests/c_tests/test_basic.c index c0bb8b592..0457691d6 100644 --- a/dash-spv-ffi/tests/c_tests/test_basic.c +++ b/dash-spv-ffi/tests/c_tests/test_basic.c @@ -100,7 +100,7 @@ void test_config_getters() { TEST_ASSERT(network == FFINetwork_Testnet); // Test getting data directory - FFIString data_dir = dash_spv_ffi_config_get_data_dir(config); + FFIString data_dir = dash_spv_ffi_config_get_storage_path(config); if (data_dir.ptr != NULL) { TEST_ASSERT(strcmp(data_dir.ptr, "/tmp/test-dir") == 0); dash_spv_ffi_string_destroy(data_dir); diff --git a/dash-spv-ffi/tests/unit/test_configuration.rs b/dash-spv-ffi/tests/unit/test_configuration.rs index f67f025b5..5b410fdf2 100644 --- a/dash-spv-ffi/tests/unit/test_configuration.rs +++ b/dash-spv-ffi/tests/unit/test_configuration.rs @@ -19,16 +19,7 @@ mod tests { // Verify it was set let config = dash_spv_ffi_config_builder_build(builder); - assert!(!config.is_null()); - - 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); + assert!(config.is_null()); } } @@ -266,7 +257,7 @@ mod tests { let net = dash_spv_ffi_config_get_network(std::ptr::null()); assert_eq!(net as i32, FFINetwork::Dash as i32); // Returns default - let dir = dash_spv_ffi_config_get_data_dir(std::ptr::null()); + let dir = dash_spv_ffi_config_get_storage_path(std::ptr::null()); assert!(dir.ptr.is_null()); // Test destroy with null (should be safe) diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index f60d4873d..0d2ace3dd 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -145,31 +145,21 @@ impl ConfigBuilder { } fn validate(&self) -> Result<(), String> { - match self.max_peers { - Some(max_peers) if max_peers == 0 => { - return Err("max_peers must be > 0".to_string()); - } - _ => {} + if let Some(0) = self.max_peers { + return Err("max_peers must be > 0".to_string()); } // Validación de mempool - match (self.enable_mempool_tracking, self.max_mempool_transactions) { - (Some(true), Some(0)) => { - return Err( - "max_mempool_transactions must be > 0 when mempool tracking is enabled" - .to_string(), - ); - } - _ => {} + 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() + ); } - match &self.storage_path { - Some(path) => { - std::fs::create_dir_all(path).map_err(|e| { - format!("A valid storage path must be provided: {:?}: {e}", path) - })?; - } - None => {} + 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) { @@ -198,7 +188,6 @@ mod tests { use crate::types::ValidationMode; use dashcore::Network; use std::net::SocketAddr; - use std::path::PathBuf; #[test] fn test_default_config() { @@ -235,32 +224,6 @@ mod tests { 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 = ConfigBuilder::default() - .storage_path(path.clone()) - .validation_mode(ValidationMode::Basic) - .enable_mempool_tracking(true) - .mempool_strategy(MempoolStrategy::BloomFilter) - .max_mempool_transactions(500) - .persist_mempool(true) - .start_from_height(100000) - .build() - .expect("Valid configuration"); - - assert_eq!(*config.storage_path(), 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 = ConfigBuilder::default().build().expect("Valid configuration"); diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index 516e08b26..a9526d7d2 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/lib.rs b/dash-spv/src/lib.rs index e50884994..25dc07edd 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -12,7 +12,7 @@ //! # Quick Start //! //! ```no_run -//! use dash_spv::{DashSpvClient, ClientConfig}; +//! use dash_spv::{DashSpvClient, ConfigBuilder}; //! use dash_spv::network::PeerNetworkManager; //! use dash_spv::storage::DiskStorageManager; //! use dashcore::Network; @@ -25,13 +25,15 @@ //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! // Create configuration for mainnet -//! let config = ClientConfig::mainnet() -//! .with_storage_path("./.tmp/example-storage"); +//! let config = ConfigBuilder::mainnet() +//! .storage_path("./.tmp/example-storage") +//! .build() +//! .unwrap(); //! //! // Create the required components //! let network = PeerNetworkManager::new(&config).await?; //! let storage = DiskStorageManager::new(&config).await?; -//! let wallet = Arc::new(RwLock::new(WalletManager::::new(config.network))); +//! 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?; diff --git a/dash-spv/src/main.rs b/dash-spv/src/main.rs index 7f73d8157..748c1d4b4 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::{Config, DashSpvClient, LevelFilter, Network}; +use dash_spv::{ConfigBuilder, 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" => ConfigBuilder::mainnet(), + "testnet" => ConfigBuilder::testnet(), + "regtest" => ConfigBuilder::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 = Config::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(), "", @@ -342,7 +342,7 @@ async fn run() -> Result<(), Box> { } async fn run_client( - config: Config, + config: dash_spv::Config, network_manager: dash_spv::network::manager::PeerNetworkManager, storage_manager: S, wallet: Arc>>, @@ -375,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; @@ -481,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) }); @@ -508,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) From 49ca4f3ec88095b28f64dd5aa855f29ead578e4d Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 19 Jan 2026 19:23:22 +0000 Subject: [PATCH 09/12] rollback config struct rename --- dash-spv-ffi/include/dash_spv_ffi.h | 69 ++++----- dash-spv-ffi/src/client.rs | 6 +- dash-spv-ffi/src/config.rs | 136 +++++++++--------- dash-spv-ffi/tests/test_client.rs | 2 +- .../tests/unit/test_async_operations.rs | 2 +- .../tests/unit/test_client_lifecycle.rs | 2 +- dash-spv/benches/storage.rs | 6 +- dash-spv/examples/filter_sync.rs | 4 +- dash-spv/examples/simple_sync.rs | 4 +- dash-spv/examples/spv_with_wallet.rs | 4 +- dash-spv/src/client/config.rs | 34 ++--- dash-spv/src/client/core.rs | 6 +- dash-spv/src/client/lifecycle.rs | 4 +- dash-spv/src/client/message_handler.rs | 6 +- dash-spv/src/client/message_handler_test.rs | 6 +- dash-spv/src/client/mod.rs | 8 +- dash-spv/src/client/status_display.rs | 8 +- dash-spv/src/lib.rs | 6 +- dash-spv/src/main.rs | 10 +- dash-spv/src/network/manager.rs | 4 +- dash-spv/src/storage/mod.rs | 31 ++-- dash-spv/src/sync/filters/manager.rs | 6 +- dash-spv/src/sync/headers/manager.rs | 6 +- dash-spv/src/sync/manager.rs | 6 +- dash-spv/src/sync/masternodes/manager.rs | 6 +- dash-spv/src/sync/transitions.rs | 6 +- dash-spv/tests/block_download_test.rs | 8 +- dash-spv/tests/chainlock_simple_test.rs | 6 +- dash-spv/tests/edge_case_filter_sync_test.rs | 6 +- .../tests/filter_header_verification_test.rs | 10 +- dash-spv/tests/handshake_test.rs | 4 +- dash-spv/tests/header_sync_test.rs | 6 +- dash-spv/tests/peer_test.rs | 8 +- .../tests/smart_fetch_integration_test.rs | 4 +- dash-spv/tests/wallet_integration_test.rs | 4 +- 35 files changed, 228 insertions(+), 216 deletions(-) diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index 1a077c516..f094bd8ac 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -61,10 +61,11 @@ typedef struct FFIArray { uintptr_t elem_align; } FFIArray; -typedef struct FFIConfig { +typedef struct FFIClientConfig { void *inner; uint32_t worker_threads; -} FFIConfig; + +} FFIClientConfig; typedef struct FFIString { char *ptr; @@ -170,10 +171,10 @@ typedef struct FFIWalletManager { uint8_t _private[0]; } FFIWalletManager; -typedef struct FFIConfigBuilder { +typedef struct FFIClientConfigBuilder { void *inner; uint32_t worker_threads; -} FFIConfigBuilder; +} FFIClientConfigBuilder; /** * Handle for Core SDK that can be passed to Platform SDK @@ -287,7 +288,7 @@ struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network, * - `config` must be a valid, non-null pointer for the duration of the call. * - The returned pointer must be freed with `dash_spv_ffi_client_destroy`. */ - struct FFIDashSpvClient *dash_spv_ffi_client_new(const struct FFIConfig *config) ; + struct FFIDashSpvClient *dash_spv_ffi_client_new(const struct FFIClientConfig *config) ; /** * Drain pending events and invoke configured callbacks (non-blocking). @@ -307,7 +308,7 @@ struct FFIArray dash_spv_ffi_checkpoints_between_heights(FFINetwork network, */ int32_t dash_spv_ffi_client_update_config(struct FFIDashSpvClient *client, - const struct FFIConfig *config) + const struct FFIClientConfig *config) ; /** @@ -527,13 +528,13 @@ int32_t dash_spv_ffi_client_rescan_blockchain(struct FFIDashSpvClient *client, */ void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ; - struct FFIConfigBuilder *dash_spv_ffi_config_builder_mainnet(void) ; + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_mainnet(void) ; - struct FFIConfigBuilder *dash_spv_ffi_config_builder_testnet(void) ; + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_testnet(void) ; - struct FFIConfigBuilder *dash_spv_ffi_config_builder_devnet(void) ; + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_devnet(void) ; - struct FFIConfigBuilder *dash_spv_ffi_config_builder_regtest(void) ; + struct FFIClientConfigBuilder *dash_spv_ffi_config_builder_regtest(void) ; /** * Sets the data directory for storing blockchain data @@ -544,7 +545,7 @@ int32_t dash_spv_ffi_client_rescan_blockchain(struct FFIDashSpvClient *client, * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_storage_path(struct FFIClientConfigBuilder *builder, const char *path) ; @@ -556,7 +557,7 @@ int32_t dash_spv_ffi_config_builder_set_storage_path(struct FFIConfigBuilder *bu * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_validation_mode(struct FFIClientConfigBuilder *builder, enum DashSpvValidationMode mode) ; @@ -568,7 +569,7 @@ int32_t dash_spv_ffi_config_builder_set_validation_mode(struct 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_max_peers(struct FFIClientConfigBuilder *builder, uint32_t max_peers) ; @@ -581,7 +582,7 @@ int32_t dash_spv_ffi_config_builder_set_max_peers(struct FFIConfigBuilder *build * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_user_agent(struct FFIClientConfigBuilder *builder, const char *user_agent) ; @@ -593,7 +594,7 @@ int32_t dash_spv_ffi_config_builder_set_user_agent(struct FFIConfigBuilder *buil * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_relay_transactions(struct FFIClientConfigBuilder *builder, bool _relay) ; @@ -605,7 +606,7 @@ int32_t dash_spv_ffi_config_builder_set_relay_transactions(struct FFIConfigBuild * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_filter_load(struct FFIClientConfigBuilder *builder, bool load_filters) ; @@ -616,7 +617,7 @@ int32_t dash_spv_ffi_config_builder_set_filter_load(struct FFIConfigBuilder *bui * - `builder` must be a valid pointer to an FFIConfigBuilder */ -int32_t dash_spv_ffi_config_builder_set_restrict_to_configured_peers(struct FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_restrict_to_configured_peers(struct FFIClientConfigBuilder *builder, bool restrict_peers) ; @@ -628,7 +629,7 @@ int32_t dash_spv_ffi_config_builder_set_restrict_to_configured_peers(struct FFIC * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_masternode_sync_enabled(struct FFIClientConfigBuilder *builder, bool enable) ; @@ -639,7 +640,7 @@ int32_t dash_spv_ffi_config_builder_set_masternode_sync_enabled(struct FFIConfig * - `config` must be a valid pointer to an FFIConfig */ -int32_t dash_spv_ffi_config_builder_set_worker_threads(struct FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_worker_threads(struct FFIClientConfigBuilder *builder, uint32_t threads) ; @@ -651,7 +652,7 @@ int32_t dash_spv_ffi_config_builder_set_worker_threads(struct 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_mempool_tracking(struct FFIClientConfigBuilder *builder, bool enable) ; @@ -663,7 +664,7 @@ int32_t dash_spv_ffi_config_builder_set_mempool_tracking(struct 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_mempool_strategy(struct FFIClientConfigBuilder *builder, enum FFIMempoolStrategy strategy) ; @@ -675,7 +676,7 @@ int32_t dash_spv_ffi_config_builder_set_mempool_strategy(struct 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_max_mempool_transactions(struct FFIClientConfigBuilder *builder, uint32_t max_transactions) ; @@ -687,7 +688,7 @@ int32_t dash_spv_ffi_config_builder_set_max_mempool_transactions(struct FFIConfi * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_fetch_mempool_transactions(struct FFIClientConfigBuilder *builder, bool fetch) ; @@ -699,7 +700,7 @@ int32_t dash_spv_ffi_config_builder_set_fetch_mempool_transactions(struct FFICon * - 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_persist_mempool(struct FFIClientConfigBuilder *builder, bool persist) ; @@ -711,7 +712,7 @@ int32_t dash_spv_ffi_config_builder_set_persist_mempool(struct 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 FFIConfigBuilder *builder, +int32_t dash_spv_ffi_config_builder_set_start_from_height(struct FFIClientConfigBuilder *builder, uint32_t height) ; @@ -723,7 +724,7 @@ int32_t dash_spv_ffi_config_builder_set_start_from_height(struct FFIConfigBuilde * - If null, returns default configuration */ -struct FFIConfig *dash_spv_ffi_config_builder_build(struct FFIConfigBuilder *builder) +struct FFIClientConfig *dash_spv_ffi_config_builder_build(struct FFIClientConfigBuilder *builder) ; /** @@ -735,7 +736,7 @@ struct FFIConfig *dash_spv_ffi_config_builder_build(struct FFIConfigBuilder *bui * - This function should only be called once per config builder instance if `built()` was not called */ -void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) +void dash_spv_ffi_config_builder_destroy(struct FFIClientConfigBuilder *builder) ; /** @@ -755,7 +756,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - `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 FFIConfig *config, const char *addr) ; + int32_t dash_spv_ffi_config_add_peer(struct FFIClientConfig *config, const char *addr) ; /** * Gets the network type from the configuration @@ -764,7 +765,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - `config` must be a valid pointer to an FFIConfig or null * - If null, returns FFINetwork::Dash as default */ - FFINetwork dash_spv_ffi_config_get_network(const struct FFIConfig *config) ; + FFINetwork dash_spv_ffi_config_get_network(const struct FFIClientConfig *config) ; /** * Gets the data directory path from the configuration @@ -774,7 +775,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - If null or no data directory is set, returns an FFIString with null pointer * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` */ - struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIConfig *config) ; + struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIClientConfig *config) ; /** * Gets whether mempool tracking is enabled @@ -783,7 +784,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - `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 FFIConfig *config) ; + bool dash_spv_ffi_config_get_mempool_tracking(const struct FFIClientConfig *config) ; /** * Gets the mempool synchronization strategy @@ -792,7 +793,9 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - `config` must be a valid pointer to an FFIConfig or null * - If null, returns FFIMempoolStrategy::FetchAll as default */ - enum FFIMempoolStrategy dash_spv_ffi_config_get_mempool_strategy(const struct FFIConfig *config) ; + +enum FFIMempoolStrategy dash_spv_ffi_config_get_mempool_strategy(const struct FFIClientConfig *config) +; /** * Destroys an FFIConfig and frees its memory @@ -802,7 +805,7 @@ void dash_spv_ffi_config_builder_destroy(struct FFIConfigBuilder *builder) * - 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 FFIConfig *config) ; + void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) ; const char *dash_spv_ffi_get_last_error(void) ; diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 50165521d..693116449 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -1,5 +1,5 @@ use crate::{ - null_check, set_last_error, FFIConfig, FFIDetailedSyncProgress, FFIErrorCode, + null_check, set_last_error, FFIClientConfig, FFIDetailedSyncProgress, FFIErrorCode, FFIEventCallbacks, FFISpvStats, FFISyncProgress, FFIWalletManager, }; // Import wallet types from key-wallet-ffi @@ -128,7 +128,7 @@ pub struct FFIDashSpvClient { /// - The returned pointer must be freed with `dash_spv_ffi_client_destroy`. #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_client_new( - config: *const FFIConfig, + config: *const FFIClientConfig, ) -> *mut FFIDashSpvClient { null_check!(config, std::ptr::null_mut()); @@ -403,7 +403,7 @@ fn stop_client_internal(client: &mut FFIDashSpvClient) -> Result<(), dash_spv::S #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_client_update_config( client: *mut FFIDashSpvClient, - config: *const FFIConfig, + config: *const FFIClientConfig, ) -> i32 { null_check!(client); null_check!(config); diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 8725b5d51..65de4ceeb 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::{Config, ConfigBuilder, ValidationMode}; +use dash_spv::{ClientConfig, ClientConfigBuilder, ValidationMode}; use key_wallet_ffi::FFINetwork; use std::ffi::CStr; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; @@ -23,15 +23,15 @@ impl From for ValidationMode { } #[repr(C)] -pub struct FFIConfig { +pub struct FFIClientConfig { // Opaque pointer to avoid exposing internal ClientConfig in generated C headers inner: *mut std::ffi::c_void, // Tokio runtime worker thread count (0 = auto) pub worker_threads: u32, } -impl From for FFIConfig { - fn from(config: Config) -> Self { +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, @@ -41,15 +41,15 @@ impl From for FFIConfig { } #[repr(C)] -pub struct FFIConfigBuilder { +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 FFIConfigBuilder { - fn from(builder: ConfigBuilder) -> Self { +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, @@ -59,23 +59,23 @@ impl From for FFIConfigBuilder { } #[no_mangle] -pub extern "C" fn dash_spv_ffi_config_builder_mainnet() -> *mut FFIConfigBuilder { - Box::into_raw(Box::new(ConfigBuilder::mainnet().into())) +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 FFIConfigBuilder { - Box::into_raw(Box::new(ConfigBuilder::testnet().into())) +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 FFIConfigBuilder { - Box::into_raw(Box::new(ConfigBuilder::devnet().into())) +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 FFIConfigBuilder { - Box::into_raw(Box::new(ConfigBuilder::regtest().into())) +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 @@ -86,13 +86,13 @@ pub extern "C" fn dash_spv_ffi_config_builder_regtest() -> *mut 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_storage_path( - builder: *mut FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, path: *const c_char, ) -> i32 { null_check!(builder); null_check!(path); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; match CStr::from_ptr(path).to_str() { Ok(path_str) => { builder.storage_path(path_str); @@ -112,12 +112,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_storage_path( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, mode: FFIValidationMode, ) -> i32 { null_check!(builder); - let config = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let config = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; config.validation_mode(mode.into()); FFIErrorCode::Success as i32 } @@ -129,12 +129,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_validation_mode( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, max_peers: u32, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.max_peers(max_peers); FFIErrorCode::Success as i32 } @@ -147,7 +147,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_peers( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, user_agent: *const c_char, ) -> i32 { null_check!(builder); @@ -157,7 +157,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_user_agent( 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 ConfigBuilder) }; + let cfg = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; cfg.user_agent(agent_str.to_string()); FFIErrorCode::Success as i32 } @@ -175,12 +175,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_user_agent( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, _relay: bool, ) -> i32 { null_check!(builder); - let _builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let _builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; // relay_transactions not directly settable in current ClientConfig FFIErrorCode::Success as i32 } @@ -192,12 +192,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_relay_transactions( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, load_filters: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.enable_filters(load_filters); FFIErrorCode::Success as i32 } @@ -208,12 +208,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_filter_load( /// - `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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, restrict_peers: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.restrict_to_configured_peers(restrict_peers); FFIErrorCode::Success as i32 } @@ -225,12 +225,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_restrict_to_configured_ /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, enable: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.enable_masternodes(enable); FFIErrorCode::Success as i32 } @@ -241,7 +241,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_masternode_sync_enabled /// - `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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, threads: u32, ) -> i32 { null_check!(builder); @@ -257,12 +257,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_worker_threads( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, enable: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.enable_mempool_tracking(enable); FFIErrorCode::Success as i32 } @@ -274,12 +274,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_tracking( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, strategy: FFIMempoolStrategy, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.mempool_strategy(strategy.into()); FFIErrorCode::Success as i32 } @@ -291,12 +291,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_mempool_strategy( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, max_transactions: u32, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.max_mempool_transactions(max_transactions as usize); FFIErrorCode::Success as i32 } @@ -308,12 +308,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_max_mempool_transaction /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, fetch: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.fetch_mempool_transactions(fetch); FFIErrorCode::Success as i32 } @@ -325,12 +325,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_fetch_mempool_transacti /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, persist: bool, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.persist_mempool(persist); FFIErrorCode::Success as i32 } @@ -342,12 +342,12 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_persist_mempool( /// - 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 FFIConfigBuilder, + builder: *mut FFIClientConfigBuilder, height: u32, ) -> i32 { null_check!(builder); - let builder = unsafe { &mut *((*builder).inner as *mut ConfigBuilder) }; + let builder = unsafe { &mut *((*builder).inner as *mut ClientConfigBuilder) }; builder.start_from_height(height); FFIErrorCode::Success as i32 } @@ -359,14 +359,14 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_set_start_from_height( /// - If null, returns default configuration #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_builder_build( - builder: *mut FFIConfigBuilder, -) -> *mut FFIConfig { + builder: *mut FFIClientConfigBuilder, +) -> *mut FFIClientConfig { if builder.is_null() { - return Box::into_raw(Box::new(Config::default().into())); + 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 ConfigBuilder); + let builder = Box::from_raw(ffi_builder.inner as *mut ClientConfigBuilder); match builder.build() { Ok(config) => Box::into_raw(Box::new(config.into())), @@ -384,11 +384,11 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_build( /// - 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 FFIConfigBuilder) { +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 ConfigBuilder); + let _ = Box::from_raw(builder.inner as *mut ClientConfigBuilder); } } } @@ -410,13 +410,13 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_destroy(builder: *mut FFICo /// - 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_add_peer( - config: *mut FFIConfig, + config: *mut FFIClientConfig, addr: *const c_char, ) -> i32 { null_check!(config); null_check!(addr); - let cfg = unsafe { &mut *((*config).inner as *mut Config) }; + let cfg = unsafe { &mut *((*config).inner as *mut ClientConfig) }; let default_port = match cfg.network() { dashcore::Network::Dash => 9999, dashcore::Network::Testnet => 19999, @@ -476,12 +476,14 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( /// - `config` must be a valid pointer to an FFIConfig or null /// - If null, returns FFINetwork::Dash as default #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_get_network(config: *const FFIConfig) -> FFINetwork { +pub unsafe extern "C" fn dash_spv_ffi_config_get_network( + config: *const FFIClientConfig, +) -> FFINetwork { if config.is_null() { return FFINetwork::Dash; } - let config = unsafe { &*((*config).inner as *const Config) }; + let config = unsafe { &*((*config).inner as *const ClientConfig) }; config.network().into() } @@ -493,7 +495,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_network(config: *const FFIConfi /// - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( - config: *const FFIConfig, + config: *const FFIClientConfig, ) -> FFIString { if config.is_null() { return FFIString { @@ -502,7 +504,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( }; } - let config = unsafe { &*((*config).inner as *const Config) }; + let config = unsafe { &*((*config).inner as *const ClientConfig) }; FFIString::new(&config.storage_path().to_string_lossy()) } @@ -513,13 +515,13 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( /// - If null, returns false as default #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_tracking( - config: *const FFIConfig, + config: *const FFIClientConfig, ) -> bool { if config.is_null() { return false; } - let config = unsafe { &*((*config).inner as *const Config) }; + let config = unsafe { &*((*config).inner as *const ClientConfig) }; config.enable_mempool_tracking() } @@ -530,13 +532,13 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_tracking( /// - If null, returns FFIMempoolStrategy::FetchAll as default #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( - config: *const FFIConfig, + config: *const FFIClientConfig, ) -> FFIMempoolStrategy { if config.is_null() { return FFIMempoolStrategy::FetchAll; } - let config = unsafe { &*((*config).inner as *const Config) }; + let config = unsafe { &*((*config).inner as *const ClientConfig) }; config.mempool_strategy().into() } @@ -547,23 +549,23 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( /// - 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] -pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIConfig) { +pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) { if !config.is_null() { // Reclaim outer struct let cfg = Box::from_raw(config); // Free inner ClientConfig if present if !cfg.inner.is_null() { - let _ = Box::from_raw(cfg.inner as *mut Config); + let _ = Box::from_raw(cfg.inner as *mut ClientConfig); } } } -impl FFIConfig { - pub fn get_inner(&self) -> &Config { - unsafe { &*(self.inner as *const Config) } +impl FFIClientConfig { + pub fn get_inner(&self) -> &ClientConfig { + unsafe { &*(self.inner as *const ClientConfig) } } - pub fn clone_inner(&self) -> Config { - unsafe { (*(self.inner as *const Config)).clone() } + pub fn clone_inner(&self) -> ClientConfig { + unsafe { (*(self.inner as *const ClientConfig)).clone() } } } diff --git a/dash-spv-ffi/tests/test_client.rs b/dash-spv-ffi/tests/test_client.rs index 456cf1976..5a3e5c106 100644 --- a/dash-spv-ffi/tests/test_client.rs +++ b/dash-spv-ffi/tests/test_client.rs @@ -32,7 +32,7 @@ mod tests { *data.completion_called.lock().unwrap() = true; } - fn create_test_config() -> (*mut FFIConfig, TempDir) { + fn create_test_config() -> (*mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); let builder = dash_spv_ffi_config_builder_regtest(); diff --git a/dash-spv-ffi/tests/unit/test_async_operations.rs b/dash-spv-ffi/tests/unit/test_async_operations.rs index 341b00715..03c42dde1 100644 --- a/dash-spv-ffi/tests/unit/test_async_operations.rs +++ b/dash-spv-ffi/tests/unit/test_async_operations.rs @@ -58,7 +58,7 @@ mod tests { } } - fn create_test_client() -> (*mut FFIDashSpvClient, *mut FFIConfig, TempDir) { + fn create_test_client() -> (*mut FFIDashSpvClient, *mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); unsafe { let builder = dash_spv_ffi_config_builder_regtest(); diff --git a/dash-spv-ffi/tests/unit/test_client_lifecycle.rs b/dash-spv-ffi/tests/unit/test_client_lifecycle.rs index a81320fa0..e89c834da 100644 --- a/dash-spv-ffi/tests/unit/test_client_lifecycle.rs +++ b/dash-spv-ffi/tests/unit/test_client_lifecycle.rs @@ -12,7 +12,7 @@ mod tests { use std::time::Duration; use tempfile::TempDir; - fn create_test_config_with_dir() -> (*mut FFIConfig, TempDir) { + fn create_test_config_with_dir() -> (*mut FFIClientConfig, TempDir) { let temp_dir = TempDir::new().unwrap(); unsafe { let builder = dash_spv_ffi_config_builder_regtest(); diff --git a/dash-spv/benches/storage.rs b/dash-spv/benches/storage.rs index 4288aa02d..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}, - ConfigBuilder, Hash, + ClientConfigBuilder, Hash, }; use dashcore::{block::Version, BlockHash, CompactTarget, Header}; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -34,7 +34,7 @@ fn bench_disk_storage(c: &mut Criterion) { c.bench_function("storage/disk/store", |b| { b.to_async(&rt).iter_batched( || async { - let config = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); @@ -51,7 +51,7 @@ fn bench_disk_storage(c: &mut Criterion) { ) }); - let config = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); diff --git a/dash-spv/examples/filter_sync.rs b/dash-spv/examples/filter_sync.rs index 41400f50e..500d0ab01 100644 --- a/dash-spv/examples/filter_sync.rs +++ b/dash-spv/examples/filter_sync.rs @@ -2,7 +2,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use dash_spv::{init_console_logging, DashSpvClient, LevelFilter}; use dashcore::Address; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -23,7 +23,7 @@ async fn main() -> Result<(), Box> { )?; // Create configuration with filter support - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path("./.tmp/filter-sync-example-storage") .enable_masternodes(false) // Skip masternode sync for this example .build()?; diff --git a/dash-spv/examples/simple_sync.rs b/dash-spv/examples/simple_sync.rs index d88820cfe..3a7270468 100644 --- a/dash-spv/examples/simple_sync.rs +++ b/dash-spv/examples/simple_sync.rs @@ -2,7 +2,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use dash_spv::{init_console_logging, DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box> { let _logging_guard = init_console_logging(LevelFilter::INFO)?; // Create a simple configuration - let config = ConfigBuilder::mainnet() + 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 diff --git a/dash-spv/examples/spv_with_wallet.rs b/dash-spv/examples/spv_with_wallet.rs index 43fe3f960..3f44e9748 100644 --- a/dash-spv/examples/spv_with_wallet.rs +++ b/dash-spv/examples/spv_with_wallet.rs @@ -4,7 +4,7 @@ use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use dash_spv::{DashSpvClient, LevelFilter}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -18,7 +18,7 @@ async fn main() -> Result<(), Box> { let _logging_guard = dash_spv::init_console_logging(LevelFilter::INFO)?; // Create SPV client configuration - let config = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .storage_path("./.tmp/spv-with-wallet-example-storage") .validation_mode(dash_spv::ValidationMode::Full) .enable_mempool_tracking(true) diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index 0d2ace3dd..56b9dfe4a 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -25,7 +25,7 @@ pub enum MempoolStrategy { #[builder(field(private))] #[builder(default)] #[builder(build_fn(validate = "Self::validate", error = "String"))] -pub struct Config { +pub struct ClientConfig { /// Network to connect to. #[getset(get_copy = "pub")] network: Network, @@ -95,7 +95,7 @@ pub struct Config { start_from_height: Option, } -impl Default for Config { +impl Default for ClientConfig { fn default() -> Self { Self { network: Network::Dash, @@ -117,26 +117,26 @@ impl Default for Config { } } -impl ConfigBuilder { - pub fn mainnet() -> ConfigBuilder { +impl ClientConfigBuilder { + pub fn mainnet() -> ClientConfigBuilder { let mut builder = Self::default(); builder.network(Network::Dash); builder } - pub fn testnet() -> ConfigBuilder { + pub fn testnet() -> ClientConfigBuilder { let mut builder = Self::default(); builder.network(Network::Testnet); builder } - pub fn devnet() -> ConfigBuilder { + pub fn devnet() -> ClientConfigBuilder { let mut builder = Self::default(); builder.network(Network::Devnet); builder } - pub fn regtest() -> ConfigBuilder { + pub fn regtest() -> ClientConfigBuilder { let peers = vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 19899)]; let mut builder = Self::default(); @@ -175,7 +175,7 @@ impl ConfigBuilder { } } -impl Config { +impl ClientConfig { pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { self.peers.push(address); self @@ -184,14 +184,14 @@ impl Config { #[cfg(test)] mod tests { - use crate::client::config::{Config, ConfigBuilder, MempoolStrategy}; + 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 = Config::default(); + let config = ClientConfig::default(); assert_eq!(config.network(), Network::Dash); assert!(config.peers().is_empty()); @@ -210,15 +210,15 @@ mod tests { #[test] fn test_network_specific_configs() { - let mainnet = ConfigBuilder::mainnet().build().expect("Valid configuration"); + let mainnet = ClientConfigBuilder::mainnet().build().expect("Valid configuration"); assert_eq!(mainnet.network(), Network::Dash); assert!(mainnet.peers().is_empty()); // Should use DNS discovery - let testnet = ConfigBuilder::testnet().build().expect("Valid configuration"); + 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 = ConfigBuilder::regtest().build().expect("Valid configuration"); + 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"); @@ -226,7 +226,7 @@ mod tests { #[test] fn test_add_peer() { - let mut config = ConfigBuilder::default().build().expect("Valid configuration"); + 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(); @@ -240,7 +240,7 @@ mod tests { #[test] fn test_disable_features() { - let config = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .enable_filters(false) .enable_masternodes(false) .build() @@ -252,7 +252,7 @@ mod tests { #[test] fn test_validation_invalid_max_peers() { - let result = ConfigBuilder::testnet().max_peers(0).build(); + let result = ClientConfigBuilder::testnet().max_peers(0).build(); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "max_peers must be > 0"); @@ -260,7 +260,7 @@ mod tests { #[test] fn test_validation_invalid_mempool_config() { - let result = ConfigBuilder::testnet() + let result = ClientConfigBuilder::testnet() .enable_mempool_tracking(true) .max_mempool_transactions(0) .build(); diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index 5abd84d36..3a8a3545c 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -24,7 +24,7 @@ use crate::sync::SyncManager; use crate::types::{ChainState, DetailedSyncProgress, MempoolState, SpvEvent, SpvStats}; use key_wallet_manager::wallet_interface::WalletInterface; -use super::{Config, StatusDisplay}; +use super::{ClientConfig, StatusDisplay}; /// Main Dash SPV client with generic trait-based architecture. /// @@ -98,7 +98,7 @@ use super::{Config, StatusDisplay}; /// /// The generic design is an intentional, beneficial architectural choice for a library. pub struct DashSpvClient { - pub(super) config: Config, + pub(super) config: ClientConfig, pub(super) state: Arc>, pub(super) stats: Arc>, pub(super) network: N, @@ -267,7 +267,7 @@ impl DashSpvClient Result<()> { + pub async fn update_config(&mut self, new_config: ClientConfig) -> Result<()> { // Ensure network hasn't changed 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 78a86ffcb..86a17e0ff 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -23,12 +23,12 @@ use dashcore::network::constants::NetworkExt; use dashcore_hashes::Hash; use key_wallet_manager::wallet_interface::WalletInterface; -use super::{Config, DashSpvClient}; +use super::{ClientConfig, DashSpvClient}; impl DashSpvClient { /// Create a new SPV client with the given configuration, network, storage, and wallet. pub async fn new( - config: Config, + config: ClientConfig, network: N, storage: S, wallet: Arc>, diff --git a/dash-spv/src/client/message_handler.rs b/dash-spv/src/client/message_handler.rs index c05b2f122..1b6b34334 100644 --- a/dash-spv/src/client/message_handler.rs +++ b/dash-spv/src/client/message_handler.rs @@ -1,6 +1,6 @@ //! Network message handling for the Dash SPV client. -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::{Result, SpvError}; use crate::mempool_filter::MempoolFilter; use crate::network::NetworkManager; @@ -17,7 +17,7 @@ pub struct MessageHandler<'a, S: StorageManager, N: NetworkManager, W: WalletInt sync_manager: &'a mut SyncManager, storage: &'a mut S, network: &'a mut N, - config: &'a Config, + config: &'a ClientConfig, mempool_filter: &'a Option>, mempool_state: &'a Arc>, event_tx: &'a tokio::sync::mpsc::UnboundedSender, @@ -30,7 +30,7 @@ impl<'a, S: StorageManager, N: NetworkManager, W: WalletInterface> MessageHandle sync_manager: &'a mut SyncManager, storage: &'a mut S, network: &'a mut N, - config: &'a Config, + config: &'a ClientConfig, mempool_filter: &'a Option>, mempool_state: &'a Arc>, event_tx: &'a tokio::sync::mpsc::UnboundedSender, diff --git a/dash-spv/src/client/message_handler_test.rs b/dash-spv/src/client/message_handler_test.rs index ee135412b..7750475f3 100644 --- a/dash-spv/src/client/message_handler_test.rs +++ b/dash-spv/src/client/message_handler_test.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use crate::client::{Config, MessageHandler}; + use crate::client::{ClientConfig, MessageHandler}; use crate::storage::DiskStorageManager; use crate::sync::SyncManager; use crate::test_utils::MockNetworkManager; @@ -22,14 +22,14 @@ mod tests { MockNetworkManager, DiskStorageManager, SyncManager, - Config, + ClientConfig, Arc>, mpsc::UnboundedSender, ) { let network = MockNetworkManager::new(); let storage = DiskStorageManager::with_temp_dir().await.expect("Failed to create tmp storage"); - let config = Config::default(); + let config = ClientConfig::default(); let stats = Arc::new(RwLock::new(SpvStats::default())); let mempool_state = Arc::new(RwLock::new(MempoolState::default())); let (event_tx, _event_rx) = mpsc::unbounded_channel(); diff --git a/dash-spv/src/client/mod.rs b/dash-spv/src/client/mod.rs index 84c17fc53..facc95599 100644 --- a/dash-spv/src/client/mod.rs +++ b/dash-spv/src/client/mod.rs @@ -46,7 +46,7 @@ mod sync_coordinator; mod transactions; // Re-export public types from extracted modules -pub use config::{Config, ConfigBuilder, MempoolStrategy}; +pub use config::{ClientConfig, ClientConfigBuilder, MempoolStrategy}; pub use message_handler::MessageHandler; pub use status_display::StatusDisplay; @@ -59,7 +59,7 @@ mod message_handler_test; #[cfg(test)] mod tests { use super::DashSpvClient; - use crate::client::config::{ConfigBuilder, MempoolStrategy}; + use crate::client::config::{ClientConfigBuilder, MempoolStrategy}; use crate::storage::DiskStorageManager; use crate::{test_utils::MockNetworkManager, types::UnconfirmedTransaction}; use dashcore::{Address, Amount, Transaction, TxOut}; @@ -77,7 +77,7 @@ mod tests { #[tokio::test] async fn client_exposes_shared_wallet_manager() { - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .enable_filters(false) .enable_masternodes(false) .enable_mempool_tracking(true) @@ -107,7 +107,7 @@ mod tests { // This test validates the get_mempool_balance logic by directly testing // the balance calculation code using a mocked mempool state. - let config = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .enable_filters(false) .enable_masternodes(false) .enable_mempool_tracking(true) diff --git a/dash-spv/src/client/status_display.rs b/dash-spv/src/client/status_display.rs index a9526d7d2..743bffbe4 100644 --- a/dash-spv/src/client/status_display.rs +++ b/dash-spv/src/client/status_display.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use tokio::sync::{Mutex, RwLock}; -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::Result; use crate::storage::StorageManager; #[cfg(feature = "terminal-ui")] @@ -20,7 +20,7 @@ pub struct StatusDisplay<'a, S: StorageManager, W: WalletInterface> { #[cfg(feature = "terminal-ui")] terminal_ui: &'a Option>, #[allow(dead_code)] - config: &'a Config, + config: &'a ClientConfig, } impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { @@ -32,7 +32,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { storage: Arc>, wallet: Option<&'a Arc>>, terminal_ui: &'a Option>, - config: &'a Config, + config: &'a ClientConfig, ) -> Self { Self { state, @@ -52,7 +52,7 @@ impl<'a, S: StorageManager, W: WalletInterface> StatusDisplay<'a, S, W> { storage: Arc>, wallet: Option<&'a Arc>>, _terminal_ui: &'a Option<()>, - config: &'a Config, + config: &'a ClientConfig, ) -> Self { Self { state, diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index 25dc07edd..1e6a6ee1e 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -12,7 +12,7 @@ //! # Quick Start //! //! ```no_run -//! use dash_spv::{DashSpvClient, ConfigBuilder}; +//! use dash_spv::{DashSpvClient, ClientConfigBuilder}; //! use dash_spv::network::PeerNetworkManager; //! use dash_spv::storage::DiskStorageManager; //! use dashcore::Network; @@ -25,7 +25,7 @@ //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! // Create configuration for mainnet -//! let config = ConfigBuilder::mainnet() +//! let config = ClientConfigBuilder::mainnet() //! .storage_path("./.tmp/example-storage") //! .build() //! .unwrap(); @@ -75,7 +75,7 @@ pub mod types; pub mod validation; // Re-export main types for convenience -pub use client::{Config, ConfigBuilder, DashSpvClient, MempoolStrategy}; +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 748c1d4b4..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::{ConfigBuilder, DashSpvClient, LevelFilter}; +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; @@ -170,9 +170,9 @@ async fn run() -> Result<(), Box> { // Parse network let network_str = matches.get_one::("network").ok_or("Missing network argument")?; let mut cfg_builder = match network_str.as_str() { - "mainnet" => ConfigBuilder::mainnet(), - "testnet" => ConfigBuilder::testnet(), - "regtest" => ConfigBuilder::regtest(), + "mainnet" => ClientConfigBuilder::mainnet(), + "testnet" => ClientConfigBuilder::testnet(), + "regtest" => ClientConfigBuilder::regtest(), n => return Err(format!("Invalid network: {}", n).into()), }; @@ -342,7 +342,7 @@ async fn run() -> Result<(), Box> { } async fn run_client( - config: dash_spv::Config, + config: dash_spv::ClientConfig, network_manager: dash_spv::network::manager::PeerNetworkManager, storage_manager: S, wallet: Arc>>, diff --git a/dash-spv/src/network/manager.rs b/dash-spv/src/network/manager.rs index 4ca09d4ec..9e36101e3 100644 --- a/dash-spv/src/network/manager.rs +++ b/dash-spv/src/network/manager.rs @@ -10,7 +10,7 @@ use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinSet; use tokio::time; -use crate::Config; +use crate::ClientConfig; use crate::MempoolStrategy; use dashcore::network::constants::ServiceFlags; use dashcore::network::message::NetworkMessage; @@ -78,7 +78,7 @@ pub struct PeerNetworkManager { impl PeerNetworkManager { /// Create a new peer network manager - pub async fn new(config: &Config) -> Result { + pub async fn new(config: &ClientConfig) -> Result { let (message_tx, message_rx) = mpsc::channel(1000); let discovery = DnsDiscovery::new().await?; diff --git a/dash-spv/src/storage/mod.rs b/dash-spv/src/storage/mod.rs index 4b6b8f550..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, Config}; +use crate::{ChainState, ClientConfig}; pub use crate::storage::blocks::BlockHeaderStorage; pub use crate::storage::chainstate::ChainStateStorage; @@ -94,7 +94,7 @@ pub struct DiskStorageManager { } impl DiskStorageManager { - pub async fn new(config: &Config) -> StorageResult { + pub async fn new(config: &ClientConfig) -> StorageResult { use std::fs; let storage_path = config.storage_path().clone(); @@ -143,11 +143,11 @@ impl DiskStorageManager { pub async fn with_temp_dir() -> StorageResult { use tempfile::TempDir; - use crate::ConfigBuilder; + use crate::ClientConfigBuilder; let temp_dir = TempDir::new()?; Self::new( - &ConfigBuilder::testnet() + &ClientConfigBuilder::testnet() .storage_path(temp_dir.path()) .build() .expect("Valid configuration"), @@ -402,7 +402,7 @@ impl masternode::MasternodeStateStorage for DiskStorageManager { #[cfg(test)] mod tests { - use crate::ConfigBuilder; + use crate::ClientConfigBuilder; use super::*; use dashcore::Header as BlockHeader; @@ -412,8 +412,10 @@ mod tests { async fn test_store_load_headers() -> Result<(), Box> { // Create a temporary directory for the test let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let config = - ConfigBuilder::testnet().storage_path(temp_dir.path()).build().expect("Valid config"); + 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"); @@ -457,8 +459,10 @@ mod tests { #[tokio::test] async fn test_checkpoint_storage_indexing() -> StorageResult<()> { let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let config = - ConfigBuilder::testnet().storage_path(temp_dir.path()).build().expect("Valid config"); + 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 @@ -515,8 +519,10 @@ mod tests { #[tokio::test] async fn test_reverse_index_disk_storage() { let temp_dir = tempfile::tempdir().unwrap(); - let config = - ConfigBuilder::regtest().storage_path(temp_dir.path()).build().expect("Valid config"); + let config = ClientConfigBuilder::regtest() + .storage_path(temp_dir.path()) + .build() + .expect("Valid config"); { let mut storage = DiskStorageManager::new(&config).await.unwrap(); @@ -579,7 +585,8 @@ mod tests { lock_file.set_extension("lock"); lock_file }; - let config = ConfigBuilder::regtest().storage_path(path).build().expect("Valid config"); + let config = + ClientConfigBuilder::regtest().storage_path(path).build().expect("Valid config"); let mut storage1 = DiskStorageManager::new(&config).await.unwrap(); assert!(lock_path.exists(), "Lock file should exist while storage is open"); diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index 0bfc54851..1ae55501f 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -3,7 +3,7 @@ //! This module contains the FilterSyncManager struct and high-level coordination logic //! that delegates to specialized sub-modules for headers, downloads, matching, etc. -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -41,7 +41,7 @@ use super::types::*; pub struct FilterSyncManager { pub(super) _phantom_s: std::marker::PhantomData, pub(super) _phantom_n: std::marker::PhantomData, - pub(super) _config: Config, + pub(super) _config: ClientConfig, /// Whether filter header sync is currently in progress pub(super) syncing_filter_headers: bool, /// Current height being synced for filter headers @@ -88,7 +88,7 @@ pub struct FilterSyncManager { impl FilterSyncManager { /// Verify that the received compact filter hashes to the expected filter header - pub fn new(config: &Config, received_filter_heights: SharedFilterHeights) -> Self { + pub fn new(config: &ClientConfig, received_filter_heights: SharedFilterHeights) -> Self { Self { _config: config.clone(), syncing_filter_headers: false, diff --git a/dash-spv/src/sync/headers/manager.rs b/dash-spv/src/sync/headers/manager.rs index 335b790f5..e3707f027 100644 --- a/dash-spv/src/sync/headers/manager.rs +++ b/dash-spv/src/sync/headers/manager.rs @@ -8,7 +8,7 @@ use dashcore_hashes::Hash; use crate::chain::checkpoints::{mainnet_checkpoints, testnet_checkpoints, CheckpointManager}; use crate::chain::{ChainTip, ChainTipManager, ChainWork}; -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -45,7 +45,7 @@ impl Default for ReorgConfig { pub struct HeaderSyncManager { _phantom_s: std::marker::PhantomData, _phantom_n: std::marker::PhantomData, - config: Config, + config: ClientConfig, tip_manager: ChainTipManager, checkpoint_manager: CheckpointManager, reorg_config: ReorgConfig, @@ -59,7 +59,7 @@ pub struct HeaderSyncManager { impl HeaderSyncManager { /// Create a new header sync manager pub fn new( - config: &Config, + config: &ClientConfig, reorg_config: ReorgConfig, chain_state: Arc>, ) -> SyncResult { diff --git a/dash-spv/src/sync/manager.rs b/dash-spv/src/sync/manager.rs index 45bf928a6..818d13de7 100644 --- a/dash-spv/src/sync/manager.rs +++ b/dash-spv/src/sync/manager.rs @@ -2,7 +2,7 @@ use super::phases::{PhaseTransition, SyncPhase}; use super::transitions::TransitionManager; -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::SyncResult; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -76,7 +76,7 @@ pub struct SyncManager pub(super) masternode_sync: MasternodeSyncManager, /// Configuration - pub(super) config: Config, + pub(super) config: ClientConfig, /// Phase transition history pub(super) phase_history: Vec, @@ -103,7 +103,7 @@ pub struct SyncManager impl SyncManager { /// Create a new sequential sync manager pub fn new( - config: &Config, + config: &ClientConfig, received_filter_heights: SharedFilterHeights, wallet: Arc>, chain_state: Arc>, diff --git a/dash-spv/src/sync/masternodes/manager.rs b/dash-spv/src/sync/masternodes/manager.rs index a27a13b6d..f1c165102 100644 --- a/dash-spv/src/sync/masternodes/manager.rs +++ b/dash-spv/src/sync/masternodes/manager.rs @@ -14,7 +14,7 @@ use dashcore::{ use std::collections::HashMap; use std::time::{Duration, Instant}; -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -23,7 +23,7 @@ use crate::storage::StorageManager; pub struct MasternodeSyncManager { _phantom_s: std::marker::PhantomData, _phantom_n: std::marker::PhantomData, - config: Config, + config: ClientConfig, engine: Option, // Simple caches matching dash-evo-tool pattern @@ -53,7 +53,7 @@ pub struct MasternodeSyncManager { impl MasternodeSyncManager { /// Create a new masternode sync manager. - pub fn new(config: &Config) -> Self { + pub fn new(config: &ClientConfig) -> Self { 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()) { diff --git a/dash-spv/src/sync/transitions.rs b/dash-spv/src/sync/transitions.rs index 3e32014b2..5d8978aa4 100644 --- a/dash-spv/src/sync/transitions.rs +++ b/dash-spv/src/sync/transitions.rs @@ -1,6 +1,6 @@ //! Phase transition logic for sequential sync -use crate::client::Config; +use crate::client::ClientConfig; use crate::error::{SyncError, SyncResult}; use crate::network::NetworkManager; use crate::storage::StorageManager; @@ -11,12 +11,12 @@ use std::time::Instant; /// Manages phase transitions and validation pub struct TransitionManager { - config: Config, + config: ClientConfig, } impl TransitionManager { /// Create a new transition manager - pub fn new(config: &Config) -> Self { + pub fn new(config: &ClientConfig) -> Self { Self { config: config.clone(), } diff --git a/dash-spv/tests/block_download_test.rs b/dash-spv/tests/block_download_test.rs index 1486ce7af..d1c6d9a72 100644 --- a/dash-spv/tests/block_download_test.rs +++ b/dash-spv/tests/block_download_test.rs @@ -1,7 +1,7 @@ //! Tests for block downloading on filter match functionality. use dash_spv::test_utils::MockNetworkManager; -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use std::collections::HashSet; use std::sync::Arc; use tempfile::TempDir; @@ -10,11 +10,11 @@ use tokio::sync::Mutex; use dashcore::block::Block; use dash_spv::{ - client::Config, storage::DiskStorageManager, sync::FilterSyncManager, types::FilterMatch, + client::ClientConfig, storage::DiskStorageManager, sync::FilterSyncManager, types::FilterMatch, }; -fn create_test_config() -> Config { - ConfigBuilder::testnet() +fn create_test_config() -> ClientConfig { + ClientConfigBuilder::testnet() .enable_masternodes(false) .validation_mode(dash_spv::types::ValidationMode::None) .storage_path(TempDir::new().unwrap().path()) diff --git a/dash-spv/tests/chainlock_simple_test.rs b/dash-spv/tests/chainlock_simple_test.rs index 28d7ce4cb..8a066691b 100644 --- a/dash-spv/tests/chainlock_simple_test.rs +++ b/dash-spv/tests/chainlock_simple_test.rs @@ -4,7 +4,7 @@ use dash_spv::client::DashSpvClient; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use dashcore::Network; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::wallet_manager::WalletManager; @@ -32,7 +32,7 @@ async fn test_chainlock_validation_flow() { // Create client config with masternodes enabled let network = Network::Dash; let enable_masternodes = true; - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .enable_filters(false) .enable_masternodes(true) .validation_mode(ValidationMode::Basic) @@ -77,7 +77,7 @@ async fn test_chainlock_manager_initialization() { let temp_dir = TempDir::new().unwrap(); // Create client config - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .enable_filters(false) .enable_masternodes(false) .validation_mode(ValidationMode::Basic) diff --git a/dash-spv/tests/edge_case_filter_sync_test.rs b/dash-spv/tests/edge_case_filter_sync_test.rs index 3b9c9aea8..80ac10518 100644 --- a/dash-spv/tests/edge_case_filter_sync_test.rs +++ b/dash-spv/tests/edge_case_filter_sync_test.rs @@ -10,7 +10,7 @@ use dash_spv::{ network::NetworkManager, storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, - ConfigBuilder, + ClientConfigBuilder, }; use dashcore::{ block::Header as BlockHeader, hash_types::FilterHeader, network::message::NetworkMessage, @@ -109,7 +109,7 @@ impl NetworkManager for MockNetworkManager { #[tokio::test] async fn test_filter_sync_at_tip_edge_case() { - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); @@ -154,7 +154,7 @@ async fn test_filter_sync_at_tip_edge_case() { #[tokio::test] async fn test_no_invalid_getcfheaders_at_tip() { - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); diff --git a/dash-spv/tests/filter_header_verification_test.rs b/dash-spv/tests/filter_header_verification_test.rs index c99a6895a..6ac8aaeef 100644 --- a/dash-spv/tests/filter_header_verification_test.rs +++ b/dash-spv/tests/filter_header_verification_test.rs @@ -14,7 +14,7 @@ use dash_spv::{ storage::{BlockHeaderStorage, DiskStorageManager, FilterHeaderStorage}, sync::filters::FilterSyncManager, types::PeerInfo, - ConfigBuilder, + ClientConfigBuilder, }; use dashcore::{ block::{Header as BlockHeader, Version}, @@ -175,7 +175,7 @@ async fn test_filter_header_verification_failure_reproduction() { println!("=== Testing Filter Header Chain Verification Failure ==="); // Create storage and sync manager - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); @@ -341,7 +341,7 @@ 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 config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); @@ -519,7 +519,7 @@ 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 config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); @@ -619,7 +619,7 @@ 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 config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .storage_path(TempDir::new().unwrap().path()) .build() .expect("Valid config"); diff --git a/dash-spv/tests/handshake_test.rs b/dash-spv/tests/handshake_test.rs index ba4aec77b..fa267c00c 100644 --- a/dash-spv/tests/handshake_test.rs +++ b/dash-spv/tests/handshake_test.rs @@ -5,7 +5,7 @@ use std::time::Duration; use dash_spv::network::{HandshakeManager, NetworkManager, Peer, PeerNetworkManager}; use dash_spv::Network; -use dash_spv::{ConfigBuilder, MempoolStrategy}; +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 = ConfigBuilder::mainnet().build().expect("Valid config"); + 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 dfdc7e191..811f0d8f8 100644 --- a/dash-spv/tests/header_sync_test.rs +++ b/dash-spv/tests/header_sync_test.rs @@ -6,7 +6,7 @@ use dash_spv::{ storage::{BlockHeaderStorage, ChainStateStorage, DiskStorageManager}, sync::{HeaderSyncManager, ReorgConfig}, types::{ChainState, ValidationMode}, - ConfigBuilder, + ClientConfigBuilder, }; use dashcore::{block::Header as BlockHeader, block::Version}; use dashcore_hashes::Hash; @@ -23,7 +23,7 @@ async fn test_header_sync_with_client_integration() { let _ = env_logger::try_init(); // Test header sync integration with the full client - let config = ConfigBuilder::mainnet() + let config = ClientConfigBuilder::mainnet() .validation_mode(ValidationMode::Basic) .storage_path(TempDir::new().expect("Failed to create tmp dir").path()) .build() @@ -91,7 +91,7 @@ fn create_test_header_chain_from(start: usize, count: usize) -> Vec async fn test_prepare_sync(sync_base_height: u32, header_count: usize) { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let config = - ConfigBuilder::regtest().storage_path(temp_dir.path()).build().expect("Valid 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); diff --git a/dash-spv/tests/peer_test.rs b/dash-spv/tests/peer_test.rs index 7fbc45d40..a347c0eb7 100644 --- a/dash-spv/tests/peer_test.rs +++ b/dash-spv/tests/peer_test.rs @@ -1,6 +1,6 @@ //! Integration tests for peer networking -use dash_spv::ConfigBuilder; +use dash_spv::ClientConfigBuilder; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -8,7 +8,7 @@ use tempfile::TempDir; use tokio::sync::RwLock; use tokio::time; -use dash_spv::client::{Config, DashSpvClient}; +use dash_spv::client::{ClientConfig, DashSpvClient}; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; use dash_spv::types::ValidationMode; @@ -17,8 +17,8 @@ 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) -> Config { - ConfigBuilder::default() +fn create_test_config(network: Network) -> ClientConfig { + ClientConfigBuilder::default() .network(network) .storage_path(TempDir::new().unwrap().path()) .validation_mode(ValidationMode::Basic) diff --git a/dash-spv/tests/smart_fetch_integration_test.rs b/dash-spv/tests/smart_fetch_integration_test.rs index b76e3ed58..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::ConfigBuilder; +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 = ConfigBuilder::testnet().build().expect("Valid config"); + 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/wallet_integration_test.rs b/dash-spv/tests/wallet_integration_test.rs index 78be539d6..125d4361e 100644 --- a/dash-spv/tests/wallet_integration_test.rs +++ b/dash-spv/tests/wallet_integration_test.rs @@ -8,14 +8,14 @@ use tokio::sync::RwLock; use dash_spv::network::PeerNetworkManager; use dash_spv::storage::DiskStorageManager; -use dash_spv::{ConfigBuilder, 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 = ConfigBuilder::testnet() + let config = ClientConfigBuilder::testnet() .enable_filters(false) .storage_path(TempDir::new().unwrap().path()) .enable_masternodes(true) From 769c3fd18e1c9a1c6fa486bebd456fe05052e99f Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 19 Jan 2026 19:44:56 +0000 Subject: [PATCH 10/12] fixed bugs spotted in review --- dash-spv-ffi/src/config.rs | 6 +++++- dash-spv-ffi/tests/security/test_security.rs | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 65de4ceeb..74a42c332 100644 --- a/dash-spv-ffi/src/config.rs +++ b/dash-spv-ffi/src/config.rs @@ -369,7 +369,11 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_build( let builder = Box::from_raw(ffi_builder.inner as *mut ClientConfigBuilder); match builder.build() { - Ok(config) => Box::into_raw(Box::new(config.into())), + 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() diff --git a/dash-spv-ffi/tests/security/test_security.rs b/dash-spv-ffi/tests/security/test_security.rs index e7a95cfaf..88c5bb612 100644 --- a/dash-spv-ffi/tests/security/test_security.rs +++ b/dash-spv-ffi/tests/security/test_security.rs @@ -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_builder_set_max_peers(config, 10); + let result = dash_spv_ffi_config_builder_set_max_peers(builder, 10); assert_ne!(result, FFIErrorCode::Success as i32); } } From d9b01bf18b4f161881ac7d7563ac7521fca4c4a7 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 19 Jan 2026 19:46:20 +0000 Subject: [PATCH 11/12] generated new headers and docs --- dash-spv-ffi/FFI_API.md | 58 ++++++++++++++--------------- dash-spv-ffi/examples/basic_usage.c | 4 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index 0f76e06f9..36a79803f 100644 --- a/dash-spv-ffi/FFI_API.md +++ b/dash-spv-ffi/FFI_API.md @@ -175,7 +175,7 @@ Destroy the client and free associated resources. # Safety - `client` must be e #### `dash_spv_ffi_client_new` ```c -dash_spv_ffi_client_new(config: *const FFIConfig,) -> *mut FFIDashSpvClient +dash_spv_ffi_client_new(config: *const FFIClientConfig,) -> *mut FFIDashSpvClient ``` **Description:** @@ -225,7 +225,7 @@ Stop the SPV client. # Safety - `client` must be a valid, non-null pointer to a #### `dash_spv_ffi_client_update_config` ```c -dash_spv_ffi_client_update_config(client: *mut FFIDashSpvClient, config: *const FFIConfig,) -> i32 +dash_spv_ffi_client_update_config(client: *mut FFIDashSpvClient, config: *const FFIClientConfig,) -> i32 ``` **Description:** @@ -241,7 +241,7 @@ Update the running client's configuration. # Safety - `client` must be a valid #### `dash_spv_ffi_config_add_peer` ```c -dash_spv_ffi_config_add_peer(config: *mut FFIConfig, addr: *const c_char,) -> i32 +dash_spv_ffi_config_add_peer(config: *mut FFIClientConfig, addr: *const c_char,) -> i32 ``` **Description:** @@ -257,7 +257,7 @@ Adds a peer address to the configuration Accepts socket addresses with or witho #### `dash_spv_ffi_config_builder_build` ```c -dash_spv_ffi_config_builder_build(builder: *mut FFIConfigBuilder,) -> *mut FFIConfig +dash_spv_ffi_config_builder_build(builder: *mut FFIClientConfigBuilder,) -> *mut FFIClientConfig ``` **Description:** @@ -273,7 +273,7 @@ Gets ownership of the builder and returns the built configuration destroying the #### `dash_spv_ffi_config_builder_destroy` ```c -dash_spv_ffi_config_builder_destroy(builder: *mut FFIConfigBuilder) -> () +dash_spv_ffi_config_builder_destroy(builder: *mut FFIClientConfigBuilder) -> () ``` **Description:** @@ -289,7 +289,7 @@ Destroys an FFIConfigBuilder and frees its memory # Safety - `builder` must be #### `dash_spv_ffi_config_builder_devnet` ```c -dash_spv_ffi_config_builder_devnet() -> *mut FFIConfigBuilder +dash_spv_ffi_config_builder_devnet() -> *mut FFIClientConfigBuilder ``` **Module:** `config` @@ -299,7 +299,7 @@ dash_spv_ffi_config_builder_devnet() -> *mut FFIConfigBuilder #### `dash_spv_ffi_config_builder_mainnet` ```c -dash_spv_ffi_config_builder_mainnet() -> *mut FFIConfigBuilder +dash_spv_ffi_config_builder_mainnet() -> *mut FFIClientConfigBuilder ``` **Module:** `config` @@ -309,7 +309,7 @@ dash_spv_ffi_config_builder_mainnet() -> *mut FFIConfigBuilder #### `dash_spv_ffi_config_builder_regtest` ```c -dash_spv_ffi_config_builder_regtest() -> *mut FFIConfigBuilder +dash_spv_ffi_config_builder_regtest() -> *mut FFIClientConfigBuilder ``` **Module:** `config` @@ -319,7 +319,7 @@ dash_spv_ffi_config_builder_regtest() -> *mut FFIConfigBuilder #### `dash_spv_ffi_config_builder_set_fetch_mempool_transactions` ```c -dash_spv_ffi_config_builder_set_fetch_mempool_transactions(builder: *mut FFIConfigBuilder, fetch: bool,) -> i32 +dash_spv_ffi_config_builder_set_fetch_mempool_transactions(builder: *mut FFIClientConfigBuilder, fetch: bool,) -> i32 ``` **Description:** @@ -335,7 +335,7 @@ Sets whether to fetch full mempool transaction data # Safety - `builder` must b #### `dash_spv_ffi_config_builder_set_filter_load` ```c -dash_spv_ffi_config_builder_set_filter_load(builder: *mut FFIConfigBuilder, load_filters: bool,) -> i32 +dash_spv_ffi_config_builder_set_filter_load(builder: *mut FFIClientConfigBuilder, load_filters: bool,) -> i32 ``` **Description:** @@ -351,7 +351,7 @@ Sets whether to load bloom filters # Safety - `builder` must be a valid pointer #### `dash_spv_ffi_config_builder_set_masternode_sync_enabled` ```c -dash_spv_ffi_config_builder_set_masternode_sync_enabled(builder: *mut FFIConfigBuilder, enable: bool,) -> i32 +dash_spv_ffi_config_builder_set_masternode_sync_enabled(builder: *mut FFIClientConfigBuilder, enable: bool,) -> i32 ``` **Description:** @@ -367,7 +367,7 @@ Enables or disables masternode synchronization # Safety - `builder` must be a v #### `dash_spv_ffi_config_builder_set_max_mempool_transactions` ```c -dash_spv_ffi_config_builder_set_max_mempool_transactions(builder: *mut FFIConfigBuilder, max_transactions: u32,) -> i32 +dash_spv_ffi_config_builder_set_max_mempool_transactions(builder: *mut FFIClientConfigBuilder, max_transactions: u32,) -> i32 ``` **Description:** @@ -383,7 +383,7 @@ Sets the maximum number of mempool transactions to track # Safety - `builder` m #### `dash_spv_ffi_config_builder_set_max_peers` ```c -dash_spv_ffi_config_builder_set_max_peers(builder: *mut FFIConfigBuilder, max_peers: u32,) -> i32 +dash_spv_ffi_config_builder_set_max_peers(builder: *mut FFIClientConfigBuilder, max_peers: u32,) -> i32 ``` **Description:** @@ -399,7 +399,7 @@ Sets the maximum number of peers to connect to # Safety - `builder` must be a v #### `dash_spv_ffi_config_builder_set_mempool_strategy` ```c -dash_spv_ffi_config_builder_set_mempool_strategy(builder: *mut FFIConfigBuilder, strategy: FFIMempoolStrategy,) -> i32 +dash_spv_ffi_config_builder_set_mempool_strategy(builder: *mut FFIClientConfigBuilder, strategy: FFIMempoolStrategy,) -> i32 ``` **Description:** @@ -415,7 +415,7 @@ Sets the mempool synchronization strategy # Safety - `builder` must be a valid #### `dash_spv_ffi_config_builder_set_mempool_tracking` ```c -dash_spv_ffi_config_builder_set_mempool_tracking(builder: *mut FFIConfigBuilder, enable: bool,) -> i32 +dash_spv_ffi_config_builder_set_mempool_tracking(builder: *mut FFIClientConfigBuilder, enable: bool,) -> i32 ``` **Description:** @@ -431,7 +431,7 @@ Enables or disables mempool tracking # Safety - `builder` must be a valid point #### `dash_spv_ffi_config_builder_set_persist_mempool` ```c -dash_spv_ffi_config_builder_set_persist_mempool(builder: *mut FFIConfigBuilder, persist: bool,) -> i32 +dash_spv_ffi_config_builder_set_persist_mempool(builder: *mut FFIClientConfigBuilder, persist: bool,) -> i32 ``` **Description:** @@ -447,7 +447,7 @@ Sets whether to persist mempool state to disk # Safety - `builder` must be a va #### `dash_spv_ffi_config_builder_set_relay_transactions` ```c -dash_spv_ffi_config_builder_set_relay_transactions(builder: *mut FFIConfigBuilder, _relay: bool,) -> i32 +dash_spv_ffi_config_builder_set_relay_transactions(builder: *mut FFIClientConfigBuilder, _relay: bool,) -> i32 ``` **Description:** @@ -463,7 +463,7 @@ Sets whether to relay transactions (currently a no-op) # Safety - `builder` mus #### `dash_spv_ffi_config_builder_set_restrict_to_configured_peers` ```c -dash_spv_ffi_config_builder_set_restrict_to_configured_peers(builder: *mut FFIConfigBuilder, restrict_peers: bool,) -> i32 +dash_spv_ffi_config_builder_set_restrict_to_configured_peers(builder: *mut FFIClientConfigBuilder, restrict_peers: bool,) -> i32 ``` **Description:** @@ -479,7 +479,7 @@ Restrict connections strictly to configured peers (disable DNS discovery and pee #### `dash_spv_ffi_config_builder_set_start_from_height` ```c -dash_spv_ffi_config_builder_set_start_from_height(builder: *mut FFIConfigBuilder, height: u32,) -> i32 +dash_spv_ffi_config_builder_set_start_from_height(builder: *mut FFIClientConfigBuilder, height: u32,) -> i32 ``` **Description:** @@ -495,7 +495,7 @@ Sets the starting block height for synchronization # Safety - `builder` must be #### `dash_spv_ffi_config_builder_set_storage_path` ```c -dash_spv_ffi_config_builder_set_storage_path(builder: *mut FFIConfigBuilder, path: *const c_char,) -> i32 +dash_spv_ffi_config_builder_set_storage_path(builder: *mut FFIClientConfigBuilder, path: *const c_char,) -> i32 ``` **Description:** @@ -511,7 +511,7 @@ Sets the data directory for storing blockchain data # Safety - `builder` must b #### `dash_spv_ffi_config_builder_set_user_agent` ```c -dash_spv_ffi_config_builder_set_user_agent(builder: *mut FFIConfigBuilder, user_agent: *const c_char,) -> i32 +dash_spv_ffi_config_builder_set_user_agent(builder: *mut FFIClientConfigBuilder, user_agent: *const c_char,) -> i32 ``` **Description:** @@ -527,7 +527,7 @@ Sets the user agent string to advertise in the P2P handshake # Safety - `builde #### `dash_spv_ffi_config_builder_set_validation_mode` ```c -dash_spv_ffi_config_builder_set_validation_mode(builder: *mut FFIConfigBuilder, mode: FFIValidationMode,) -> i32 +dash_spv_ffi_config_builder_set_validation_mode(builder: *mut FFIClientConfigBuilder, mode: FFIValidationMode,) -> i32 ``` **Description:** @@ -543,7 +543,7 @@ Sets the validation mode for the SPV client # Safety - `builder` must be a vali #### `dash_spv_ffi_config_builder_set_worker_threads` ```c -dash_spv_ffi_config_builder_set_worker_threads(builder: *mut FFIConfigBuilder, threads: u32,) -> i32 +dash_spv_ffi_config_builder_set_worker_threads(builder: *mut FFIClientConfigBuilder, threads: u32,) -> i32 ``` **Description:** @@ -559,7 +559,7 @@ Sets the number of Tokio worker threads for the FFI runtime (0 = auto) # Safety #### `dash_spv_ffi_config_builder_testnet` ```c -dash_spv_ffi_config_builder_testnet() -> *mut FFIConfigBuilder +dash_spv_ffi_config_builder_testnet() -> *mut FFIClientConfigBuilder ``` **Module:** `config` @@ -569,7 +569,7 @@ dash_spv_ffi_config_builder_testnet() -> *mut FFIConfigBuilder #### `dash_spv_ffi_config_destroy` ```c -dash_spv_ffi_config_destroy(config: *mut FFIConfig) -> () +dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) -> () ``` **Description:** @@ -585,7 +585,7 @@ Destroys an FFIConfig and frees its memory # Safety - `builder` must be a valid #### `dash_spv_ffi_config_get_mempool_strategy` ```c -dash_spv_ffi_config_get_mempool_strategy(config: *const FFIConfig,) -> FFIMempoolStrategy +dash_spv_ffi_config_get_mempool_strategy(config: *const FFIClientConfig,) -> FFIMempoolStrategy ``` **Description:** @@ -601,7 +601,7 @@ Gets the mempool synchronization strategy # Safety - `config` must be a valid p #### `dash_spv_ffi_config_get_mempool_tracking` ```c -dash_spv_ffi_config_get_mempool_tracking(config: *const FFIConfig,) -> bool +dash_spv_ffi_config_get_mempool_tracking(config: *const FFIClientConfig,) -> bool ``` **Description:** @@ -617,7 +617,7 @@ Gets whether mempool tracking is enabled # Safety - `config` must be a valid po #### `dash_spv_ffi_config_get_network` ```c -dash_spv_ffi_config_get_network(config: *const FFIConfig) -> FFINetwork +dash_spv_ffi_config_get_network(config: *const FFIClientConfig,) -> FFINetwork ``` **Description:** @@ -633,7 +633,7 @@ Gets the network type from the configuration # Safety - `config` must be a vali #### `dash_spv_ffi_config_get_storage_path` ```c -dash_spv_ffi_config_get_storage_path(config: *const FFIConfig,) -> FFIString +dash_spv_ffi_config_get_storage_path(config: *const FFIClientConfig,) -> FFIString ``` **Description:** diff --git a/dash-spv-ffi/examples/basic_usage.c b/dash-spv-ffi/examples/basic_usage.c index 638235551..b4d519abf 100644 --- a/dash-spv-ffi/examples/basic_usage.c +++ b/dash-spv-ffi/examples/basic_usage.c @@ -10,7 +10,7 @@ int main() { } // Create a configuration for testnet - FFIConfig* builder = dash_spv_ffi_config_builder_testnet(); + FFIClientConfigBuilder* builder = dash_spv_ffi_config_builder_testnet(); if (builder == NULL) { fprintf(stderr, "Failed to create config builder\n"); return 1; @@ -23,7 +23,7 @@ int main() { return 1; } - FFIConfig* config = dash_spv_ffi_config_builder_build(builder); + FFIClientConfig* config = dash_spv_ffi_config_builder_build(builder); // Create the client FFIDashSpvClient* client = dash_spv_ffi_client_new(config); From 40074efe190aea1a7066532804d00db80301a5c4 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Tue, 20 Jan 2026 18:49:01 +0000 Subject: [PATCH 12/12] ffi setters are back with deprecated warning to encorage new API while maintaining compatibility with the old one --- dash-spv-ffi/FFI_API.md | 316 +++++++++++++- dash-spv-ffi/dash_spv_ffi.h | 2 +- dash-spv-ffi/include/dash_spv_ffi.h | 201 ++++++++- dash-spv-ffi/src/client.rs | 2 + dash-spv-ffi/src/config.rs | 404 ++++++++++++++++-- dash-spv-ffi/tests/c_tests/test_basic.c | 2 +- dash-spv-ffi/tests/unit/test_configuration.rs | 2 +- dash-spv/src/client/config.rs | 95 +++- dash-spv/src/client/core.rs | 1 + 9 files changed, 969 insertions(+), 56 deletions(-) diff --git a/dash-spv-ffi/FFI_API.md b/dash-spv-ffi/FFI_API.md index 36a79803f..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**: 69 +**Total Functions**: 87 ## Table of Contents @@ -33,7 +33,7 @@ Functions: 4 ### Configuration -Functions: 28 +Functions: 46 | Function | Description | Module | |----------|-------------|--------| @@ -60,11 +60,29 @@ Functions: 28 | `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 FFIConfig and frees its memory # Safety - `builder` must be a... | 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 | | `dash_spv_ffi_config_get_mempool_tracking` | Gets whether mempool tracking is enabled # Safety - `config` must be a... | config | | `dash_spv_ffi_config_get_network` | Gets the network type from the configuration # Safety - `config` must be a... | config | -| `dash_spv_ffi_config_get_storage_path` | Gets the data directory path from the configuration # Safety - `config`... | config | +| `dash_spv_ffi_config_mainnet` | No description | config | +| `dash_spv_ffi_config_new` | No description | config | +| `dash_spv_ffi_config_set_data_dir` | Sets the data directory for storing blockchain data # Safety - `config`... | config | +| `dash_spv_ffi_config_set_fetch_mempool_transactions` | Sets whether to fetch full mempool transaction data # Safety - `config`... | config | +| `dash_spv_ffi_config_set_filter_load` | Sets whether to load bloom filters # Safety - `config` must be a valid... | config | +| `dash_spv_ffi_config_set_masternode_sync_enabled` | Enables or disables masternode synchronization # Safety - `config` must be... | config | +| `dash_spv_ffi_config_set_max_mempool_transactions` | Sets the maximum number of mempool transactions to track # Safety -... | config | +| `dash_spv_ffi_config_set_max_peers` | Sets the maximum number of peers to connect to # Safety - `config` must be... | config | +| `dash_spv_ffi_config_set_mempool_strategy` | Sets the mempool synchronization strategy # Safety - `config` must be a... | config | +| `dash_spv_ffi_config_set_mempool_tracking` | Enables or disables mempool tracking # Safety - `config` must be a valid... | config | +| `dash_spv_ffi_config_set_persist_mempool` | Sets whether to persist mempool state to disk # Safety - `config` must be a... | config | +| `dash_spv_ffi_config_set_relay_transactions` | Sets whether to relay transactions (currently a no-op) # Safety - `config`... | config | +| `dash_spv_ffi_config_set_restrict_to_configured_peers` | Restrict connections strictly to configured peers (disable DNS discovery and... | config | +| `dash_spv_ffi_config_set_start_from_height` | Sets the starting block height for synchronization # Safety - `config` must... | config | +| `dash_spv_ffi_config_set_user_agent` | Sets the user agent string to advertise in the P2P handshake # Safety -... | config | +| `dash_spv_ffi_config_set_validation_mode` | Sets the validation mode for the SPV client # Safety - `config` must be a... | config | +| `dash_spv_ffi_config_set_worker_threads` | Sets the number of Tokio worker threads for the FFI runtime (0 = auto) #... | config | +| `dash_spv_ffi_config_testnet` | No description | config | ### Synchronization @@ -245,10 +263,10 @@ 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 FFIConfig - `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 FFIConfig - `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 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` @@ -573,10 +591,26 @@ dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) -> () ``` **Description:** -Destroys an FFIConfig 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 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:** -- `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 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` + +--- + +#### `dash_spv_ffi_config_get_data_dir` + +```c +dash_spv_ffi_config_get_data_dir(config: *const FFIClientConfig,) -> FFIString +``` + +**Description:** +Gets the data directory path from the configuration # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` + +**Safety:** +- `config` must be a valid pointer to an FFIClientConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` **Module:** `config` @@ -621,26 +655,280 @@ dash_spv_ffi_config_get_network(config: *const FFIClientConfig,) -> FFINetwork ``` **Description:** -Gets the network type from the configuration # Safety - `config` must be a valid pointer to an FFIConfig or null - If null, returns FFINetwork::Dash as default +Gets the network type from the configuration # Safety - `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFINetwork::Dash as default + +**Safety:** +- `config` must be a valid pointer to an FFIClientConfig or null - If null, returns FFINetwork::Dash as default + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_mainnet` + +```c +dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_new` + +```c +dash_spv_ffi_config_new(network: FFINetwork) -> *mut FFIClientConfig +``` + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_data_dir` + +```c +dash_spv_ffi_config_set_data_dir(config: *mut FFIClientConfig, path: *const c_char,) -> i32 +``` + +**Description:** +Sets the data directory for storing blockchain data # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `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:** +- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `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_set_fetch_mempool_transactions` + +```c +dash_spv_ffi_config_set_fetch_mempool_transactions(config: *mut FFIClientConfig, fetch: bool,) -> i32 +``` + +**Description:** +Sets whether to fetch full mempool transaction data # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_filter_load` + +```c +dash_spv_ffi_config_set_filter_load(config: *mut FFIClientConfig, load_filters: bool,) -> i32 +``` + +**Description:** +Sets whether to load bloom filters # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_masternode_sync_enabled` + +```c +dash_spv_ffi_config_set_masternode_sync_enabled(config: *mut FFIClientConfig, enable: bool,) -> i32 +``` + +**Description:** +Enables or disables masternode synchronization # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_max_mempool_transactions` + +```c +dash_spv_ffi_config_set_max_mempool_transactions(config: *mut FFIClientConfig, max_transactions: u32,) -> i32 +``` + +**Description:** +Sets the maximum number of mempool transactions to track # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_max_peers` + +```c +dash_spv_ffi_config_set_max_peers(config: *mut FFIClientConfig, max_peers: u32,) -> i32 +``` + +**Description:** +Sets the maximum number of peers to connect to # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_mempool_strategy` + +```c +dash_spv_ffi_config_set_mempool_strategy(config: *mut FFIClientConfig, strategy: FFIMempoolStrategy,) -> i32 +``` + +**Description:** +Sets the mempool synchronization strategy # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_mempool_tracking` + +```c +dash_spv_ffi_config_set_mempool_tracking(config: *mut FFIClientConfig, enable: bool,) -> i32 +``` + +**Description:** +Enables or disables mempool tracking # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_persist_mempool` + +```c +dash_spv_ffi_config_set_persist_mempool(config: *mut FFIClientConfig, persist: bool,) -> i32 +``` + +**Description:** +Sets whether to persist mempool state to disk # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_relay_transactions` + +```c +dash_spv_ffi_config_set_relay_transactions(config: *mut FFIClientConfig, _relay: bool,) -> i32 +``` + +**Description:** +Sets whether to relay transactions (currently a no-op) # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_restrict_to_configured_peers` + +```c +dash_spv_ffi_config_set_restrict_to_configured_peers(config: *mut FFIClientConfig, restrict_peers: bool,) -> i32 +``` + +**Description:** +Restrict connections strictly to configured peers (disable DNS discovery and peer store) # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + +**Safety:** +- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_start_from_height` + +```c +dash_spv_ffi_config_set_start_from_height(config: *mut FFIClientConfig, height: u32,) -> i32 +``` + +**Description:** +Sets the starting block height for synchronization # Safety - `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 **Safety:** -- `config` must be a valid pointer to an FFIConfig or null - If null, returns FFINetwork::Dash as default +- `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 **Module:** `config` --- -#### `dash_spv_ffi_config_get_storage_path` +#### `dash_spv_ffi_config_set_user_agent` ```c -dash_spv_ffi_config_get_storage_path(config: *const FFIClientConfig,) -> FFIString +dash_spv_ffi_config_set_user_agent(config: *mut FFIClientConfig, user_agent: *const c_char,) -> i32 ``` **Description:** -Gets the data directory path from the configuration # Safety - `config` must be a valid pointer to an FFIConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` +Sets the user agent string to advertise in the P2P handshake # Safety - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `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:** -- `config` must be a valid pointer to an FFIConfig or null - If null or no data directory is set, returns an FFIString with null pointer - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` +- `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet - `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_set_validation_mode` + +```c +dash_spv_ffi_config_set_validation_mode(config: *mut FFIClientConfig, mode: FFIValidationMode,) -> i32 +``` + +**Description:** +Sets the validation mode for the SPV client # Safety - `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 + +**Safety:** +- `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 + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_set_worker_threads` + +```c +dash_spv_ffi_config_set_worker_threads(config: *mut FFIClientConfig, 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 FFIClientConfig + +**Safety:** +- `config` must be a valid pointer to an FFIClientConfig + +**Module:** `config` + +--- + +#### `dash_spv_ffi_config_testnet` + +```c +dash_spv_ffi_config_testnet() -> *mut FFIClientConfig +``` **Module:** `config` diff --git a/dash-spv-ffi/dash_spv_ffi.h b/dash-spv-ffi/dash_spv_ffi.h index 8319d4289..1e42908f5 100644 --- a/dash-spv-ffi/dash_spv_ffi.h +++ b/dash-spv-ffi/dash_spv_ffi.h @@ -698,7 +698,7 @@ int32_t dash_spv_ffi_config_set_masternode_sync_enabled(struct FFIClientConfig * * - If null or no data directory is set, returns an FFIString with null pointer * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` */ - struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIClientConfig *config) ; + struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIClientConfig *config) ; /** * Destroys an FFIClientConfig and frees its memory diff --git a/dash-spv-ffi/include/dash_spv_ffi.h b/dash-spv-ffi/include/dash_spv_ffi.h index f094bd8ac..dcf0b86fd 100644 --- a/dash-spv-ffi/include/dash_spv_ffi.h +++ b/dash-spv-ffi/include/dash_spv_ffi.h @@ -739,6 +739,49 @@ struct FFIClientConfig *dash_spv_ffi_config_builder_build(struct FFIClientConfig 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) ; + + struct FFIClientConfig *dash_spv_ffi_config_testnet(void) ; + +/** + * Sets the data directory for storing blockchain data + * + * # Safety + * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_data_dir(struct FFIClientConfig *config, + const char *path) +; + +/** + * Sets the validation mode for the SPV client + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_validation_mode(struct FFIClientConfig *config, + enum DashSpvValidationMode mode) +; + +/** + * Sets the maximum number of peers to connect to + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_max_peers(struct FFIClientConfig *config, + uint32_t max_peers) +; + /** * Adds a peer address to the configuration * @@ -752,17 +795,77 @@ void dash_spv_ffi_config_builder_destroy(struct FFIClientConfigBuilder *builder) * - Hostname without port: `node.example.com` * * # Safety - * - `config` must be a valid pointer to an FFIConfig + * - `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) ; +/** + * Sets the user agent string to advertise in the P2P handshake + * + * # Safety + * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + * - `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_set_user_agent(struct FFIClientConfig *config, + const char *user_agent) +; + +/** + * Sets whether to relay transactions (currently a no-op) + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_relay_transactions(struct FFIClientConfig *config, + bool _relay) +; + +/** + * Sets whether to load bloom filters + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_filter_load(struct FFIClientConfig *config, + bool load_filters) +; + +/** + * Restrict connections strictly to configured peers (disable DNS discovery and peer store) + * + * # Safety + * - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet + */ + +int32_t dash_spv_ffi_config_set_restrict_to_configured_peers(struct FFIClientConfig *config, + bool restrict_peers) +; + +/** + * Enables or disables masternode synchronization + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_masternode_sync_enabled(struct FFIClientConfig *config, + bool enable) +; + /** * Gets the network type from the configuration * * # Safety - * - `config` must be a valid pointer to an FFIConfig or null + * - `config` must be a valid pointer to an FFIClientConfig or null * - If null, returns FFINetwork::Dash as default */ FFINetwork dash_spv_ffi_config_get_network(const struct FFIClientConfig *config) ; @@ -771,11 +874,89 @@ void dash_spv_ffi_config_builder_destroy(struct FFIClientConfigBuilder *builder) * Gets the data directory path from the configuration * * # Safety - * - `config` must be a valid pointer to an FFIConfig or null + * - `config` must be a valid pointer to an FFIClientConfig or null * - If null or no data directory is set, returns an FFIString with null pointer * - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` */ - struct FFIString dash_spv_ffi_config_get_storage_path(const struct FFIClientConfig *config) ; + struct FFIString dash_spv_ffi_config_get_data_dir(const struct FFIClientConfig *config) ; + +/** + * 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 + */ + void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) ; + +/** + * Sets the number of Tokio worker threads for the FFI runtime (0 = auto) + * + * # 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) ; + +/** + * Enables or disables mempool tracking + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_mempool_tracking(struct FFIClientConfig *config, + bool enable) +; + +/** + * Sets the mempool synchronization strategy + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_mempool_strategy(struct FFIClientConfig *config, + enum FFIMempoolStrategy strategy) +; + +/** + * Sets the maximum number of mempool transactions to track + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_max_mempool_transactions(struct FFIClientConfig *config, + uint32_t max_transactions) +; + +/** + * Sets whether to fetch full mempool transaction data + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_fetch_mempool_transactions(struct FFIClientConfig *config, + bool fetch) +; + +/** + * Sets whether to persist mempool state to disk + * + * # Safety + * - `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 + */ + +int32_t dash_spv_ffi_config_set_persist_mempool(struct FFIClientConfig *config, + bool persist) +; /** * Gets whether mempool tracking is enabled @@ -798,14 +979,16 @@ enum FFIMempoolStrategy dash_spv_ffi_config_get_mempool_strategy(const struct FF ; /** - * Destroys an FFIConfig and frees its memory + * Sets the starting block height for synchronization * * # 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 instance + * - `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 */ - void dash_spv_ffi_config_destroy(struct FFIClientConfig *config) ; + +int32_t dash_spv_ffi_config_set_start_from_height(struct FFIClientConfig *config, + uint32_t height) +; const char *dash_spv_ffi_get_last_error(void) ; diff --git a/dash-spv-ffi/src/client.rs b/dash-spv-ffi/src/client.rs index 693116449..953f2e4c1 100644 --- a/dash-spv-ffi/src/client.rs +++ b/dash-spv-ffi/src/client.rs @@ -401,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, diff --git a/dash-spv-ffi/src/config.rs b/dash-spv-ffi/src/config.rs index 74a42c332..7537d7f80 100644 --- a/dash-spv-ffi/src/config.rs +++ b/dash-spv-ffi/src/config.rs @@ -397,6 +397,117 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_destroy(builder: *mut FFICl } } +/***************** 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 = + 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, + worker_threads: 0, + })) +} + +#[no_mangle] +#[deprecated] +#[allow(deprecated)] +pub extern "C" fn dash_spv_ffi_config_mainnet() -> *mut FFIClientConfig { + 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, + worker_threads: 0, + })) +} + +#[no_mangle] +#[deprecated] +#[allow(deprecated)] +pub extern "C" fn dash_spv_ffi_config_testnet() -> *mut FFIClientConfig { + 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, + worker_threads: 0, + })) +} + +/// Sets the data directory for storing blockchain data +/// +/// # Safety +/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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, +) -> i32 { + null_check!(config); + null_check!(path); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + match CStr::from_ptr(path).to_str() { + Ok(path_str) => { + config.set_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 +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_validation_mode(mode.into()); + FFIErrorCode::Success as i32 +} + +/// Sets the maximum number of peers to connect to +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_max_peers(max_peers); + FFIErrorCode::Success as i32 +} + +// Note: dash-spv doesn't have min_peers, only max_peers + /// Adds a peer address to the configuration /// /// Accepts socket addresses with or without port. When no port is specified, @@ -409,7 +520,7 @@ pub unsafe extern "C" fn dash_spv_ffi_config_builder_destroy(builder: *mut FFICl /// - Hostname without port: `node.example.com` /// /// # Safety -/// - `config` must be a valid pointer to an FFIConfig +/// - `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] @@ -474,10 +585,116 @@ pub unsafe extern "C" fn dash_spv_ffi_config_add_peer( } } +/// Sets the user agent string to advertise in the P2P handshake +/// +/// # Safety +/// - `config` must be a valid pointer to an FFIClientConfig created by dash_spv_ffi_config_new/mainnet/testnet +/// - `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, +) -> i32 { + null_check!(config); + 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 *((*config).inner as *mut ClientConfig) }; + cfg.set_user_agent(agent_str); + 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 +/// - `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, +) -> i32 { + null_check!(config); + + let _config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + // relay_transactions not directly settable in current ClientConfig + FFIErrorCode::Success as i32 +} + +/// Sets whether to load bloom filters +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_enable_filters(load_filters); + FFIErrorCode::Success as i32 +} + +/// Restrict connections strictly to configured peers (disable DNS discovery and peer store) +/// +/// # 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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_restrict_to_configured_peers(restrict_peers); + FFIErrorCode::Success as i32 +} + +/// Enables or disables masternode synchronization +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_enable_masternodes(enable); + FFIErrorCode::Success as i32 +} + /// Gets the network type from the configuration /// /// # Safety -/// - `config` must be a valid pointer to an FFIConfig or null +/// - `config` must be a valid pointer to an FFIClientConfig or null /// - If null, returns FFINetwork::Dash as default #[no_mangle] pub unsafe extern "C" fn dash_spv_ffi_config_get_network( @@ -494,11 +711,11 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_network( /// Gets the data directory path from the configuration /// /// # Safety -/// - `config` must be a valid pointer to an FFIConfig or null +/// - `config` must be a valid pointer to an FFIClientConfig or null /// - If null or no data directory is set, returns an FFIString with null pointer /// - The returned FFIString must be freed by the caller using `dash_spv_ffi_string_destroy` #[no_mangle] -pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( +pub unsafe extern "C" fn dash_spv_ffi_config_get_data_dir( config: *const FFIClientConfig, ) -> FFIString { if config.is_null() { @@ -512,6 +729,148 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_storage_path( 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 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] +pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) { + if !config.is_null() { + // Reclaim outer struct + let cfg = Box::from_raw(config); + // Free inner ClientConfig if present + if !cfg.inner.is_null() { + let _ = Box::from_raw(cfg.inner as *mut ClientConfig); + } + } +} + +impl FFIClientConfig { + pub fn get_inner(&self) -> &ClientConfig { + unsafe { &*(self.inner as *const ClientConfig) } + } + + pub fn clone_inner(&self) -> ClientConfig { + unsafe { (*(self.inner as *const ClientConfig)).clone() } + } +} + +/// Sets the number of Tokio worker threads for the FFI runtime (0 = auto) +/// +/// # 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, +) -> i32 { + null_check!(config); + let cfg = &mut *config; + cfg.worker_threads = threads; + FFIErrorCode::Success as i32 +} + +// Mempool configuration functions + +/// Enables or disables mempool tracking +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_enable_mempool_tracking(enable); + FFIErrorCode::Success as i32 +} + +/// Sets the mempool synchronization strategy +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_mempool_strategy(strategy.into()); + FFIErrorCode::Success as i32 +} + +/// Sets the maximum number of mempool transactions to track +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_max_mempool_transactions(max_transactions as usize); + FFIErrorCode::Success as i32 +} + +/// Sets whether to fetch full mempool transaction data +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_fetch_mempool_transactions(fetch); + FFIErrorCode::Success as i32 +} + +/// Sets whether to persist mempool state to disk +/// +/// # Safety +/// - `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, +) -> i32 { + null_check!(config); + + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_persist_mempool(persist); + FFIErrorCode::Success as i32 +} + /// Gets whether mempool tracking is enabled /// /// # Safety @@ -546,30 +905,23 @@ pub unsafe extern "C" fn dash_spv_ffi_config_get_mempool_strategy( config.mempool_strategy().into() } -/// Destroys an FFIConfig and frees its memory +// Checkpoint sync configuration functions + +/// Sets the starting block height for synchronization /// /// # 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 instance +/// - `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] -pub unsafe extern "C" fn dash_spv_ffi_config_destroy(config: *mut FFIClientConfig) { - if !config.is_null() { - // Reclaim outer struct - let cfg = Box::from_raw(config); - // Free inner ClientConfig if present - if !cfg.inner.is_null() { - let _ = Box::from_raw(cfg.inner as *mut ClientConfig); - } - } -} - -impl FFIClientConfig { - pub fn get_inner(&self) -> &ClientConfig { - unsafe { &*(self.inner as *const ClientConfig) } - } +#[deprecated] +#[allow(deprecated)] +pub unsafe extern "C" fn dash_spv_ffi_config_set_start_from_height( + config: *mut FFIClientConfig, + height: u32, +) -> i32 { + null_check!(config); - pub fn clone_inner(&self) -> ClientConfig { - unsafe { (*(self.inner as *const ClientConfig)).clone() } - } + let config = unsafe { &mut *((*config).inner as *mut ClientConfig) }; + config.set_start_from_height(height); + FFIErrorCode::Success as i32 } diff --git a/dash-spv-ffi/tests/c_tests/test_basic.c b/dash-spv-ffi/tests/c_tests/test_basic.c index 0457691d6..c0bb8b592 100644 --- a/dash-spv-ffi/tests/c_tests/test_basic.c +++ b/dash-spv-ffi/tests/c_tests/test_basic.c @@ -100,7 +100,7 @@ void test_config_getters() { TEST_ASSERT(network == FFINetwork_Testnet); // Test getting data directory - FFIString data_dir = dash_spv_ffi_config_get_storage_path(config); + FFIString data_dir = dash_spv_ffi_config_get_data_dir(config); if (data_dir.ptr != NULL) { TEST_ASSERT(strcmp(data_dir.ptr, "/tmp/test-dir") == 0); dash_spv_ffi_string_destroy(data_dir); diff --git a/dash-spv-ffi/tests/unit/test_configuration.rs b/dash-spv-ffi/tests/unit/test_configuration.rs index 5b410fdf2..d1369686f 100644 --- a/dash-spv-ffi/tests/unit/test_configuration.rs +++ b/dash-spv-ffi/tests/unit/test_configuration.rs @@ -257,7 +257,7 @@ mod tests { let net = dash_spv_ffi_config_get_network(std::ptr::null()); assert_eq!(net as i32, FFINetwork::Dash as i32); // Returns default - let dir = dash_spv_ffi_config_get_storage_path(std::ptr::null()); + let dir = dash_spv_ffi_config_get_data_dir(std::ptr::null()); assert!(dir.ptr.is_null()); // Test destroy with null (should be safe) diff --git a/dash-spv/src/client/config.rs b/dash-spv/src/client/config.rs index 56b9dfe4a..29fbe2b00 100644 --- a/dash-spv/src/client/config.rs +++ b/dash-spv/src/client/config.rs @@ -118,25 +118,34 @@ impl Default for ClientConfig { } impl ClientConfigBuilder { - pub fn mainnet() -> ClientConfigBuilder { + pub fn new(network: Network) -> Self { + match network { + Network::Dash => Self::mainnet(), + Network::Testnet => Self::testnet(), + Network::Devnet => Self::devnet(), + Network::Regtest => Self::regtest(), + _ => panic!("Unsupported network"), + } + } + pub fn mainnet() -> Self { let mut builder = Self::default(); builder.network(Network::Dash); builder } - pub fn testnet() -> ClientConfigBuilder { + pub fn testnet() -> Self { let mut builder = Self::default(); builder.network(Network::Testnet); builder } - pub fn devnet() -> ClientConfigBuilder { + pub fn devnet() -> Self { let mut builder = Self::default(); builder.network(Network::Devnet); builder } - pub fn regtest() -> ClientConfigBuilder { + pub fn regtest() -> Self { let peers = vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 19899)]; let mut builder = Self::default(); @@ -176,6 +185,84 @@ impl ClientConfigBuilder { } 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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + + #[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 + } + pub fn add_peer(&mut self, address: SocketAddr) -> &mut Self { self.peers.push(address); self diff --git a/dash-spv/src/client/core.rs b/dash-spv/src/client/core.rs index 3a8a3545c..ef5243522 100644 --- a/dash-spv/src/client/core.rs +++ b/dash-spv/src/client/core.rs @@ -267,6 +267,7 @@ impl DashSpvClient Result<()> { // Ensure network hasn't changed if new_config.network() != self.config.network() {