diff --git a/packages/rs-platform-wallet-ffi/src/spv.rs b/packages/rs-platform-wallet-ffi/src/spv.rs index 55ece2d51b..77e8fe0ac1 100644 --- a/packages/rs-platform-wallet-ffi/src/spv.rs +++ b/packages/rs-platform-wallet-ffi/src/spv.rs @@ -10,7 +10,7 @@ use platform_wallet::spv::{ use crate::error::*; use crate::handle::*; -use crate::runtime::runtime; +use crate::runtime::{block_on_worker, runtime}; use crate::types::FFINetwork; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; @@ -358,10 +358,23 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( config.devnet = Some(devnet); } - let _guard = runtime().enter(); - manager.spv_arc().spawn_in_background(config); + let spv = manager.spv_arc(); + let start_result = { + let spv = spv.clone(); + block_on_worker(async move { spv.start(config).await }) + }; + + if start_result.is_ok() { + let _guard = runtime().enter(); + spv.spawn_run_loop(); + } + + start_result }); - unwrap_option_or_return!(option); + + let start_result = unwrap_option_or_return!(option); + unwrap_result_or_return!(start_result); + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet/src/error.rs b/packages/rs-platform-wallet/src/error.rs index 189772d10e..c94cb7093d 100644 --- a/packages/rs-platform-wallet/src/error.rs +++ b/packages/rs-platform-wallet/src/error.rs @@ -149,9 +149,6 @@ pub enum PlatformWalletError { #[error("No wallets configured — add a wallet before starting SPV")] NoWalletsConfigured, - #[error("SPV client is not running")] - SpvNotRunning, - #[error("SPV error: {0}")] SpvError(String), diff --git a/packages/rs-platform-wallet/src/manager/accessors.rs b/packages/rs-platform-wallet/src/manager/accessors.rs index cdf679bef2..7bf901bccf 100644 --- a/packages/rs-platform-wallet/src/manager/accessors.rs +++ b/packages/rs-platform-wallet/src/manager/accessors.rs @@ -256,7 +256,7 @@ impl PlatformWalletManager

{ } /// Clone the `Arc` so callers (e.g. FFI) can invoke - /// [`SpvRuntime::spawn_in_background`] which takes `&Arc`. + /// [`SpvRuntime::spawn_run_loop`] which takes `&Arc`. pub fn spv_arc(&self) -> Arc { Arc::clone(&self.spv_manager) } diff --git a/packages/rs-platform-wallet/src/spv/runtime.rs b/packages/rs-platform-wallet/src/spv/runtime.rs index 215f3022bb..d2c0674254 100644 --- a/packages/rs-platform-wallet/src/spv/runtime.rs +++ b/packages/rs-platform-wallet/src/spv/runtime.rs @@ -97,9 +97,9 @@ impl SpvRuntime { tx: &Transaction, ) -> Result<(), PlatformWalletError> { let client_guard = self.client.read().await; - let client = client_guard - .as_ref() - .ok_or(PlatformWalletError::SpvNotRunning)?; + let client = client_guard.as_ref().ok_or(PlatformWalletError::SpvError( + "SPV Client not started".to_string(), + ))?; client .broadcast_transaction(tx) @@ -117,9 +117,9 @@ impl SpvRuntime { height: u32, ) -> Result<[u8; 48], PlatformWalletError> { let client_guard = self.client.read().await; - let client = client_guard - .as_ref() - .ok_or(PlatformWalletError::SpvNotRunning)?; + let client = client_guard.as_ref().ok_or(PlatformWalletError::SpvError( + "SPV Client not started".to_string(), + ))?; let llmq_type = LLMQType::from(quorum_type as u8); let qh = QuorumHash::from_byte_array(quorum_hash).reverse(); @@ -132,16 +132,15 @@ impl SpvRuntime { Ok(*quorum.quorum_entry.quorum_public_key.as_ref()) } - /// Run the SPV sync loop until calling [`stop`]. This blocks the current thread. - pub async fn run(&self, config: ClientConfig) -> Result<(), PlatformWalletError> { - tracing::info!("SpvRuntime::run() starting client..."); - self.start(config).await?; - tracing::info!("SpvRuntime::run() client started, entering sync loop"); - + /// Drive the sync loop of an already-[`start`]ed client until [`stop`] + /// is called + async fn run(&self) -> Result<(), PlatformWalletError> { let client_guard = self.client.read().await; let client = client_guard .as_ref() - .ok_or(PlatformWalletError::SpvNotRunning)? + .ok_or(PlatformWalletError::SpvError( + "SPV Client not started".to_string(), + ))? .clone(); drop(client_guard); @@ -189,10 +188,11 @@ impl SpvRuntime { stop_result } - /// Spawn `run()` on the current tokio runtime and return immediately. + /// Spawn the sync loop of an already-[`start`]ed client on the current + /// tokio runtime and return immediately. /// /// Call [`stop`] to stop it - pub fn spawn_in_background(self: &Arc, config: ClientConfig) { + pub fn spawn_run_loop(self: &Arc) { { let existing = self.task.lock().expect("spv task mutex poisoned"); if existing.is_some() { @@ -206,8 +206,8 @@ impl SpvRuntime { let this = Arc::clone(self); let handle = tokio::spawn(async move { - if let Err(e) = this.run(config).await { - tracing::warn!("SpvRuntime background run exited with error: {}", e); + if let Err(e) = this.run().await { + tracing::warn!("SpvRuntime background run loop exited with error: {}", e); } }); @@ -252,9 +252,10 @@ impl SpvRuntime { /// The SPV client must be running to perform this operation. pub async fn clear_storage(&self) -> Result<(), PlatformWalletError> { let client_guard = self.client.read().await; - let client = client_guard - .as_ref() - .ok_or(PlatformWalletError::SpvNotRunning)?; + let client = client_guard.as_ref().ok_or(PlatformWalletError::SpvError( + "SPV Client not started".to_string(), + ))?; + client .clear_storage() .await @@ -266,9 +267,10 @@ impl SpvRuntime { /// The network cannot be changed on a running client. pub async fn update_config(&self, config: ClientConfig) -> Result<(), PlatformWalletError> { let client_guard = self.client.read().await; - let client = client_guard - .as_ref() - .ok_or(PlatformWalletError::SpvNotRunning)?; + let client = client_guard.as_ref().ok_or(PlatformWalletError::SpvError( + "SPV Client not started".to_string(), + ))?; + client .update_config(config) .await diff --git a/packages/rs-platform-wallet/tests/spv_sync.rs b/packages/rs-platform-wallet/tests/spv_sync.rs index b91206de05..adcbd4a312 100644 --- a/packages/rs-platform-wallet/tests/spv_sync.rs +++ b/packages/rs-platform-wallet/tests/spv_sync.rs @@ -219,13 +219,9 @@ async fn test_spv_sync_and_balance() { } // --- Start SPV in background --- - let manager_for_spv = Arc::clone(&manager); - let spv_handle = tokio::spawn(async move { - if let Err(e) = manager_for_spv.spv().run(config).await { - eprintln!("SPV runtime error: {}", e); - } - }); - + let spv = manager.spv_arc(); + spv.start(config).await.unwrap(); + spv.spawn_run_loop(); // --- Wait for confirmed balance --- // Cold start needs to sync full testnet chain headers (~1M+ blocks). // Second run with cached state is much faster (~20s). @@ -241,7 +237,6 @@ async fn test_spv_sync_and_balance() { loop { if start.elapsed() > timeout { let _ = manager.spv().stop().await; - let _ = spv_handle.await; panic!("Timeout waiting for wallet balance after {:?}", timeout); } @@ -266,7 +261,6 @@ async fn test_spv_sync_and_balance() { if confirmed > 0 { println!("SUCCESS: Wallet has confirmed balance: {} duffs", confirmed); let _ = manager.spv().stop().await; - let _ = spv_handle.await; // --- Verify persistence --- let core_stores = persister_for_check.core_store_count();