From 416fabaede82c6ed19354182ef36be5ab4512604 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Sun, 7 Jun 2026 23:31:35 -0700 Subject: [PATCH] fix(platform-wallet-ffi): propagate SPV start errors to caller --- packages/rs-platform-wallet-ffi/src/spv.rs | 21 ++++++-- packages/rs-platform-wallet/src/error.rs | 3 -- .../src/manager/accessors.rs | 2 +- .../rs-platform-wallet/src/spv/runtime.rs | 48 ++++++++++--------- packages/rs-platform-wallet/tests/spv_sync.rs | 12 ++--- 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/packages/rs-platform-wallet-ffi/src/spv.rs b/packages/rs-platform-wallet-ffi/src/spv.rs index 55ece2d51b3..77e8fe0ac1d 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 189772d10e0..c94cb7093d1 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 cdf679bef28..7bf901bccf4 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 215f3022bb7..d2c06742546 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 b91206de056..adcbd4a3121 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();