diff --git a/packages/rs-platform-wallet/src/spv/runtime.rs b/packages/rs-platform-wallet/src/spv/runtime.rs index 5f63ac27e19..d95054814e0 100644 --- a/packages/rs-platform-wallet/src/spv/runtime.rs +++ b/packages/rs-platform-wallet/src/spv/runtime.rs @@ -9,7 +9,7 @@ use dashcore::sml::llmq_type::LLMQType; use dashcore::{QuorumHash, Transaction}; use dash_spv::network::PeerNetworkManager; -use dash_spv::storage::DiskStorageManager; +use dash_spv::storage::{DiskStorageManager, StorageManager}; use dash_spv::sync::SyncProgress; use dash_spv::{ClientConfig, DashSpvClient, EventHandler, Hash}; @@ -31,6 +31,7 @@ pub struct SpvRuntime { event_manager: Arc, wallet_manager: Arc>>, client: RwLock>, + last_config: RwLock>, task: Mutex>>, } /// Classify a dash-spv broadcast failure per the @@ -69,6 +70,7 @@ impl SpvRuntime { event_manager, wallet_manager, client: RwLock::new(None), + last_config: RwLock::new(None), task: Mutex::new(None), } } @@ -96,6 +98,9 @@ impl SpvRuntime { // platform event manager's own handler list). let event_handlers: Vec> = vec![Arc::clone(&self.event_manager) as Arc]; + + let retained_config = config.clone(); + let spv_client = DashSpvClient::new( config, network_manager, @@ -108,6 +113,7 @@ impl SpvRuntime { let mut client = self.client.write().await; *client = Some(spv_client); + *self.last_config.write().await = Some(retained_config); Ok(()) } @@ -283,17 +289,37 @@ impl SpvRuntime { /// Clear all persisted SPV storage (headers, filters, state). /// - /// The SPV client must be running to perform this operation. + /// If the SPVClient is running it will be stopped and the + /// storage will be cleaned. If it is not running a tmp + /// Storage Manager built from the cached config will be used. pub async fn clear_storage(&self) -> Result<(), PlatformWalletError> { - let client_guard = self.client.read().await; - let client = client_guard.as_ref().ok_or(PlatformWalletError::SpvError( - "SPV Client not started".to_string(), - ))?; + // Fast path: a live client holds the storage lock; clear through it. + { + let client_guard = self.client.read().await; + if let Some(client) = client_guard.as_ref() { + return client + .clear_storage() + .await + .map_err(|e| PlatformWalletError::SpvError(e.to_string())); + } + } - client - .clear_storage() + let config = self.last_config.read().await.clone().ok_or_else(|| { + PlatformWalletError::SpvError( + "SPV storage location unknown; start the client at least once before clearing" + .to_string(), + ) + })?; + + let mut storage = DiskStorageManager::new(&config) .await - .map_err(|e| PlatformWalletError::SpvError(e.to_string())) + .map_err(|e| PlatformWalletError::SpvError(e.to_string()))?; + StorageManager::clear(&mut storage) + .await + .map_err(|e| PlatformWalletError::SpvError(e.to_string()))?; + StorageManager::shutdown(&mut storage).await; + + Ok(()) } /// Update the running SPV client's configuration. diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift index 5e99a16bf27..30d389b798b 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift @@ -237,8 +237,6 @@ extension PlatformWalletManager { } /// Clear all persisted SPV storage (headers, filters, state). - /// - /// The SPV client must be running. public func clearSpvStorage() throws { try platform_wallet_manager_spv_clear_storage(handle).check() } diff --git a/packages/swift-sdk/SwiftTests/SwiftDashSDKIntegrationTests/Core/SpvClearStorageIntegrationTests.swift b/packages/swift-sdk/SwiftTests/SwiftDashSDKIntegrationTests/Core/SpvClearStorageIntegrationTests.swift new file mode 100644 index 00000000000..c7a1dfd8b0b --- /dev/null +++ b/packages/swift-sdk/SwiftTests/SwiftDashSDKIntegrationTests/Core/SpvClearStorageIntegrationTests.swift @@ -0,0 +1,64 @@ +import XCTest +@testable import SwiftDashSDK + +/// Regression: clearing SPV storage must work *after* the client is stopped +final class SpvClearStorageIntegrationTests: IntegrationTestCase { + func testClearStorageAfterStopWipesDisk() async throws { + try await env.walletManager.startSpv(config: env.spvConfig) + try await env.walletManager.waitUntilUpToDate( + height: try await env.coreRPC.getBlockCount(), + timeout: 30 + ) + + let before = Self.dirSignature(env.spvDataDir) + XCTAssertFalse( + before.isEmpty, + "expected synced SPV data on disk before clearing" + ) + + try await env.walletManager.stopSpv() + let running = try await env.walletManager.isSpvRunning() + XCTAssertFalse(running, "SPV should be stopped") + + try await env.walletManager.clearSpvStorage() + + let after = Self.dirSignature(env.spvDataDir) + XCTAssertNotEqual( + after, before, + "clearSpvStorage after stopSpv must wipe the synced data on disk" + ) + + // The stopped-path clear must release the storage lock: a fresh start + // on the same data dir must still succeed (no leftover lockfile). + try await env.walletManager.startSpv(config: env.spvConfig) + try await env.walletManager.stopSpv() + } + + /// Sorted "relativePath:size" list of every regular file under `path`. + /// Mirrors the harness's own cache signature so a wipe is observable as a + /// change in the file/size set. + private static func dirSignature(_ path: String) -> String { + let fm = FileManager.default + let root = URL(fileURLWithPath: path).resolvingSymlinksInPath() + let rootCount = root.pathComponents.count + + guard let walker = fm.enumerator( + at: root, + includingPropertiesForKeys: [.isRegularFileKey, .fileSizeKey] + ) else { return "" } + + var entries: [String] = [] + for case let url as URL in walker { + let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .fileSizeKey]) + guard values?.isRegularFile == true else { continue } + + let rel = url.resolvingSymlinksInPath().pathComponents + .dropFirst(rootCount) + .joined(separator: "/") + + entries.append("\(rel):\(values?.fileSize ?? 0)") + } + + return entries.sorted().joined(separator: "\n") + } +}