Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions packages/rs-platform-wallet-ffi/src/spv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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()
}

Expand Down
3 changes: 0 additions & 3 deletions packages/rs-platform-wallet/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-platform-wallet/src/manager/accessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
}

/// Clone the `Arc<SpvRuntime>` so callers (e.g. FFI) can invoke
/// [`SpvRuntime::spawn_in_background`] which takes `&Arc<Self>`.
/// [`SpvRuntime::spawn_run_loop`] which takes `&Arc<Self>`.
pub fn spv_arc(&self) -> Arc<SpvRuntime> {
Arc::clone(&self.spv_manager)
}
Expand Down
48 changes: 25 additions & 23 deletions packages/rs-platform-wallet/src/spv/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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();
Expand All @@ -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> {
Comment thread
ZocoLini marked this conversation as resolved.
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);

Expand Down Expand Up @@ -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<Self>, config: ClientConfig) {
pub fn spawn_run_loop(self: &Arc<Self>) {
{
let existing = self.task.lock().expect("spv task mutex poisoned");
if existing.is_some() {
Expand All @@ -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);
}
});

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 3 additions & 9 deletions packages/rs-platform-wallet/tests/spv_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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);
}

Expand All @@ -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();
Expand Down
Loading