From 6eb39561ec1f87e0c6c9d01398d97a9dc85f75e9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 19 Jul 2023 18:13:59 +0100 Subject: [PATCH] Migrate to Rust backend with fast spendability support --- Package.resolved | 2 +- Package.swift | 2 +- .../Block/Actions/Action.swift | 1 - .../Block/Actions/DownloadAction.swift | 2 +- .../Block/Actions/ValidateAction.swift | 28 ----- .../Block/CompactBlockProcessor.swift | 4 - .../Block/Scan/BlockScanner.swift | 5 +- .../Block/Validate/BlockValidator.swift | 51 -------- .../Entity/SubtreeRoot.swift | 13 ++ .../Error/ZcashError.swift | 23 ++++ .../Error/ZcashErrorCode.swift | 8 ++ .../ZcashLightClientKit/Model/ScanRange.swift | 13 ++ .../Rust/ZcashRustBackend.swift | 116 +++++++++++++++--- .../Rust/ZcashRustBackendWelding.swift | 48 ++++---- .../Synchronizer/Dependencies.swift | 12 -- .../DownloadActionTests.swift | 12 +- .../ValidateActionTests.swift | 36 ------ Tests/TestUtils/Sourcery/AutoMockable.swift | 1 - .../AutoMockable.generated.swift | 91 ++++++-------- Tests/TestUtils/Stubs.swift | 34 +---- 20 files changed, 232 insertions(+), 270 deletions(-) delete mode 100644 Sources/ZcashLightClientKit/Block/Actions/ValidateAction.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Validate/BlockValidator.swift create mode 100644 Sources/ZcashLightClientKit/Entity/SubtreeRoot.swift create mode 100644 Sources/ZcashLightClientKit/Model/ScanRange.swift delete mode 100644 Tests/OfflineTests/CompactBlockProcessorActions/ValidateActionTests.swift diff --git a/Package.resolved b/Package.resolved index 96295130..71744ee2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -104,7 +104,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "cdbc06f10b2d7cbe0d6362b30f68167825942e86" + "revision" : "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125" } } ], diff --git a/Package.swift b/Package.swift index f92ed9a3..4de64717 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"), - .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "cdbc06f10b2d7cbe0d6362b30f68167825942e86") + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125") ], targets: [ .target( diff --git a/Sources/ZcashLightClientKit/Block/Actions/Action.swift b/Sources/ZcashLightClientKit/Block/Actions/Action.swift index 699122ac..b3b1f350 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/Action.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/Action.swift @@ -36,7 +36,6 @@ enum CBPState: CaseIterable { case validateServer case computeSyncControlData case download - case validate case scan case clearAlreadyScannedBlocks case enhance diff --git a/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift b/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift index dfd34061..951115e4 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift @@ -21,7 +21,7 @@ final class DownloadAction { } private func update(context: ActionContext) async -> ActionContext { - await context.update(state: .validate) + await context.update(state: .scan) return context } } diff --git a/Sources/ZcashLightClientKit/Block/Actions/ValidateAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ValidateAction.swift deleted file mode 100644 index 7af1f343..00000000 --- a/Sources/ZcashLightClientKit/Block/Actions/ValidateAction.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ValidateAction.swift -// -// -// Created by Michal Fousek on 05.05.2023. -// - -import Foundation - -final class ValidateAction { - let validator: BlockValidator - - init(container: DIContainer) { - validator = container.resolve(BlockValidator.self) - } -} - -extension ValidateAction: Action { - var removeBlocksCacheWhenFailed: Bool { true } - - func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext { - try await validator.validate() - await context.update(state: .scan) - return context - } - - func stop() async { } -} diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 2ebf0a71..aa08f09f 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -218,8 +218,6 @@ actor CompactBlockProcessor { action = ComputeSyncControlDataAction(container: container, configProvider: configProvider) case .download: action = DownloadAction(container: container, configProvider: configProvider) - case .validate: - action = ValidateAction(container: container) case .scan: action = ScanAction(container: container, configProvider: configProvider) case .clearAlreadyScannedBlocks: @@ -591,8 +589,6 @@ extension CompactBlockProcessor { break case .download: break - case .validate: - break case .scan: break case .clearAlreadyScannedBlocks: diff --git a/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift b/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift index 815e159b..c4e1a1d9 100644 --- a/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift +++ b/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift @@ -50,13 +50,14 @@ extension BlockScannerImpl: BlockScanner { try Task.checkCancellation() let previousScannedHeight = lastScannedHeight + let startHeight = previousScannedHeight + 1 // TODO: [#576] remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576 - let batchSize = scanBatchSize(startScanHeight: previousScannedHeight + 1, network: config.networkType) + let batchSize = scanBatchSize(startScanHeight: startHeight, network: config.networkType) let scanStartTime = Date() do { - try await self.rustBackend.scanBlocks(limit: batchSize) + try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize) } catch { logger.debug("block scanning failed with error: \(String(describing: error))") throw error diff --git a/Sources/ZcashLightClientKit/Block/Validate/BlockValidator.swift b/Sources/ZcashLightClientKit/Block/Validate/BlockValidator.swift deleted file mode 100644 index 1b9c71eb..00000000 --- a/Sources/ZcashLightClientKit/Block/Validate/BlockValidator.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// CompactBlockValidationInformation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/30/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -protocol BlockValidator { - /// Validate all the downloaded blocks that haven't been yet validated. - func validate() async throws -} - -struct BlockValidatorImpl { - let rustBackend: ZcashRustBackendWelding - let metrics: SDKMetrics - let logger: Logger -} - -extension BlockValidatorImpl: BlockValidator { - /// - Throws: - /// - `rustValidateCombinedChainValidationFailed` if there was an error during validation unrelated to chain validity. - /// - `rustValidateCombinedChainInvalidChain(upperBound)` if the combined chain is invalid. `upperBound` is the height of the highest invalid - /// block(on the assumption that the highest block in the cache database is correct). - func validate() async throws { - try Task.checkCancellation() - - let startTime = Date() - do { - try await rustBackend.validateCombinedChain(limit: 0) - pushProgressReport(startTime: startTime, finishTime: Date()) - logger.debug("validateChainFinished") - } catch { - logger.debug("Validate chain failed with \(error)") - pushProgressReport(startTime: startTime, finishTime: Date()) - throw error - } - } - - private func pushProgressReport(startTime: Date, finishTime: Date) { - metrics.pushProgressReport( - progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0), - start: startTime, - end: finishTime, - batchSize: 0, - operation: .validateBlocks - ) - } -} diff --git a/Sources/ZcashLightClientKit/Entity/SubtreeRoot.swift b/Sources/ZcashLightClientKit/Entity/SubtreeRoot.swift new file mode 100644 index 00000000..bab287b6 --- /dev/null +++ b/Sources/ZcashLightClientKit/Entity/SubtreeRoot.swift @@ -0,0 +1,13 @@ +// +// SubtreeRoot.swift +// +// +// Created by Jack Grigg on 19/07/2023. +// + +import Foundation + +public struct SubtreeRoot { + public let rootHash: Data + public let completingBlockHeight: BlockHeight +} diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index 0e86e33a..4c3901bb 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -276,6 +276,21 @@ public enum ZcashError: Equatable, Error { /// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver /// ZRUST0045 case rustGetTransparentReceiverInvalidReceiver + /// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots + /// ZRUST0046 + case rustPutSaplingSubtreeRootsAllocationProblem + /// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots + /// - `rustError` contains error generated by the rust layer. + /// ZRUST0047 + case rustPutSaplingSubtreeRoots(_ rustError: String) + /// Error from rust layer when calling ZcashRustBackend.updateChainTip + /// - `rustError` contains error generated by the rust layer. + /// ZRUST0048 + case rustUpdateChainTip(_ rustError: String) + /// Error from rust layer when calling ZcashRustBackend.suggestScanRanges + /// - `rustError` contains error generated by the rust layer. + /// ZRUST0049 + case rustSuggestScanRanges(_ rustError: String) /// SQLite query failed when fetching all accounts from the database. /// - `sqliteError` is error produced by SQLite library. /// ZADAO0001 @@ -605,6 +620,10 @@ public enum ZcashError: Equatable, Error { case .rustGetSaplingReceiverInvalidReceiver: return "Sapling receiver generated by rust layer is invalid when calling ZcashRustBackend.getSaplingReceiver" case .rustGetTransparentReceiverInvalidAddress: return "Error from rust layer when calling ZcashRustBackend.getTransparentReceiver" case .rustGetTransparentReceiverInvalidReceiver: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver" + case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to store subtree roots when calling ZcashRustBackend.putSaplingSubtreeRoots" + case .rustPutSaplingSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots" + case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip" + case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges" case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database." case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them." case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database." @@ -763,6 +782,10 @@ public enum ZcashError: Equatable, Error { case .rustGetSaplingReceiverInvalidReceiver: return .rustGetSaplingReceiverInvalidReceiver case .rustGetTransparentReceiverInvalidAddress: return .rustGetTransparentReceiverInvalidAddress case .rustGetTransparentReceiverInvalidReceiver: return .rustGetTransparentReceiverInvalidReceiver + case .rustPutSaplingSubtreeRootsAllocationProblem: return .rustPutSaplingSubtreeRootsAllocationProblem + case .rustPutSaplingSubtreeRoots: return .rustPutSaplingSubtreeRoots + case .rustUpdateChainTip: return .rustUpdateChainTip + case .rustSuggestScanRanges: return .rustSuggestScanRanges case .accountDAOGetAll: return .accountDAOGetAll case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode case .accountDAOFindBy: return .accountDAOFindBy diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index 83a7ff9b..9648861d 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -153,6 +153,14 @@ public enum ZcashErrorCode: String { case rustGetTransparentReceiverInvalidAddress = "ZRUST0044" /// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver case rustGetTransparentReceiverInvalidReceiver = "ZRUST0045" + /// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots + case rustPutSaplingSubtreeRootsAllocationProblem = "ZRUST0046" + /// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots + case rustPutSaplingSubtreeRoots = "ZRUST0047" + /// Error from rust layer when calling ZcashRustBackend.updateChainTip + case rustUpdateChainTip = "ZRUST0048" + /// Error from rust layer when calling ZcashRustBackend.suggestScanRanges + case rustSuggestScanRanges = "ZRUST0049" /// SQLite query failed when fetching all accounts from the database. case accountDAOGetAll = "ZADAO0001" /// Fetched accounts from SQLite but can't decode them. diff --git a/Sources/ZcashLightClientKit/Model/ScanRange.swift b/Sources/ZcashLightClientKit/Model/ScanRange.swift new file mode 100644 index 00000000..cd6401db --- /dev/null +++ b/Sources/ZcashLightClientKit/Model/ScanRange.swift @@ -0,0 +1,13 @@ +// +// ScanRange.swift +// +// +// Created by Jack Grigg on 17/07/2023. +// + +import Foundation + +struct ScanRange { + let range: Range + let priority: UInt8 +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index dfc82eaf..3d12179b 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -490,21 +490,6 @@ actor ZcashRustBackend: ZcashRustBackendWelding { } } - func validateCombinedChain(limit: UInt32 = 0) async throws { - let result = zcashlc_validate_combined_chain(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId) - - switch result { - case -1: - return - case 0: - throw ZcashError.rustValidateCombinedChainValidationFailed( - lastErrorMessage(fallback: "`validateCombinedChain` failed with unknown error") - ) - default: - throw ZcashError.rustValidateCombinedChainInvalidChain(result) - } - } - func rewindToHeight(height: Int32) async throws { let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId) @@ -521,8 +506,97 @@ actor ZcashRustBackend: ZcashRustBackendWelding { } } - func scanBlocks(limit: UInt32 = 0) async throws { - let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId) + func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws { + var ffiSubtreeRootsVec: [FfiSubtreeRoot] = [] + + for root in roots { + let hashPtr = UnsafeMutablePointer.allocate(capacity: root.rootHash.count) + + let contiguousHashBytes = ContiguousArray(root.rootHash.bytes) + + let result: Void? = contiguousHashBytes.withContiguousStorageIfAvailable { hashBytesPtr in + // swiftlint:disable:next force_unwrapping + hashPtr.initialize(from: hashBytesPtr.baseAddress!, count: hashBytesPtr.count) + } + + guard result != nil else { + defer { + hashPtr.deallocate() + ffiSubtreeRootsVec.deallocateElements() + } + throw ZcashError.rustPutSaplingSubtreeRootsAllocationProblem + } + + ffiSubtreeRootsVec.append( + FfiSubtreeRoot( + root_hash_ptr: hashPtr, + root_hash_ptr_len: UInt(contiguousHashBytes.count), + completing_block_height: UInt32(root.completingBlockHeight) + ) + ) + } + + var contiguousFfiRoots = ContiguousArray(ffiSubtreeRootsVec) + + let len = UInt(contiguousFfiRoots.count) + + let rootsPtr = UnsafeMutablePointer.allocate(capacity: 1) + + defer { ffiSubtreeRootsVec.deallocateElements() } + + try contiguousFfiRoots.withContiguousMutableStorageIfAvailable { ptr in + var roots = FfiSubtreeRoots() + roots.ptr = ptr.baseAddress + roots.len = len + + rootsPtr.initialize(to: roots) + + let res = zcashlc_put_sapling_subtree_roots(dbData.0, dbData.1, startIndex, rootsPtr, networkType.networkId) + + guard res else { + throw ZcashError.rustPutSaplingSubtreeRoots(lastErrorMessage(fallback: "`putSaplingSubtreeRoots` failed with unknown error")) + } + } + } + + func updateChainTip(height: Int32) async throws { + let result = zcashlc_update_chain_tip(dbData.0, dbData.1, height, networkType.networkId) + + guard result else { + throw ZcashError.rustUpdateChainTip(lastErrorMessage(fallback: "`updateChainTip` failed with unknown error")) + } + } + + func suggestScanRanges() async throws -> [ScanRange] { + let scanRangesPtr = zcashlc_suggest_scan_ranges(dbData.0, dbData.1, networkType.networkId) + + guard let scanRangesPtr else { + throw ZcashError.rustSuggestScanRanges(lastErrorMessage(fallback: "`suggestScanRanges` failed with unknown error")) + } + + defer { zcashlc_free_scan_ranges(scanRangesPtr) } + + var scanRanges: [ScanRange] = [] + + for i in (0 ..< Int(scanRangesPtr.pointee.len)) { + let scanRange = scanRangesPtr.pointee.ptr.advanced(by: i).pointee + + scanRanges.append( + ScanRange( + range: Range(uncheckedBounds: ( + BlockHeight(scanRange.start), + BlockHeight(scanRange.end) + )), + priority: scanRange.priority + ) + ) + } + + return scanRanges + } + + func scanBlocks(fromHeight: Int32, limit: UInt32 = 0) async throws { + let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, fromHeight, limit, networkType.networkId) guard result != 0 else { throw ZcashError.rustScanBlocks(lastErrorMessage(fallback: "`scanBlocks` failed with unknown error")) @@ -657,3 +731,11 @@ extension Array where Element == FFIBlockMeta { } } } + +extension Array where Element == FfiSubtreeRoot { + func deallocateElements() { + self.forEach { element in + element.root_hash_ptr.deallocate() + } + } +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index 1b3c39cb..78367fb5 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -159,26 +159,6 @@ protocol ZcashRustBackendWelding { /// - `rustGetVerifiedTransparentBalance` if rust layer returns error. func getVerifiedTransparentBalance(account: Int32) async throws -> Int64 - /// Checks that the scanned blocks in the data database, when combined with the recent - /// `CompactBlock`s in the cache database, form a valid chain. - /// This function is built on the core assumption that the information provided in the - /// cache database is more likely to be accurate than the previously-scanned information. - /// This follows from the design (and trust) assumption that the `lightwalletd` server - /// provides accurate block information as of the time it was requested. - /// - parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is. - /// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename - /// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions. - /// - parameter dbData: location of the data db file - /// - parameter networkType: the network type - /// - parameter limit: a limit to validate a fixed number of blocks instead of the whole cache. - /// - Throws: - /// - `rustValidateCombinedChainValidationFailed` if there was an error during validation unrelated to chain validity. - /// - `rustValidateCombinedChainInvalidChain(upperBound)` if the combined chain is invalid. `upperBound` is the height of the highest invalid - /// block(on the assumption that the highest block in the cache database is correct). - /// - /// - Important: This function does not mutate either of the databases. - func validateCombinedChain(limit: UInt32) async throws - /// Resets the state of the database to only contain block and transaction information up to the given height. clears up all derived data as well /// - parameter height: height to rewind to. /// - Throws: `rustRewindToHeight` if rust layer returns error. @@ -190,21 +170,35 @@ protocol ZcashRustBackendWelding { /// - Throws: `rustRewindCacheToHeight` if rust layer returns error. func rewindCacheToHeight(height: Int32) async throws + /// Returns a list of suggested scan ranges based upon the current wallet state. + /// + /// This method should only be used in cases where the `CompactBlock` data that will be + /// made available to `scanBlocks` for the requested block ranges includes note + /// commitment tree size information for each block; or else the scan is likely to fail if + /// notes belonging to the wallet are detected. + func suggestScanRanges() async throws -> [ScanRange] + /// Scans new blocks added to the cache for any transactions received by the tracked - /// accounts. - /// This function pays attention only to cached blocks with heights greater than the - /// highest scanned block in `db_data`. Cached blocks with lower heights are not verified - /// against previously-scanned blocks. In particular, this function **assumes** that the - /// caller is handling rollbacks. + /// accounts, while checking that they form a valid chan. + /// + /// This function is built on the core assumption that the information provided in the + /// block cache is more likely to be accurate than the previously-scanned information. + /// This follows from the design (and trust) assumption that the `lightwalletd` server + /// provides accurate block information as of the time it was requested. + /// + /// This function **assumes** that the caller is handling rollbacks. + /// /// For brand-new light client databases, this function starts scanning from the Sapling /// activation height. This height can be fast-forwarded to a more recent block by calling /// [`initBlocksTable`] before this function. + /// /// Scanned blocks are required to be height-sequential. If a block is missing from the /// cache, an error will be signalled. /// - /// - parameter limit: scan up to limit blocks. pass 0 to set no limit. + /// - parameter fromHeight: scan starting from the given height. + /// - parameter limit: scan up to limit blocks. /// - Throws: `rustScanBlocks` if rust layer returns error. - func scanBlocks(limit: UInt32) async throws + func scanBlocks(fromHeight: Int32, limit: UInt32) async throws /// Upserts a UTXO into the data db database /// - parameter txid: the txid bytes for the UTXO diff --git a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift index 2ff15c92..9e3b40c2 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift @@ -114,18 +114,6 @@ enum Dependencies { logger: logger ) } - - container.register(type: BlockValidator.self, isSingleton: true) { di in - let rustBackend = di.resolve(ZcashRustBackendWelding.self) - let metrics = di.resolve(SDKMetrics.self) - let logger = di.resolve(Logger.self) - - return BlockValidatorImpl( - rustBackend: rustBackend, - metrics: metrics, - logger: logger - ) - } container.register(type: BlockScanner.self, isSingleton: true) { di in let rustBackend = di.resolve(ZcashRustBackendWelding.self) diff --git a/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift b/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift index 37a81b98..077f013f 100644 --- a/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift +++ b/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift @@ -48,8 +48,8 @@ final class DownloadActionTests: ZcashTestCase { let nextState = await nextContext.state XCTAssertTrue( - nextState == .validate, - "nextContext after .download is expected to be .validate but received \(nextState)" + nextState == .scan, + "nextContext after .download is expected to be .scan but received \(nextState)" ) } catch { XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)") @@ -84,8 +84,8 @@ final class DownloadActionTests: ZcashTestCase { let nextState = await nextContext.state XCTAssertTrue( - nextState == .validate, - "nextContext after .download is expected to be .validate but received \(nextState)" + nextState == .scan, + "nextContext after .download is expected to be .scan but received \(nextState)" ) } catch { XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)") @@ -122,8 +122,8 @@ final class DownloadActionTests: ZcashTestCase { let nextState = await nextContext.state XCTAssertTrue( - nextState == .validate, - "nextContext after .download is expected to be .validate but received \(nextState)" + nextState == .scan, + "nextContext after .download is expected to be .scan but received \(nextState)" ) } catch { XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)") diff --git a/Tests/OfflineTests/CompactBlockProcessorActions/ValidateActionTests.swift b/Tests/OfflineTests/CompactBlockProcessorActions/ValidateActionTests.swift deleted file mode 100644 index 9f3ba023..00000000 --- a/Tests/OfflineTests/CompactBlockProcessorActions/ValidateActionTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ValidateActionTests.swift -// -// -// Created by Lukáš Korba on 17.05.2023. -// - -import XCTest -@testable import TestUtils -@testable import ZcashLightClientKit - -final class ValidateActionTests: ZcashTestCase { - func testValidateAction_NextAction() async throws { - let blockValidatorMock = BlockValidatorMock() - - blockValidatorMock.validateClosure = { } - - mockContainer.mock(type: BlockValidator.self, isSingleton: true) { _ in blockValidatorMock } - - let validateAction = ValidateAction( - container: mockContainer - ) - - do { - let nextContext = try await validateAction.run(with: .init(state: .validate)) { _ in } - XCTAssertTrue(blockValidatorMock.validateCalled, "validator.validate() is expected to be called.") - let nextState = await nextContext.state - XCTAssertTrue( - nextState == .scan, - "nextContext after .validate is expected to be .scan but received \(nextState)" - ) - } catch { - XCTFail("testValidateAction_NextAction is not expected to fail. \(error)") - } - } -} diff --git a/Tests/TestUtils/Sourcery/AutoMockable.swift b/Tests/TestUtils/Sourcery/AutoMockable.swift index ed583185..fa55cf69 100644 --- a/Tests/TestUtils/Sourcery/AutoMockable.swift +++ b/Tests/TestUtils/Sourcery/AutoMockable.swift @@ -16,7 +16,6 @@ extension BlockDownloader { } extension BlockDownloaderService { } extension BlockEnhancer { } extension BlockScanner { } -extension BlockValidator { } extension CompactBlockRepository { } extension LatestBlocksDataProvider { } extension LightWalletdInfo { } diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 3b82c5e3..3f11e9d1 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -378,31 +378,6 @@ class BlockScannerMock: BlockScanner { } } -} -class BlockValidatorMock: BlockValidator { - - - init( - ) { - } - - // MARK: - validate - - var validateThrowableError: Error? - var validateCallsCount = 0 - var validateCalled: Bool { - return validateCallsCount > 0 - } - var validateClosure: (() async throws -> Void)? - - func validate() async throws { - if let error = validateThrowableError { - throw error - } - validateCallsCount += 1 - try await validateClosure!() - } - } class CompactBlockRepositoryMock: CompactBlockRepository { @@ -2555,31 +2530,6 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { } } - // MARK: - validateCombinedChain - - var validateCombinedChainLimitThrowableError: Error? - func setValidateCombinedChainLimitThrowableError(_ param: Error?) async { - validateCombinedChainLimitThrowableError = param - } - var validateCombinedChainLimitCallsCount = 0 - var validateCombinedChainLimitCalled: Bool { - return validateCombinedChainLimitCallsCount > 0 - } - var validateCombinedChainLimitReceivedLimit: UInt32? - var validateCombinedChainLimitClosure: ((UInt32) async throws -> Void)? - func setValidateCombinedChainLimitClosure(_ param: ((UInt32) async throws -> Void)?) async { - validateCombinedChainLimitClosure = param - } - - func validateCombinedChain(limit: UInt32) async throws { - if let error = validateCombinedChainLimitThrowableError { - throw error - } - validateCombinedChainLimitCallsCount += 1 - validateCombinedChainLimitReceivedLimit = limit - try await validateCombinedChainLimitClosure!(limit) - } - // MARK: - rewindToHeight var rewindToHeightHeightThrowableError: Error? @@ -2630,6 +2580,37 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { try await rewindCacheToHeightHeightClosure!(height) } + // MARK: - suggestScanRanges + + var suggestScanRangesThrowableError: Error? + func setSuggestScanRangesThrowableError(_ param: Error?) async { + suggestScanRangesThrowableError = param + } + var suggestScanRangesCallsCount = 0 + var suggestScanRangesCalled: Bool { + return suggestScanRangesCallsCount > 0 + } + var suggestScanRangesReturnValue: [ScanRange]! + func setSuggestScanRangesReturnValue(_ param: [ScanRange]) async { + suggestScanRangesReturnValue = param + } + var suggestScanRangesClosure: (() async throws -> [ScanRange])? + func setSuggestScanRangesClosure(_ param: (() async throws -> [ScanRange])?) async { + suggestScanRangesClosure = param + } + + func suggestScanRanges() async throws -> [ScanRange] { + if let error = suggestScanRangesThrowableError { + throw error + } + suggestScanRangesCallsCount += 1 + if let closure = suggestScanRangesClosure { + return try await closure() + } else { + return suggestScanRangesReturnValue + } + } + // MARK: - scanBlocks var scanBlocksLimitThrowableError: Error? @@ -2640,19 +2621,21 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { var scanBlocksLimitCalled: Bool { return scanBlocksLimitCallsCount > 0 } + var scanBlocksLimitReceivedFromHeight: Int32? var scanBlocksLimitReceivedLimit: UInt32? - var scanBlocksLimitClosure: ((UInt32) async throws -> Void)? - func setScanBlocksLimitClosure(_ param: ((UInt32) async throws -> Void)?) async { + var scanBlocksLimitClosure: ((Int32, UInt32) async throws -> Void)? + func setScanBlocksLimitClosure(_ param: ((Int32, UInt32) async throws -> Void)?) async { scanBlocksLimitClosure = param } - func scanBlocks(limit: UInt32) async throws { + func scanBlocks(fromHeight: Int32, limit: UInt32) async throws { if let error = scanBlocksLimitThrowableError { throw error } scanBlocksLimitCallsCount += 1 + scanBlocksLimitReceivedFromHeight = fromHeight scanBlocksLimitReceivedLimit = limit - try await scanBlocksLimitClosure!(limit) + try await scanBlocksLimitClosure!(fromHeight, limit) } // MARK: - putUnspentTransparentOutput diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 8350bab0..39576c8e 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -132,40 +132,18 @@ class RustBackendMockHelper { return try await rustBackend.getVerifiedBalance(account: account) } - await rustBackendMock.setValidateCombinedChainLimitClosure() { [weak self] limit in - guard let self else { throw ZcashError.rustValidateCombinedChainValidationFailed("Self is nil") } - if let rate = mockValidateCombinedChainSuccessRate { - if Self.shouldSucceed(successRate: rate) { - return try await rustBackend.validateCombinedChain(limit: limit) - } else { - throw mockValidateCombinedChainFailureError - } - } else if let attempts = self.mockValidateCombinedChainFailAfterAttempts { - self.mockValidateCombinedChainFailAfterAttempts = attempts - 1 - if attempts > 0 { - return try await rustBackend.validateCombinedChain(limit: limit) - } else { - if attempts == 0 { - throw mockValidateCombinedChainFailureError - } else if attempts < 0 && mockValidateCombinedChainKeepFailing { - throw mockValidateCombinedChainFailureError - } else { - return try await rustBackend.validateCombinedChain(limit: limit) - } - } - } else { - return try await rustBackend.validateCombinedChain(limit: limit) - } - } - await rustBackendMock.setRewindToHeightHeightClosure() { height in try await rustBackend.rewindToHeight(height: height) } await rustBackendMock.setRewindCacheToHeightHeightClosure() { _ in } - await rustBackendMock.setScanBlocksLimitClosure() { limit in - try await rustBackend.scanBlocks(limit: limit) + await rustBackendMock.setSuggestScanRangesClosure() { + try await rustBackend.suggestScanRanges() + } + + await rustBackendMock.setScanBlocksLimitClosure() { fromHeight, limit in + try await rustBackend.scanBlocks(fromHeight: fromHeight, limit: limit) } }