From 29e06d0b8a091c430b5bf4ccfcd78911a46aaf2d Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 24 Aug 2022 14:18:28 -0700 Subject: [PATCH 01/15] [#501] Bump MacOS minimum deployment target to 10_15 (#502) This is needed to be ablo to build on Macs without running Xcode IDE Closes #501 --- Package.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index f98146d4..c68f6987 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.6 import PackageDescription let package = Package( name: "ZcashLightClientKit", platforms: [ .iOS(.v13), - .macOS(.v10_12) + .macOS(.v10_15) ], products: [ .library( @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.8.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0"), - .package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi.git", from: "0.0.3"), + .package(name: "libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi.git", from: "0.0.3"), ], targets: [ .target( From f1a570bbc23701edfd69103084f8cddbcc4b940b Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 25 Aug 2022 15:39:59 +0200 Subject: [PATCH 02/15] [#463] Migrate LightwalletService to Async/Await (#493) - migration of the protocol's methods done - split the code so there's blocking and non-blocking API separately [463] Migrate LightwalletService to Async/Await - draft [463] Migrate LightwalletService to Async/Await - failing tests under investigation [463] Migrate LightwalletService to Async/Await - code cleanup - tests cleanup - async throws unit tests added [463] Migrate LightwalletService to Async/Await - sample app updated to the latest API [463] Migrate LightwalletService to Async/Await - cleanup [463] Migrate LightwalletService to Async/Await - cleanup [463] Migrate LightwalletService to Async/Await - fixed non-building tests [463] Migrate LightwalletService to Async/Await - reverting back to lastHeight() [463] Migrate LightwalletService to Async/Await updated code to AsyncStream [463] Migrate LightwalletService to Async/Await (493) - tests fixed - blockRange reimplemented to use AsyncStream - grpc proto files regenerated to exclude Server --- .../DemoAppConfig.swift | 2 +- .../LatestHeightViewController.swift | 19 +- .../CompactBlockDownloadOperation.swift | 57 +- .../Service/LightWalletGRPCService.swift | 499 +++++++----- .../Service/LightWalletService.swift | 191 ++--- .../Service/ProtoBuf/service.grpc.swift | 731 +++++++++++++++++- .../Service/ProtoBuf/service.pb.swift | 209 +++-- .../LightWalletServiceTests.swift | 32 + Tests/TestUtils/DarkSideWalletService.swift | 39 +- Tests/TestUtils/FakeService.swift | 55 +- 10 files changed, 1411 insertions(+), 423 deletions(-) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift index 00ea5506..fd589065 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift @@ -30,7 +30,7 @@ enum DemoAppConfig { }() static var endpoint: LightWalletEndpoint { - return LightWalletEndpoint(address: self.host, port: self.port, secure: true) + return LightWalletEndpoint(address: self.host, port: self.port, secure: true, streamingCallTimeoutInMillis: 10 * 60 * 60 * 1000) } } diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Latest Block Height/LatestHeightViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Latest Block Height/LatestHeightViewController.swift index 693a92d5..29101ae2 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Latest Block Height/LatestHeightViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Latest Block Height/LatestHeightViewController.swift @@ -28,18 +28,13 @@ class LatestHeightViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - service.latestBlockHeight { result in - switch result { - case .success(let height): - DispatchQueue.main.async { [weak self] in - self?.model = height - } - - case .failure(let error): - DispatchQueue.main.async { [weak self] in - self?.fail(error) - } + + /// Note: It's safe to modify model or call fail() because all methods of a UIViewController are MainActor methods by default. + Task { + do { + model = try await service.latestBlockHeightAsync() + } catch { + fail(error as? LightWalletServiceError ?? .unknown) } } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift index 8ecf6516..5a2ce023 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift @@ -53,6 +53,7 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { private var service: LightWalletService private var done = false private var cancelable: CancellableCall? + private var cancelableTask: Task? private var startHeight: BlockHeight? private var targetHeight: BlockHeight? private var blockBufferSize: Int @@ -93,13 +94,13 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { self.name = "Download Stream Operation" } - // swiftlint:disable cyclomatic_complexity override func main() { guard !shouldCancel() else { cancel() return } self.startedHandler?() + do { if self.targetHeight == nil { self.targetHeight = try service.latestBlockHeight() @@ -109,38 +110,32 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { } let latestDownloaded = try storage.latestHeight() let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded) - - self.cancelable = self.service.blockStream(startHeight: startHeight, endHeight: latestHeight) { [weak self] blockResult in - switch blockResult { - case .success(let result): - switch result { - case .success: - do { - try self?.flush() - self?.done = true - } catch { - self?.fail(error: error) - } - return - case .error(let e): - self?.fail(error: e) - } - case .failure(let e): - if case .userCancelled = e { - self?.done = true - } else { - self?.fail(error: e) - } - } - } handler: {[weak self] block in - guard let self = self else { return } + + let stream = service.blockStream( + startHeight: startHeight, + endHeight: latestHeight + ) + + cancelableTask = Task { do { - try self.cache(block, flushCache: false) + for try await zcashCompactBlock in stream { + try self.cache(zcashCompactBlock, flushCache: false) + let progress = BlockProgress( + startHeight: startHeight, + targetHeight: latestHeight, + progressHeight: zcashCompactBlock.height + ) + self.progressDelegate?.progressUpdated(.download(progress)) + } + try self.flush() + self.done = true } catch { - self.fail(error: error) + if let err = error as? LightWalletServiceError, case .userCancelled = err { + self.done = true + } else { + self.fail(error: error) + } } - } progress: { progress in - self.progressDelegate?.progressUpdated(.download(progress)) } while !done && !isCancelled { @@ -153,11 +148,13 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { override func fail(error: Error? = nil) { self.cancelable?.cancel() + self.cancelableTask?.cancel() super.fail(error: error) } override func cancel() { self.cancelable?.cancel() + self.cancelableTask?.cancel() super.cancel() } diff --git a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift index b44babad..276b598a 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -90,7 +90,8 @@ public extension BlockProgress { public class LightWalletGRPCService { let channel: Channel let connectionManager: ConnectionStatusManager - let compactTxStreamer: CompactTxStreamerClient + let compactTxStreamer: CompactTxStreamerNIOClient + let compactTxStreamerAsync: CompactTxStreamerAsyncClient let singleCallTimeout: TimeLimit let streamingCallTimeout: TimeLimit @@ -135,7 +136,14 @@ public class LightWalletGRPCService { self.channel = channel - compactTxStreamer = CompactTxStreamerClient( + compactTxStreamer = CompactTxStreamerNIOClient( + channel: self.channel, + defaultCallOptions: Self.callOptions( + timeLimit: self.singleCallTimeout + ) + ) + + compactTxStreamerAsync = CompactTxStreamerAsyncClient( channel: self.channel, defaultCallOptions: Self.callOptions( timeLimit: self.singleCallTimeout @@ -146,6 +154,7 @@ public class LightWalletGRPCService { deinit { _ = channel.close() _ = compactTxStreamer.channel.close() + _ = compactTxStreamerAsync.channel.close() } func stop() { @@ -155,7 +164,7 @@ public class LightWalletGRPCService { func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CompactBlock) -> Void) throws -> ServerStreamingCall { compactTxStreamer.getBlockRange(BlockRange(startHeight: startHeight, endHeight: endHeight), handler: result) } - + func latestBlock() throws -> BlockID { try compactTxStreamer.getLatestBlock(ChainSpec()).response.wait() } @@ -179,122 +188,18 @@ public class LightWalletGRPCService { } } -extension LightWalletGRPCService: LightWalletService { - @discardableResult - public func blockStream( - startHeight: BlockHeight, - endHeight: BlockHeight, - result: @escaping (Result) -> Void, - handler: @escaping (ZcashCompactBlock) -> Void, - progress: @escaping (BlockProgress) -> Void - ) -> CancellableCall { - let future = compactTxStreamer.getBlockRange( - BlockRange( - startHeight: startHeight, - endHeight: endHeight - ), - callOptions: Self.callOptions(timeLimit: self.streamingCallTimeout), - handler: { compactBlock in - handler(ZcashCompactBlock(compactBlock: compactBlock)) - progress( - BlockProgress( - startHeight: startHeight, - targetHeight: endHeight, - progressHeight: BlockHeight(compactBlock.height) - ) - ) - } - ) - - future.status.whenComplete { completionResult in - switch completionResult { - case .success(let status): - switch status.code { - case .ok: - result(.success(GRPCResult.success)) - default: - result(.failure(LightWalletServiceError.mapCode(status))) - } - case .failure(let error): - result(.failure(LightWalletServiceError.genericError(error: error))) - } - } - return future - } - +// MARK: - LightWalletServiceBlockingAPI + +extension LightWalletGRPCService: LightWalletServiceBlockingAPI { public func getInfo() throws -> LightWalletdInfo { try compactTxStreamer.getLightdInfo(Empty()).response.wait() } - public func getInfo(result: @escaping (Result) -> Void) { - compactTxStreamer.getLightdInfo(Empty()).response.whenComplete { completionResult in - switch completionResult { - case .success(let info): - result(.success(info)) - case .failure(let error): - result(.failure(error.mapToServiceError())) - } - } - } - - public func closeConnection() { - _ = channel.close() - } - - public func fetchTransaction(txId: Data) throws -> TransactionEntity { - var txFilter = TxFilter() - txFilter.hash = txId - - do { - let rawTx = try compactTxStreamer.getTransaction(txFilter).response.wait() - - return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx) - } catch { - throw error.mapToServiceError() - } - } - - public func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { - var txFilter = TxFilter() - txFilter.hash = txId - - compactTxStreamer.getTransaction(txFilter).response.whenComplete { response in - switch response { - case .failure(let error): - result(.failure(error.mapToServiceError())) - case .success(let rawTx): - result(.success(TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx))) - } - } - } - - public func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { - do { - let transaction = try RawTransaction(serializedData: spendTransaction) - let response = self.compactTxStreamer.sendTransaction(transaction).response - - response.whenComplete { responseResult in - switch responseResult { - case .failure(let error): - result(.failure(LightWalletServiceError.sentFailed(error: error))) - case .success(let success): - result(.success(success)) - } - } - } catch { - result(.failure(error.mapToServiceError())) - } - } - - public func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { - let rawTx = RawTransaction.with { raw in - raw.data = spendTransaction - } - do { - return try compactTxStreamer.sendTransaction(rawTx).response.wait() - } catch { - throw error.mapToServiceError() + public func latestBlockHeight() throws -> BlockHeight { + guard let height = try? compactTxStreamer.getLatestBlock(ChainSpec()).response.wait().compactBlockHeight() else { + throw LightWalletServiceError.timeOut } + return height } public func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { @@ -315,51 +220,30 @@ extension LightWalletGRPCService: LightWalletService { } } - public func latestBlockHeight(result: @escaping (Result) -> Void) { - let response = compactTxStreamer.getLatestBlock(ChainSpec()).response - - response.whenSuccessBlocking(onto: queue) { blockID in - guard let blockHeight = Int(exactly: blockID.height) else { - result(.failure(LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)"))) - return - } - result(.success(blockHeight)) + public func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { + let rawTx = RawTransaction.with { raw in + raw.data = spendTransaction } - - response.whenFailureBlocking(onto: queue) { error in - result(.failure(error.mapToServiceError())) + do { + return try compactTxStreamer.sendTransaction(rawTx).response.wait() + } catch { + throw error.mapToServiceError() } } - public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { - queue.async { [weak self] in - guard let self = self else { return } - - var blocks: [CompactBlock] = [] - let response = self.compactTxStreamer.getBlockRange(range.blockRange(), handler: { blocks.append($0) }) - - do { - let status = try response.status.wait() - switch status.code { - case .ok: - result(.success(blocks.asZcashCompactBlocks())) - - default: - result(.failure(.mapCode(status))) - } - } catch { - result(.failure(error.mapToServiceError())) - } + public func fetchTransaction(txId: Data) throws -> TransactionEntity { + var txFilter = TxFilter() + txFilter.hash = txId + + do { + let rawTx = try compactTxStreamer.getTransaction(txFilter).response.wait() + + return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx) + } catch { + throw error.mapToServiceError() } } - public func latestBlockHeight() throws -> BlockHeight { - guard let height = try? latestBlock().compactBlockHeight() else { - throw LightWalletServiceError.timeOut - } - return height - } - public func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { let arg = GetAddressUtxosArg.with { utxoArgs in utxoArgs.addresses = [tAddress] @@ -383,7 +267,168 @@ extension LightWalletGRPCService: LightWalletService { } } - public func fetchUTXOs(for tAddress: String, height: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void) { + public func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { + guard !tAddresses.isEmpty else { + return [] // FIXME: throw a real error + } + + var utxos: [UnspentTransactionOutputEntity] = [] + + let arg = GetAddressUtxosArg.with { utxoArgs in + utxoArgs.addresses = tAddresses + utxoArgs.startHeight = UInt64(height) + } + utxos.append( + contentsOf: + try self.compactTxStreamer.getAddressUtxos(arg).response.wait().addressUtxos.map { reply in + UTXO( + id: nil, + address: reply.address, + prevoutTxId: reply.txid, + prevoutIndex: Int(reply.index), + script: reply.script, + valueZat: Int(reply.valueZat), + height: Int(reply.height), + spentInTx: nil + ) + } + ) + + return utxos + } +} + +// MARK: - LightWalletServiceNonBlockingAPI + +extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { + public func getInfo(result: @escaping (Result) -> Void) { + compactTxStreamer.getLightdInfo(Empty()).response.whenComplete { completionResult in + switch completionResult { + case .success(let info): + result(.success(info)) + case .failure(let error): + result(.failure(error.mapToServiceError())) + } + } + } + + public func getInfoAsync() async throws -> LightWalletdInfo { + try await compactTxStreamerAsync.getLightdInfo(Empty()) + } + + public func latestBlockHeight(result: @escaping (Result) -> Void) { + let response = compactTxStreamer.getLatestBlock(ChainSpec()).response + + response.whenSuccessBlocking(onto: queue) { blockID in + guard let blockHeight = Int(exactly: blockID.height) else { + result(.failure(LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)"))) + return + } + result(.success(blockHeight)) + } + + response.whenFailureBlocking(onto: queue) { error in + result(.failure(error.mapToServiceError())) + } + } + + public func latestBlockHeightAsync() async throws -> BlockHeight { + try await BlockHeight(compactTxStreamerAsync.getLatestBlock(ChainSpec()).height) + } + + public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { + queue.async { [weak self] in + guard let self = self else { return } + + var blocks: [CompactBlock] = [] + let response = self.compactTxStreamer.getBlockRange(range.blockRange(), handler: { blocks.append($0) }) + + do { + let status = try response.status.wait() + switch status.code { + case .ok: + result(.success(blocks.asZcashCompactBlocks())) + + default: + result(.failure(.mapCode(status))) + } + } catch { + result(.failure(error.mapToServiceError())) + } + } + } + + public func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { + let stream = compactTxStreamerAsync.getBlockRange(range.blockRange()) + + return AsyncThrowingStream { continuation in + Task { + do { + for try await block in stream { + continuation.yield(ZcashCompactBlock(compactBlock: block)) + } + continuation.finish(throwing: nil) + } catch { + continuation.finish(throwing: error) + } + } + } + } + + public func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { + do { + let transaction = try RawTransaction(serializedData: spendTransaction) + let response = self.compactTxStreamer.sendTransaction(transaction).response + + response.whenComplete { responseResult in + switch responseResult { + case .failure(let error): + result(.failure(LightWalletServiceError.sentFailed(error: error))) + case .success(let success): + result(.success(success)) + } + } + } catch { + result(.failure(error.mapToServiceError())) + } + } + + public func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse { + do { + let transaction = try RawTransaction(serializedData: spendTransaction) + return try await compactTxStreamerAsync.sendTransaction(transaction) + } catch { + throw LightWalletServiceError.sentFailed(error: error) + } + } + + public func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { + var txFilter = TxFilter() + txFilter.hash = txId + + compactTxStreamer.getTransaction(txFilter).response.whenComplete { response in + switch response { + case .failure(let error): + result(.failure(error.mapToServiceError())) + case .success(let rawTx): + result(.success(TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx))) + } + } + } + + public func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { + var txFilter = TxFilter() + txFilter.hash = txId + + let rawTx = try await compactTxStreamerAsync.getTransaction(txFilter) + return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx) + } + + public func fetchUTXOs( + for tAddress: String, + height: BlockHeight, + result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError> + ) -> Void) { queue.async { [weak self] in guard let self = self else { return } let arg = GetAddressUtxosArg.with { utxoArgs in @@ -420,36 +465,13 @@ extension LightWalletGRPCService: LightWalletService { } } - public func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - guard !tAddresses.isEmpty else { - return [] // FIXME: throw a real error - } - - var utxos: [UnspentTransactionOutputEntity] = [] - - let arg = GetAddressUtxosArg.with { utxoArgs in - utxoArgs.addresses = tAddresses - utxoArgs.startHeight = UInt64(height) - } - utxos.append( - contentsOf: - try self.compactTxStreamer.getAddressUtxos(arg).response.wait().addressUtxos.map { reply in - UTXO( - id: nil, - address: reply.address, - prevoutTxId: reply.txid, - prevoutIndex: Int(reply.index), - script: reply.script, - valueZat: Int(reply.valueZat), - height: Int(reply.height), - spentInTx: nil - ) - } - ) - - return utxos + public func fetchUTXOs( + for tAddress: String, + height: BlockHeight + ) -> AsyncThrowingStream { + return fetchUTXOs(for: [tAddress], height: height) } - + public func fetchUTXOs( for tAddresses: [String], height: BlockHeight, @@ -495,6 +517,119 @@ extension LightWalletGRPCService: LightWalletService { } } } + + public func fetchUTXOs( + for tAddresses: [String], + height: BlockHeight + ) -> AsyncThrowingStream { + guard !tAddresses.isEmpty else { + return AsyncThrowingStream { _ in } + } + + let args = GetAddressUtxosArg.with { utxoArgs in + utxoArgs.addresses = tAddresses + utxoArgs.startHeight = UInt64(height) + } + let stream = compactTxStreamerAsync.getAddressUtxosStream(args) + + return AsyncThrowingStream { continuation in + Task { + do { + for try await reply in stream { + continuation.yield( + UTXO( + id: nil, + address: reply.address, + prevoutTxId: reply.txid, + prevoutIndex: Int(reply.index), + script: reply.script, + valueZat: Int(reply.valueZat), + height: Int(reply.height), + spentInTx: nil + ) + ) + } + continuation.finish(throwing: nil) + } catch { + continuation.finish(throwing: error) + } + } + } + } + + @discardableResult + public func blockStream( + startHeight: BlockHeight, + endHeight: BlockHeight, + result: @escaping (Result) -> Void, + handler: @escaping (ZcashCompactBlock) -> Void, + progress: @escaping (BlockProgress) -> Void + ) -> CancellableCall { + let future = compactTxStreamer.getBlockRange( + BlockRange( + startHeight: startHeight, + endHeight: endHeight + ), + callOptions: Self.callOptions(timeLimit: self.streamingCallTimeout), + handler: { compactBlock in + handler(ZcashCompactBlock(compactBlock: compactBlock)) + progress( + BlockProgress( + startHeight: startHeight, + targetHeight: endHeight, + progressHeight: BlockHeight(compactBlock.height) + ) + ) + } + ) + + future.status.whenComplete { completionResult in + switch completionResult { + case .success(let status): + switch status.code { + case .ok: + result(.success(GRPCResult.success)) + default: + result(.failure(LightWalletServiceError.mapCode(status))) + } + case .failure(let error): + result(.failure(LightWalletServiceError.genericError(error: error))) + } + } + return future + } + + public func blockStream( + startHeight: BlockHeight, + endHeight: BlockHeight + ) -> AsyncThrowingStream { + let stream = compactTxStreamerAsync.getBlockRange( + BlockRange( + startHeight: startHeight, + endHeight: endHeight + ), + callOptions: Self.callOptions(timeLimit: self.streamingCallTimeout) + ) + + return AsyncThrowingStream { continuation in + Task { + do { + for try await compactBlock in stream { + continuation.yield(ZcashCompactBlock(compactBlock: compactBlock)) + } + continuation.finish(throwing: nil) + } catch { + continuation.finish(throwing: error) + } + } + } + } +} + +extension LightWalletGRPCService: LightWalletService { + public func closeConnection() { + _ = channel.close() + } } // MARK: - Extensions diff --git a/Sources/ZcashLightClientKit/Service/LightWalletService.swift b/Sources/ZcashLightClientKit/Service/LightWalletService.swift index c0954952..ce044c3e 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletService.swift @@ -101,49 +101,102 @@ public protocol LightWalletServiceResponse { extension SendResponse: LightWalletServiceResponse {} -public protocol LightWalletService { - /** - returns the info for this lightwalletd server (blocking) - */ +/// Blocking API - used for the testing purposes +public protocol LightWalletServiceBlockingAPI { + /// Returns the info for this lightwalletd server (blocking) func getInfo() throws -> LightWalletdInfo - - /** - returns the info for this lightwalletd server - */ - func getInfo(result: @escaping (Result) -> Void) - /** - Return the latest block height known to the service. - - - Parameter result: a result containing the height or an Error - */ - func latestBlockHeight(result: @escaping (Result) -> Void) - - /** - Return the latest block height known to the service. - - - Parameter result: a result containing the height or an Error - */ + /// + /// Return the latest block height known to the service. + /// - Parameter result: a result containing the height or an Error func latestBlockHeight() throws -> BlockHeight - /** - Return the given range of blocks. - - - Parameter range: the inclusive range to fetch. - For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5. - Non blocking - */ - func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void ) - - /** - Return the given range of blocks. - - - Parameter range: the inclusive range to fetch. - For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5. - blocking - */ + /// Return the given range of blocks. + /// + /// - Parameter range: the inclusive range to fetch. + /// For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5. func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] + + /// Submits a raw transaction over lightwalletd. Blocking + /// - Parameter spendTransaction: data representing the transaction to be sent + /// - Throws: LightWalletServiceError + /// - Returns: LightWalletServiceResponse + func submit(spendTransaction: Data) throws -> LightWalletServiceResponse + + /// Gets a transaction by id + /// - Parameter txId: data representing the transaction ID + /// - Throws: LightWalletServiceError + /// - Returns: LightWalletServiceResponse + func fetchTransaction(txId: Data) throws -> TransactionEntity + + func fetchUTXOs( + for tAddress: String, + height: BlockHeight + ) throws -> [UnspentTransactionOutputEntity] + + func fetchUTXOs( + for tAddresses: [String], + height: BlockHeight + ) throws -> [UnspentTransactionOutputEntity] +} + +public protocol LightWalletServiceNonBlockingAPI { + /// Returns the info for this lightwalletd server + @available(*, deprecated, message: "This function will be removed soon. Use the `getInfoAsync()` instead.") + func getInfo(result: @escaping (Result) -> Void) + func getInfoAsync() async throws -> LightWalletdInfo + + /// + /// Return the latest block height known to the service. + /// - Parameter result: a result containing the height or an Error + @available(*, deprecated, message: "This function will be removed soon. Use the `latestBlockHeightAsync()` instead.") + func latestBlockHeight(result: @escaping (Result) -> Void) + func latestBlockHeightAsync() async throws -> BlockHeight + + /// Return the given range of blocks. + /// - Parameter range: the inclusive range to fetch. + /// For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5. + @available(*, deprecated, message: "This function will be removed soon. Use the `blockRange(...) -> AsyncThrowingStream` instead.") + func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) + func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream + + /// Submits a raw transaction over lightwalletd. Non-Blocking + /// - Parameter spendTransaction: data representing the transaction to be sent + /// - Parameter result: escaping closure that takes a result containing either LightWalletServiceResponse or LightWalletServiceError + @available(*, deprecated, message: "This function will be removed soon. Use the `submitAsync(spendTransaction: Data)` instead.") + func submit(spendTransaction: Data, result: @escaping(Result) -> Void) + func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse + + /// Gets a transaction by id + /// - Parameter txId: data representing the transaction ID + /// - Parameter result: handler for the result + /// - Throws: LightWalletServiceError + /// - Returns: LightWalletServiceResponse + @available(*, deprecated, message: "This function will be removed soon. Use the `fetchTransactionAsync(txId: Data)` instead.") + func fetchTransaction( + txId: Data, + result: @escaping (Result) -> Void + ) + func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity + + @available(*, deprecated, message: "This function will be removed soon. Use the `fetchUTXOs(for tAddress:...) -> AsyncThrowingStream` instead.") + func fetchUTXOs( + for tAddress: String, + height: BlockHeight, + result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void + ) + func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream + + @available(*, deprecated, message: "This function will be removed soon. Use the `fetchUTXOs(for tAddresses:...) -> AsyncThrowingStream` instead.") + func fetchUTXOs( + for tAddresses: [String], + height: BlockHeight, + result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void + ) + func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream + + @available(*, deprecated, message: "This function will be removed soon. Use the `blockStream(...) -> AsyncThrowingStream` instead.") @discardableResult func blockStream( startHeight: BlockHeight, @@ -153,62 +206,12 @@ public protocol LightWalletService { progress: @escaping (BlockProgress) -> Void ) -> CancellableCall - /** - Submits a raw transaction over lightwalletd. Non-Blocking - - Parameter spendTransaction: data representing the transaction to be sent - - Parameter result: escaping closure that takes a result containing either LightWalletServiceResponse or LightWalletServiceError - */ - func submit(spendTransaction: Data, result: @escaping(Result) -> Void) - - /** - Submits a raw transaction over lightwalletd. Blocking - - Parameter spendTransaction: data representing the transaction to be sent - - Throws: LightWalletServiceError - - Returns: LightWalletServiceResponse - */ - func submit(spendTransaction: Data) throws -> LightWalletServiceResponse - - /** - Gets a transaction by id - - Parameter txId: data representing the transaction ID - - Throws: LightWalletServiceError - - Returns: LightWalletServiceResponse - */ - func fetchTransaction(txId: Data) throws -> TransactionEntity - - /** - Gets a transaction by id - - Parameter txId: data representing the transaction ID - - Parameter result: handler for the result - - Throws: LightWalletServiceError - - Returns: LightWalletServiceResponse - */ - func fetchTransaction( - txId: Data, - result: @escaping (Result) -> Void - ) - - func fetchUTXOs( - for tAddress: String, - height: BlockHeight - ) throws -> [UnspentTransactionOutputEntity] - - func fetchUTXOs( - for tAddress: String, - height: BlockHeight, - result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) - - func fetchUTXOs( - for tAddresses: [String], - height: BlockHeight - ) throws -> [UnspentTransactionOutputEntity] - - func fetchUTXOs( - for tAddresses: [String], - height: BlockHeight, - result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) - + func blockStream( + startHeight: BlockHeight, + endHeight: BlockHeight + ) -> AsyncThrowingStream +} + +public protocol LightWalletService: LightWalletServiceNonBlockingAPI, LightWalletServiceBlockingAPI { func closeConnection() } diff --git a/Sources/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift b/Sources/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift index 9a1de9ae..a1945faa 100644 --- a/Sources/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift +++ b/Sources/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift @@ -22,11 +22,15 @@ // import GRPC import NIO +import NIOConcurrencyHelpers import SwiftProtobuf -/// Usage: instantiate CompactTxStreamerClient, then call methods of this protocol to make API calls. +/// Usage: instantiate `CompactTxStreamerClient`, then call methods of this protocol to make API calls. internal protocol CompactTxStreamerClientProtocol: GRPCClient { + var serviceName: String { get } + var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { get } + func getLatestBlock( _ request: ChainSpec, callOptions: CallOptions? @@ -99,10 +103,12 @@ internal protocol CompactTxStreamerClientProtocol: GRPCClient { _ request: Duration, callOptions: CallOptions? ) -> UnaryCall - } extension CompactTxStreamerClientProtocol { + internal var serviceName: String { + return "cash.z.wallet.sdk.rpc.CompactTxStreamer" + } /// Return the height of the tip of the best chain /// @@ -115,9 +121,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock", + path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? [] ) } @@ -132,9 +139,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock", + path: CompactTxStreamerClientMetadata.Methods.getBlock.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockInterceptors() ?? [] ) } @@ -151,9 +159,10 @@ extension CompactTxStreamerClientProtocol { handler: @escaping (CompactBlock) -> Void ) -> ServerStreamingCall { return self.makeServerStreamingCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange", + path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path, request: request, callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [], handler: handler ) } @@ -169,9 +178,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction", + path: CompactTxStreamerClientMetadata.Methods.getTransaction.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? [] ) } @@ -186,9 +196,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction", + path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? [] ) } @@ -205,9 +216,10 @@ extension CompactTxStreamerClientProtocol { handler: @escaping (RawTransaction) -> Void ) -> ServerStreamingCall { return self.makeServerStreamingCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressTxids", + path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path, request: request, callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [], handler: handler ) } @@ -223,9 +235,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance", + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? [] ) } @@ -241,8 +254,9 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> ClientStreamingCall { return self.makeClientStreamingCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalanceStream", - callOptions: callOptions ?? self.defaultCallOptions + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [] ) } @@ -267,9 +281,10 @@ extension CompactTxStreamerClientProtocol { handler: @escaping (CompactTx) -> Void ) -> ServerStreamingCall { return self.makeServerStreamingCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolTx", + path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path, request: request, callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [], handler: handler ) } @@ -288,9 +303,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState", + path: CompactTxStreamerClientMetadata.Methods.getTreeState.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? [] ) } @@ -305,9 +321,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos", + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? [] ) } @@ -324,9 +341,10 @@ extension CompactTxStreamerClientProtocol { handler: @escaping (GetAddressUtxosReply) -> Void ) -> ServerStreamingCall { return self.makeServerStreamingCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxosStream", + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path, request: request, callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [], handler: handler ) } @@ -342,9 +360,10 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo", + path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? [] ) } @@ -359,25 +378,679 @@ extension CompactTxStreamerClientProtocol { callOptions: CallOptions? = nil ) -> UnaryCall { return self.makeUnaryCall( - path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping", + path: CompactTxStreamerClientMetadata.Methods.ping.path, request: request, - callOptions: callOptions ?? self.defaultCallOptions + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makePingInterceptors() ?? [] ) } } +#if compiler(>=5.6) +@available(*, deprecated) +extension CompactTxStreamerClient: @unchecked Sendable {} +#endif // compiler(>=5.6) + +@available(*, deprecated, renamed: "CompactTxStreamerNIOClient") internal final class CompactTxStreamerClient: CompactTxStreamerClientProtocol { + private let lock = Lock() + private var _defaultCallOptions: CallOptions + private var _interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? internal let channel: GRPCChannel - internal var defaultCallOptions: CallOptions + internal var defaultCallOptions: CallOptions { + get { self.lock.withLock { return self._defaultCallOptions } } + set { self.lock.withLockVoid { self._defaultCallOptions = newValue } } + } + internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { + get { self.lock.withLock { return self._interceptors } } + set { self.lock.withLockVoid { self._interceptors = newValue } } + } /// Creates a client for the cash.z.wallet.sdk.rpc.CompactTxStreamer service. /// /// - Parameters: /// - channel: `GRPCChannel` to the service host. /// - defaultCallOptions: Options to use for each service call if the user doesn't provide them. - internal init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) { + /// - interceptors: A factory providing interceptors for each RPC. + internal init( + channel: GRPCChannel, + defaultCallOptions: CallOptions = CallOptions(), + interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil + ) { self.channel = channel - self.defaultCallOptions = defaultCallOptions + self._defaultCallOptions = defaultCallOptions + self._interceptors = interceptors + } +} + +internal struct CompactTxStreamerNIOClient: CompactTxStreamerClientProtocol { + internal var channel: GRPCChannel + internal var defaultCallOptions: CallOptions + internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? + + /// Creates a client for the cash.z.wallet.sdk.rpc.CompactTxStreamer service. + /// + /// - Parameters: + /// - channel: `GRPCChannel` to the service host. + /// - defaultCallOptions: Options to use for each service call if the user doesn't provide them. + /// - interceptors: A factory providing interceptors for each RPC. + internal init( + channel: GRPCChannel, + defaultCallOptions: CallOptions = CallOptions(), + interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil + ) { + self.channel = channel + self.defaultCallOptions = defaultCallOptions + self.interceptors = interceptors + } +} + +#if compiler(>=5.6) +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +internal protocol CompactTxStreamerAsyncClientProtocol: GRPCClient { + static var serviceDescriptor: GRPCServiceDescriptor { get } + var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { get } + + func makeGetLatestBlockCall( + _ request: ChainSpec, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetBlockCall( + _ request: BlockID, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetBlockRangeCall( + _ request: BlockRange, + callOptions: CallOptions? + ) -> GRPCAsyncServerStreamingCall + + func makeGetTransactionCall( + _ request: TxFilter, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeSendTransactionCall( + _ request: RawTransaction, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetTaddressTxidsCall( + _ request: TransparentAddressBlockFilter, + callOptions: CallOptions? + ) -> GRPCAsyncServerStreamingCall + + func makeGetTaddressBalanceCall( + _ request: AddressList, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetTaddressBalanceStreamCall( + callOptions: CallOptions? + ) -> GRPCAsyncClientStreamingCall + + func makeGetMempoolTxCall( + _ request: Exclude, + callOptions: CallOptions? + ) -> GRPCAsyncServerStreamingCall + + func makeGetTreeStateCall( + _ request: BlockID, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetAddressUtxosCall( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makeGetAddressUtxosStreamCall( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? + ) -> GRPCAsyncServerStreamingCall + + func makeGetLightdInfoCall( + _ request: Empty, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + + func makePingCall( + _ request: Duration, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension CompactTxStreamerAsyncClientProtocol { + internal static var serviceDescriptor: GRPCServiceDescriptor { + return CompactTxStreamerClientMetadata.serviceDescriptor + } + + internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { + return nil + } + + internal func makeGetLatestBlockCall( + _ request: ChainSpec, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? [] + ) + } + + internal func makeGetBlockCall( + _ request: BlockID, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getBlock.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockInterceptors() ?? [] + ) + } + + internal func makeGetBlockRangeCall( + _ request: BlockRange, + callOptions: CallOptions? = nil + ) -> GRPCAsyncServerStreamingCall { + return self.makeAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [] + ) + } + + internal func makeGetTransactionCall( + _ request: TxFilter, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTransaction.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? [] + ) + } + + internal func makeSendTransactionCall( + _ request: RawTransaction, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? [] + ) + } + + internal func makeGetTaddressTxidsCall( + _ request: TransparentAddressBlockFilter, + callOptions: CallOptions? = nil + ) -> GRPCAsyncServerStreamingCall { + return self.makeAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [] + ) + } + + internal func makeGetTaddressBalanceCall( + _ request: AddressList, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? [] + ) + } + + internal func makeGetTaddressBalanceStreamCall( + callOptions: CallOptions? = nil + ) -> GRPCAsyncClientStreamingCall { + return self.makeAsyncClientStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [] + ) + } + + internal func makeGetMempoolTxCall( + _ request: Exclude, + callOptions: CallOptions? = nil + ) -> GRPCAsyncServerStreamingCall { + return self.makeAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [] + ) + } + + internal func makeGetTreeStateCall( + _ request: BlockID, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTreeState.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? [] + ) + } + + internal func makeGetAddressUtxosCall( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? [] + ) + } + + internal func makeGetAddressUtxosStreamCall( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? = nil + ) -> GRPCAsyncServerStreamingCall { + return self.makeAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [] + ) + } + + internal func makeGetLightdInfoCall( + _ request: Empty, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? [] + ) + } + + internal func makePingCall( + _ request: Duration, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.ping.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makePingInterceptors() ?? [] + ) + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension CompactTxStreamerAsyncClientProtocol { + internal func getLatestBlock( + _ request: ChainSpec, + callOptions: CallOptions? = nil + ) async throws -> BlockID { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? [] + ) + } + + internal func getBlock( + _ request: BlockID, + callOptions: CallOptions? = nil + ) async throws -> CompactBlock { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getBlock.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockInterceptors() ?? [] + ) + } + + internal func getBlockRange( + _ request: BlockRange, + callOptions: CallOptions? = nil + ) -> GRPCAsyncResponseStream { + return self.performAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [] + ) + } + + internal func getTransaction( + _ request: TxFilter, + callOptions: CallOptions? = nil + ) async throws -> RawTransaction { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTransaction.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? [] + ) + } + + internal func sendTransaction( + _ request: RawTransaction, + callOptions: CallOptions? = nil + ) async throws -> SendResponse { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? [] + ) + } + + internal func getTaddressTxids( + _ request: TransparentAddressBlockFilter, + callOptions: CallOptions? = nil + ) -> GRPCAsyncResponseStream { + return self.performAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [] + ) + } + + internal func getTaddressBalance( + _ request: AddressList, + callOptions: CallOptions? = nil + ) async throws -> Balance { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? [] + ) + } + + internal func getTaddressBalanceStream( + _ requests: RequestStream, + callOptions: CallOptions? = nil + ) async throws -> Balance where RequestStream: Sequence, RequestStream.Element == Address { + return try await self.performAsyncClientStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path, + requests: requests, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [] + ) + } + + internal func getTaddressBalanceStream( + _ requests: RequestStream, + callOptions: CallOptions? = nil + ) async throws -> Balance where RequestStream: AsyncSequence & Sendable, RequestStream.Element == Address { + return try await self.performAsyncClientStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path, + requests: requests, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [] + ) + } + + internal func getMempoolTx( + _ request: Exclude, + callOptions: CallOptions? = nil + ) -> GRPCAsyncResponseStream { + return self.performAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [] + ) + } + + internal func getTreeState( + _ request: BlockID, + callOptions: CallOptions? = nil + ) async throws -> TreeState { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getTreeState.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? [] + ) + } + + internal func getAddressUtxos( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? = nil + ) async throws -> GetAddressUtxosReplyList { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? [] + ) + } + + internal func getAddressUtxosStream( + _ request: GetAddressUtxosArg, + callOptions: CallOptions? = nil + ) -> GRPCAsyncResponseStream { + return self.performAsyncServerStreamingCall( + path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [] + ) + } + + internal func getLightdInfo( + _ request: Empty, + callOptions: CallOptions? = nil + ) async throws -> LightdInfo { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? [] + ) + } + + internal func ping( + _ request: Duration, + callOptions: CallOptions? = nil + ) async throws -> PingResponse { + return try await self.performAsyncUnaryCall( + path: CompactTxStreamerClientMetadata.Methods.ping.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makePingInterceptors() ?? [] + ) + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +internal struct CompactTxStreamerAsyncClient: CompactTxStreamerAsyncClientProtocol { + internal var channel: GRPCChannel + internal var defaultCallOptions: CallOptions + internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? + + internal init( + channel: GRPCChannel, + defaultCallOptions: CallOptions = CallOptions(), + interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil + ) { + self.channel = channel + self.defaultCallOptions = defaultCallOptions + self.interceptors = interceptors + } +} + +#endif // compiler(>=5.6) + +internal protocol CompactTxStreamerClientInterceptorFactoryProtocol: GRPCSendable { + + /// - Returns: Interceptors to use when invoking 'getLatestBlock'. + func makeGetLatestBlockInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getBlock'. + func makeGetBlockInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getBlockRange'. + func makeGetBlockRangeInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getTransaction'. + func makeGetTransactionInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'sendTransaction'. + func makeSendTransactionInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getTaddressTxids'. + func makeGetTaddressTxidsInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getTaddressBalance'. + func makeGetTaddressBalanceInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getTaddressBalanceStream'. + func makeGetTaddressBalanceStreamInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getMempoolTx'. + func makeGetMempoolTxInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getTreeState'. + func makeGetTreeStateInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getAddressUtxos'. + func makeGetAddressUtxosInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getAddressUtxosStream'. + func makeGetAddressUtxosStreamInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'getLightdInfo'. + func makeGetLightdInfoInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'ping'. + func makePingInterceptors() -> [ClientInterceptor] +} + +internal enum CompactTxStreamerClientMetadata { + internal static let serviceDescriptor = GRPCServiceDescriptor( + name: "CompactTxStreamer", + fullName: "cash.z.wallet.sdk.rpc.CompactTxStreamer", + methods: [ + CompactTxStreamerClientMetadata.Methods.getLatestBlock, + CompactTxStreamerClientMetadata.Methods.getBlock, + CompactTxStreamerClientMetadata.Methods.getBlockRange, + CompactTxStreamerClientMetadata.Methods.getTransaction, + CompactTxStreamerClientMetadata.Methods.sendTransaction, + CompactTxStreamerClientMetadata.Methods.getTaddressTxids, + CompactTxStreamerClientMetadata.Methods.getTaddressBalance, + CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream, + CompactTxStreamerClientMetadata.Methods.getMempoolTx, + CompactTxStreamerClientMetadata.Methods.getTreeState, + CompactTxStreamerClientMetadata.Methods.getAddressUtxos, + CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream, + CompactTxStreamerClientMetadata.Methods.getLightdInfo, + CompactTxStreamerClientMetadata.Methods.ping, + ] + ) + + internal enum Methods { + internal static let getLatestBlock = GRPCMethodDescriptor( + name: "GetLatestBlock", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock", + type: GRPCCallType.unary + ) + + internal static let getBlock = GRPCMethodDescriptor( + name: "GetBlock", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock", + type: GRPCCallType.unary + ) + + internal static let getBlockRange = GRPCMethodDescriptor( + name: "GetBlockRange", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange", + type: GRPCCallType.serverStreaming + ) + + internal static let getTransaction = GRPCMethodDescriptor( + name: "GetTransaction", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction", + type: GRPCCallType.unary + ) + + internal static let sendTransaction = GRPCMethodDescriptor( + name: "SendTransaction", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction", + type: GRPCCallType.unary + ) + + internal static let getTaddressTxids = GRPCMethodDescriptor( + name: "GetTaddressTxids", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressTxids", + type: GRPCCallType.serverStreaming + ) + + internal static let getTaddressBalance = GRPCMethodDescriptor( + name: "GetTaddressBalance", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance", + type: GRPCCallType.unary + ) + + internal static let getTaddressBalanceStream = GRPCMethodDescriptor( + name: "GetTaddressBalanceStream", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalanceStream", + type: GRPCCallType.clientStreaming + ) + + internal static let getMempoolTx = GRPCMethodDescriptor( + name: "GetMempoolTx", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolTx", + type: GRPCCallType.serverStreaming + ) + + internal static let getTreeState = GRPCMethodDescriptor( + name: "GetTreeState", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState", + type: GRPCCallType.unary + ) + + internal static let getAddressUtxos = GRPCMethodDescriptor( + name: "GetAddressUtxos", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos", + type: GRPCCallType.unary + ) + + internal static let getAddressUtxosStream = GRPCMethodDescriptor( + name: "GetAddressUtxosStream", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxosStream", + type: GRPCCallType.serverStreaming + ) + + internal static let getLightdInfo = GRPCMethodDescriptor( + name: "GetLightdInfo", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo", + type: GRPCCallType.unary + ) + + internal static let ping = GRPCMethodDescriptor( + name: "Ping", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping", + type: GRPCCallType.unary + ) } } diff --git a/Sources/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift b/Sources/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift index 13e8e800..51b90837 100644 --- a/Sources/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift +++ b/Sources/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift @@ -33,7 +33,7 @@ struct BlockID { var height: UInt64 = 0 - var hash: Data = SwiftProtobuf.Internal.emptyData + var hash: Data = Data() var unknownFields = SwiftProtobuf.UnknownStorage() @@ -95,7 +95,7 @@ struct TxFilter { var index: UInt64 = 0 /// transaction ID (hash, txid) - var hash: Data = SwiftProtobuf.Internal.emptyData + var hash: Data = Data() var unknownFields = SwiftProtobuf.UnknownStorage() @@ -112,7 +112,7 @@ struct RawTransaction { // methods supported on all messages. /// exact data returned by Zcash 'getrawtransaction' - var data: Data = SwiftProtobuf.Internal.emptyData + var data: Data = Data() /// height that the transaction was mined (or -1) var height: UInt64 = 0 @@ -367,11 +367,11 @@ struct GetAddressUtxosReply { var address: String = String() - var txid: Data = SwiftProtobuf.Internal.emptyData + var txid: Data = Data() var index: Int32 = 0 - var script: Data = SwiftProtobuf.Internal.emptyData + var script: Data = Data() var valueZat: Int64 = 0 @@ -394,6 +394,28 @@ struct GetAddressUtxosReplyList { init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension BlockID: @unchecked Sendable {} +extension BlockRange: @unchecked Sendable {} +extension TxFilter: @unchecked Sendable {} +extension RawTransaction: @unchecked Sendable {} +extension SendResponse: @unchecked Sendable {} +extension ChainSpec: @unchecked Sendable {} +extension Empty: @unchecked Sendable {} +extension LightdInfo: @unchecked Sendable {} +extension TransparentAddressBlockFilter: @unchecked Sendable {} +extension Duration: @unchecked Sendable {} +extension PingResponse: @unchecked Sendable {} +extension Address: @unchecked Sendable {} +extension AddressList: @unchecked Sendable {} +extension Balance: @unchecked Sendable {} +extension Exclude: @unchecked Sendable {} +extension TreeState: @unchecked Sendable {} +extension GetAddressUtxosArg: @unchecked Sendable {} +extension GetAddressUtxosReply: @unchecked Sendable {} +extension GetAddressUtxosReplyList: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "cash.z.wallet.sdk.rpc" @@ -407,9 +429,12 @@ extension BlockID: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self.height) - case 2: try decoder.decodeSingularBytesField(value: &self.hash) + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.height) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.hash) }() default: break } } @@ -442,21 +467,28 @@ extension BlockRange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._start) - case 2: try decoder.decodeSingularMessageField(value: &self._end) + case 1: try { try decoder.decodeSingularMessageField(value: &self._start) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._end) }() default: break } } } func traverse(visitor: inout V) throws { - if let v = self._start { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._start { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._end { + } }() + try { if let v = self._end { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } + } }() try unknownFields.traverse(visitor: &visitor) } @@ -478,19 +510,26 @@ extension TxFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._block) - case 2: try decoder.decodeSingularUInt64Field(value: &self.index) - case 3: try decoder.decodeSingularBytesField(value: &self.hash) + case 1: try { try decoder.decodeSingularMessageField(value: &self._block) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.index) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.hash) }() default: break } } } func traverse(visitor: inout V) throws { - if let v = self._block { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._block { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } + } }() if self.index != 0 { try visitor.visitSingularUInt64Field(value: self.index, fieldNumber: 2) } @@ -518,9 +557,12 @@ extension RawTransaction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self.data) - case 2: try decoder.decodeSingularUInt64Field(value: &self.height) + case 1: try { try decoder.decodeSingularBytesField(value: &self.data) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.height) }() default: break } } @@ -553,9 +595,12 @@ extension SendResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularInt32Field(value: &self.errorCode) - case 2: try decoder.decodeSingularStringField(value: &self.errorMessage) + case 1: try { try decoder.decodeSingularInt32Field(value: &self.errorCode) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() default: break } } @@ -638,21 +683,24 @@ extension LightdInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self.version) - case 2: try decoder.decodeSingularStringField(value: &self.vendor) - case 3: try decoder.decodeSingularBoolField(value: &self.taddrSupport) - case 4: try decoder.decodeSingularStringField(value: &self.chainName) - case 5: try decoder.decodeSingularUInt64Field(value: &self.saplingActivationHeight) - case 6: try decoder.decodeSingularStringField(value: &self.consensusBranchID) - case 7: try decoder.decodeSingularUInt64Field(value: &self.blockHeight) - case 8: try decoder.decodeSingularStringField(value: &self.gitCommit) - case 9: try decoder.decodeSingularStringField(value: &self.branch) - case 10: try decoder.decodeSingularStringField(value: &self.buildDate) - case 11: try decoder.decodeSingularStringField(value: &self.buildUser) - case 12: try decoder.decodeSingularUInt64Field(value: &self.estimatedHeight) - case 13: try decoder.decodeSingularStringField(value: &self.zcashdBuild) - case 14: try decoder.decodeSingularStringField(value: &self.zcashdSubversion) + case 1: try { try decoder.decodeSingularStringField(value: &self.version) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.vendor) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.taddrSupport) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.chainName) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.saplingActivationHeight) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.consensusBranchID) }() + case 7: try { try decoder.decodeSingularUInt64Field(value: &self.blockHeight) }() + case 8: try { try decoder.decodeSingularStringField(value: &self.gitCommit) }() + case 9: try { try decoder.decodeSingularStringField(value: &self.branch) }() + case 10: try { try decoder.decodeSingularStringField(value: &self.buildDate) }() + case 11: try { try decoder.decodeSingularStringField(value: &self.buildUser) }() + case 12: try { try decoder.decodeSingularUInt64Field(value: &self.estimatedHeight) }() + case 13: try { try decoder.decodeSingularStringField(value: &self.zcashdBuild) }() + case 14: try { try decoder.decodeSingularStringField(value: &self.zcashdSubversion) }() default: break } } @@ -733,21 +781,28 @@ extension TransparentAddressBlockFilter: SwiftProtobuf.Message, SwiftProtobuf._M mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self.address) - case 2: try decoder.decodeSingularMessageField(value: &self._range) + case 1: try { try decoder.decodeSingularStringField(value: &self.address) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._range) }() default: break } } } func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.address.isEmpty { try visitor.visitSingularStringField(value: self.address, fieldNumber: 1) } - if let v = self._range { + try { if let v = self._range { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } + } }() try unknownFields.traverse(visitor: &visitor) } @@ -767,8 +822,11 @@ extension Duration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularInt64Field(value: &self.intervalUs) + case 1: try { try decoder.decodeSingularInt64Field(value: &self.intervalUs) }() default: break } } @@ -797,9 +855,12 @@ extension PingResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularInt64Field(value: &self.entry) - case 2: try decoder.decodeSingularInt64Field(value: &self.exit) + case 1: try { try decoder.decodeSingularInt64Field(value: &self.entry) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self.exit) }() default: break } } @@ -831,8 +892,11 @@ extension Address: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self.address) + case 1: try { try decoder.decodeSingularStringField(value: &self.address) }() default: break } } @@ -860,8 +924,11 @@ extension AddressList: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeRepeatedStringField(value: &self.addresses) + case 1: try { try decoder.decodeRepeatedStringField(value: &self.addresses) }() default: break } } @@ -889,8 +956,11 @@ extension Balance: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularInt64Field(value: &self.valueZat) + case 1: try { try decoder.decodeSingularInt64Field(value: &self.valueZat) }() default: break } } @@ -918,8 +988,11 @@ extension Exclude: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeRepeatedBytesField(value: &self.txid) + case 1: try { try decoder.decodeRepeatedBytesField(value: &self.txid) }() default: break } } @@ -951,12 +1024,15 @@ extension TreeState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self.network) - case 2: try decoder.decodeSingularUInt64Field(value: &self.height) - case 3: try decoder.decodeSingularStringField(value: &self.hash) - case 4: try decoder.decodeSingularUInt32Field(value: &self.time) - case 5: try decoder.decodeSingularStringField(value: &self.tree) + case 1: try { try decoder.decodeSingularStringField(value: &self.network) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.height) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.hash) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.time) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.tree) }() default: break } } @@ -1002,10 +1078,13 @@ extension GetAddressUtxosArg: SwiftProtobuf.Message, SwiftProtobuf._MessageImple mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeRepeatedStringField(value: &self.addresses) - case 2: try decoder.decodeSingularUInt64Field(value: &self.startHeight) - case 3: try decoder.decodeSingularUInt32Field(value: &self.maxEntries) + case 1: try { try decoder.decodeRepeatedStringField(value: &self.addresses) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.startHeight) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.maxEntries) }() default: break } } @@ -1046,13 +1125,16 @@ extension GetAddressUtxosReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImp mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self.txid) - case 2: try decoder.decodeSingularInt32Field(value: &self.index) - case 3: try decoder.decodeSingularBytesField(value: &self.script) - case 4: try decoder.decodeSingularInt64Field(value: &self.valueZat) - case 5: try decoder.decodeSingularUInt64Field(value: &self.height) - case 6: try decoder.decodeSingularStringField(value: &self.address) + case 1: try { try decoder.decodeSingularBytesField(value: &self.txid) }() + case 2: try { try decoder.decodeSingularInt32Field(value: &self.index) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.script) }() + case 4: try { try decoder.decodeSingularInt64Field(value: &self.valueZat) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.height) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.address) }() default: break } } @@ -1100,8 +1182,11 @@ extension GetAddressUtxosReplyList: SwiftProtobuf.Message, SwiftProtobuf._Messag mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try decoder.decodeRepeatedMessageField(value: &self.addressUtxos) + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.addressUtxos) }() default: break } } diff --git a/Tests/NetworkTests/LightWalletServiceTests.swift b/Tests/NetworkTests/LightWalletServiceTests.swift index d8f8dacc..fe23718c 100644 --- a/Tests/NetworkTests/LightWalletServiceTests.swift +++ b/Tests/NetworkTests/LightWalletServiceTests.swift @@ -61,6 +61,21 @@ class LightWalletServiceTests: XCTestCase { wait(for: [expect], timeout: 10) } + func testHundredBlocks() async throws { + let count = 99 + let lowerRange: BlockHeight = network.constants.saplingActivationHeight + let upperRange: BlockHeight = network.constants.saplingActivationHeight + count + let blockRange = lowerRange ... upperRange + + var blocks: [ZcashCompactBlock] = [] + for try await block in service.blockRange(blockRange) { + blocks.append(block) + } + XCTAssertEqual(blocks.count, blockRange.count) + XCTAssertEqual(blocks[0].height, lowerRange) + XCTAssertEqual(blocks.last!.height, upperRange) + } + func testSyncBlockRange() { let lowerRange: BlockHeight = network.constants.saplingActivationHeight let upperRange: BlockHeight = network.constants.saplingActivationHeight + 99 @@ -74,6 +89,18 @@ class LightWalletServiceTests: XCTestCase { } } + func testSyncBlockRange() async throws { + let lowerRange: BlockHeight = network.constants.saplingActivationHeight + let upperRange: BlockHeight = network.constants.saplingActivationHeight + 99 + let blockRange = lowerRange ... upperRange + + var blocks: [ZcashCompactBlock] = [] + for try await block in service.blockRange(blockRange) { + blocks.append(block) + } + XCTAssertEqual(blocks.count, blockRange.count) + } + func testLatestBlock() { let expect = XCTestExpectation(description: self.description) service.latestBlockHeight { result in @@ -88,4 +115,9 @@ class LightWalletServiceTests: XCTestCase { wait(for: [expect], timeout: 10) } + + func testLatestBlock() async throws { + let height = try await service.latestBlockHeightAsync() + XCTAssertTrue(height > self.network.constants.saplingActivationHeight) + } } diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index 880052fd..b92f0035 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -75,6 +75,10 @@ class DarksideWalletService: LightWalletService { ) } + func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream { + service.blockStream(startHeight: startHeight, endHeight: endHeight) + } + func getInfo() throws -> LightWalletdInfo { try service.getInfo() } @@ -87,7 +91,11 @@ class DarksideWalletService: LightWalletService { } func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - return [] + try service.fetchUTXOs(for: tAddress, height: height) + } + + func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { + try service.fetchUTXOs(for: tAddresses, height: height) } func fetchUTXOs( @@ -98,8 +106,8 @@ class DarksideWalletService: LightWalletService { service.fetchUTXOs(for: tAddress, height: height, result: result) } - func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - try service.fetchUTXOs(for: tAddresses, height: height) + func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream { + service.fetchUTXOs(for: tAddress, height: height) } func fetchUTXOs( @@ -109,7 +117,10 @@ class DarksideWalletService: LightWalletService { ) { service.fetchUTXOs(for: tAddresses, height: height, result: result) } - + + func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream { + service.fetchUTXOs(for: tAddresses, height: height) + } func fetchTransaction(txId: Data) throws -> TransactionEntity { try service.fetchTransaction(txId: txId) @@ -222,6 +233,26 @@ class DarksideWalletService: LightWalletService { func clearAddedUTXOs() throws { _ = try darksideService.clearAddressUtxo(Empty(), callOptions: nil).response.wait() } + + func getInfoAsync() async throws -> LightWalletdInfo { + try service.getInfo() + } + + func latestBlockHeightAsync() async throws -> BlockHeight { + try service.latestBlockHeight() + } + + func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { + service.blockRange(range) + } + + func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse { + try service.submit(spendTransaction: spendTransaction) + } + + func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { + try service.fetchTransaction(txId: txId) + } } enum DarksideWalletDConstants: NetworkConstants { diff --git a/Tests/TestUtils/FakeService.swift b/Tests/TestUtils/FakeService.swift index fa252654..39c60bdc 100644 --- a/Tests/TestUtils/FakeService.swift +++ b/Tests/TestUtils/FakeService.swift @@ -35,6 +35,10 @@ class MockLightWalletService: LightWalletService { return MockCancellable() } + func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream { + AsyncThrowingStream { _ in } + } + func getInfo() throws -> LightWalletdInfo { guard let info = mockLightDInfo else { throw LightWalletServiceError.generalError(message: "Not Implemented") @@ -59,28 +63,30 @@ class MockLightWalletService: LightWalletService { [] } + func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { + [] + } + func fetchUTXOs( for tAddress: String, height: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void ) { } - - func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - [] + + func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream { + AsyncThrowingStream { _ in } } - + func fetchUTXOs( for tAddresses: [String], height: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void ) { } - - func fetchUTXOs( - for tAddress: String, - result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) { + + func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream { + AsyncThrowingStream { _ in } } private var service: LightWalletService @@ -126,4 +132,35 @@ class MockLightWalletService: LightWalletService { func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { } + + func getInfoAsync() async throws -> LightWalletdInfo { + guard let info = mockLightDInfo else { + throw LightWalletServiceError.generalError(message: "Not Implemented") + } + return info + } + + func latestBlockHeightAsync() async throws -> BlockHeight { + latestHeight + } + + func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { + service.blockRange(range) + } + + func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse { + LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage()) + } + + func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { + Transaction(id: 1, transactionId: Data(), created: "Today", transactionIndex: 1, expiryHeight: -1, minedHeight: -1, raw: nil) + } + + func fetchUTXOsAsync(for tAddress: String, height: BlockHeight) async throws -> [UnspentTransactionOutputEntity] { + [] + } + + func fetchUTXOsAsync(for tAddresses: [String], height: BlockHeight) async throws -> [UnspentTransactionOutputEntity] { + [] + } } From 3606a13623399c8c0b401950aea9fc4f8c3ba3c3 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Fri, 26 Aug 2022 13:21:42 -0300 Subject: [PATCH 03/15] [#503] Add new checkpoints on top of 0.16.7-beta and release .8 Closes #503 This commit adds new checkpoints on top 0.16.7-beta and serves as a point to tag the release of 0.16.8-beta --- .../Resources/checkpoints/mainnet/1775000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1777500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1780000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1782500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1785000.json | 8 ++++++++ .../Resources/checkpoints/testnet/2000000.json | 8 ++++++++ .../Resources/checkpoints/testnet/2010000.json | 8 ++++++++ ZcashLightClientKit.podspec | 2 +- changelog.md | 17 +++++++++++++++++ 9 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1777500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1780000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1782500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1785000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2000000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json new file mode 100644 index 00000000..f9b8baa2 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1775000", + "hash": "000000000019cdcd93e8a0704c40cd74e65424e4bfaaa2a21227db81355ccb46", + "time": 1660646308, + "saplingTree": "01597a94eed997b611665ea28eb0fbebf872dc4187b81baf2d7b061d9498e4be07001901289049f3d2fb297ec300241c8ca66400d8cb214272da11c7dddbf249a83b363200019448787f294385a1cf41c26b5ecc3a9f2b552d85dd959d71ad8fb79ab82f315e0000015692b62820a0affe568750d52d557ae99781acf19545f95404b3d7122e00e50d014c13e6f6cd1938716b4870921418d553fbc13ad5e174c720556e43eeedff992b01d5af984301be600c7cf840dc1fbbcdedb2d2e801813bb68a01be00c6822d5a6a017e978b347c274b38ee016285dd14132e83d59786019d1a46ce0163771dfdce0c000000017b24ec22b8e69bfebe3a24693645c8905f2c3756dd67d646d4266c5624ce29520001887d900f0f1c22d239b925d77200c12a7a637dfc7723f08aa34c0808cd1740200001e9358e668fd2780e13975fa8d46cd6ea762252a2ce849e2c82affb1c58edcb190000013eca32c92486829a47625ea4acb38897025abb7af0d1aba2d12c9e828a8d0a4f000000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "0182a273f144dba41ed805d348a8eeae5692b737863da75ac3384af983d54fd109001f01f72799b4c4c3ec3048b5c724b0edf553fdac716a6f2e771b8e40be6f9ece050301eec827aaef0952edafa106766d905c03b29a58b8f502b579e18d6b8a36b9a2120001997dd960a2474458cc8d914bf46232ec351eb5b3ff614e455d0fe6cf1c0d961f01726cc90e9a3d35324d807825bc4eceb7c976735bf8d5072f24f916f11231aa2901e4d4b64f108c937551964335b41b8a6e43bca7b2f2c468f86bb27a938ffb912001395fc86db243265b7a2722b3a49c1ac5449d671980bfaaebd1f8b26d4324451f0001056bee64e920ba6e8604fa32b294591abb40b5fc35b9b6f257b3b465fce371270001ea6a4ce4349b49c0b9788bcf94d27cb8c95f8f5a640919110387cd66f063e113017d6446e58ba6f7f8e7f60594b91bb67739986c89c670ad3996205ca9df7eac1100000001188cb8c8ad6ec4d5a9f90689123894bc1d021fb63d53347246ef21657c04f3110000014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1777500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1777500.json new file mode 100644 index 00000000..64dbcbfd --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1777500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1777500", + "hash": "000000000114eb90f65670123d685d4e1ac4208dcc39a9dc1111060789a2c418", + "time": 1660835117, + "saplingTree": "0179c12f2782a76e421834ecfcd800a5cbd1edc79663d3c58d25d14200c60a2b050019000000000000000001091c24ca6d7180e15a22a46f9fa2d0a90783ce8e8bf4346097b36c469f7859220113d4335546307bb7558b2bfa8a309abb341da3f88acfc3b7d0523519a993662701d99f7f7123501008b4b145d772c66240aa9bdfe1fadd8cd420a3f159126950350185b933c8fc56ff06180fc5d2dbaf7234d615c724b1dbcdd4130ddff0ac732e6e000000019fe9488e3027bf007c951a3706ac7422d242c3ece25c20d5fd8254fdc77d883c000177ac81d2104e4de33629aaf3187c8daff10899ef791b1fbb18d2f2c17296870a0000018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01dde5b2585ed32435eba299a594f328983090c620d3e40c58e2c457c5adbc7835001f01430c6ec1ba7a552b02f897a8789639760786e29fc4ce7e133f4d8768fba4032b0001b99142688612333a2926ebf2b271c50018a4240c9522cdee66b68c4ffa8c3616000172a6fbb0176b644a4a11f6c658324fc5d8140845959cb3c924b190a8ef86e22a0153250a8de904795ee993882737ee5f13458561a0f78d6ab1432e165f0b19ed2f01b54e695a6d674420b22d8b52727140dbde2d83ed45966a2cde91393d89ea6a0101d3f0b23da54e9066a2e4621baa67d255a69f90f788e35975e5fc14ae62eb9b3d012d59e25ff1850760f04238d20341db41c77a069ce6146af0c66693c5a7e84c04014caf0366131977fe12e0ef793ad22fa3f75df7a2bca71acba67bbb206797ff3c0001ce3e5c7e4f546304954681fd0e8f0e68dc38e5c788c99c1cc2a5404c160df93a0001038ca3c0ad85cbc76b94531a2c8d7efc43af084abdc88d2d28b30bc3a8ba4d39000001aa7b1980e5656743063400012808a5a2920431959e0b721c9a915b37ca3b450801c4accd50986a74b77eddf2e5191d20e40035f38bc7c9b91a03339af16c5a7032014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1780000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1780000.json new file mode 100644 index 00000000..14b075a1 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1780000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1780000", + "hash": "000000000092cd2f7d70451422ec93bc0c32dbb4b7ba7bfab4c6bb2f646621cf", + "time": 1661023245, + "saplingTree": "01eb57dc27bf63678d8d3a9d19b422a4694a9d43765bd308abd6ac1ee838dc4524018572cb62805c814962aee358456f07f5494cf3fa7b9ec1bc35dcd8f217e0920b19014241b45704d5126780eede03872b8f87501e4184dc68f977aab8e20d1e83da330001be1052772495777a8224029288e3cbce5f6528380c7dfab653661fa068e43267000000000120d655140e8a7069e4bb227ef3be10337d1fe805f6c143042f39e04047dcaa4801edee36cd129d61db744d5c70d749a066ae0903b5310f309609d014d349b7e435000001e4730f055d123a824efda2abf609b64bca407df2582fb188f15876c17a93183f01428fe4cdc82c312139518ae3cfed4fc769329bc192c724029a38dbf221557a56000176138376dab12f9063c0d4be21178499d1f912756be2dda3cb7540469376905901b261f10e8b41673b7707c93d6a38c9b3ca331d7beaf312b17dc5536bf9ac262500018fc9b4ef24688bf42b782c41232f9ec501b1c4ab6c950b0c41d34eb23b17402800017412114010d9ffb2f7387eda6e29942c0c80b6330788102467e4a0afe325382d018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01b02222e4afcd845dc497c76d04b9f6ddb9081d01b9f9d5a2aa05fbc8d1443b1601a0331c1517205b7e943e9668d61e77e875c662dc96ef7cc96299b6a83890b41d1f0000013c6b5a330ccb2ba9442273535baa2dc7fa6dbf999b72b31afe032c168f4bd7080001e74ec029cd1c152dc8b09f65985d6c0c2c78e56da80c941538520a28587d9f160181a58cf9d3ea78a5685698258689ebe5fa4ce66d0ef9eceb6ae478750ef63139012e734c4d341726e1c02519bca7c04706db73fede3e95e9eb81208265d7b5c51e01e12417482428167d1173531eb01c4f05257c8915bd7b3c81668f7e4c0525d91901673db13a5adedf791c6b644470c5f61d202abec03de1ad93ad9b534e93925e0c01256c8aa3be887192ee742261edd0c2788d1b625442150145f4d3b8ed4905c218017fcf9eb3250652f2d0edd5af7d812909570a857529d71182f1e67fe20131b43a01c11b33655a2af90f359b1372d26da8e92bd8885107b6cc623e96243d56ec051700010d01fcaf34b9d9127ad8aade1bc05ed6c70040a7146a9fd596073bfcea1aad390137638774e0047f7be64682876e9fa29021fb428b367731ff1dc7701b58633e390000000001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1782500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1782500.json new file mode 100644 index 00000000..c3c876fb --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1782500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1782500", + "hash": "0000000000c96f27e270aa37db650cf15c6b75f1fa59a947cca4f41abf004676", + "time": 1661211894, + "saplingTree": "01ed3ce95d71c92565819babefc0f1026beac767dd6a162ac0ae9b15f619446545001900000001ac44d394a0bac1701dd6b74ab80f53729963826696a1da89aae59e12cf4c1b5f01dacf1de9631ba5147d124a2502808ee3eabdccbcaee03b03521cf765324f52710001efe591a8f59c1b2271ff973d8204c588b21747c305a7603cac7476f62424ed0d000000000138279c0da3d1576d92933860f5de697d212036da77df1af780bebe1b0fb1df2700000001734a94d73f408cf9aa047cb69f2b67382cd2eac7ad9fd8d4dca6b9ef4706d93a01323df8cc8affced4411de770643c86329441d276e3d0af469a5387baf14b702e000196ea06c6c9b1bffd35111b29addc63a564d780556def4814bd2bdf44568add0000000190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "0173e8d2617537311c62aa77f610f752e4553fac6d8f8cdb05248941a2afc25f0b001f00011093e632be4376c29ca004fa22ceeb426c283d9006436eba5185b63ec4adab09000001ed13450a8b52b2467727d09d721f8054d408cc007a3574f090bca255d94da90800012d81bec0d20bb2214b91f3caf8d03c66c779b67f06a9908bfb239e25b2156c1400019971eb95fe89873ce0271ed1e70b8ea4498e54a81d4db7b092fefc7b9f540212000146181485362325772638425fe945f012e9b3884886dfc0f5c689f556d40d8032000001cbc56207feb2d10de3c2dffcc93040d4ed95a1d0c653c9587739681a162a601401aad0d336606800990785d124bdfe61ffbd399167bbc344f5855d3d4de468de190001464e545a0f5a15e6c09f82335c092a66ff8f10d0abe8dd0db230ca90dbcc0d2401f11d7f967046007bf41c33b30c0dc3b52c1a4a9cc3a7a85657a2fc11a39d393a0001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1785000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1785000.json new file mode 100644 index 00000000..51e2fe6f --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1785000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1785000", + "hash": "000000000048a2a2cc1132d171fd58ae0483322d33a4ca4e5814d2f928358ddb", + "time": 1661400251, + "saplingTree": "019cdc2862d4223e86a3b2780f9cdf19b2b22431faa4c849d39f681bd01d32a0630145fdfaf100b993c09d16b34c6bb30834fbd9519be36b7cf5adc645fd2727805a190196199419320b54a8acf9a9b6883abf64a7e80fe75a17a6227bd47b8a2636b84700000001c5c6f40ccdd6c8c7c8b8257c1dbed8c9ab5b99320c0b367bd80c471f676dfe3d01324e46f8529d6becc636a9b57eb864b3cc6a19f9e1cf3da6fcd4b96456fdf1350111fe0737c21b236b783ca356947277888a785e569d1c8953d341741df944742800010e4303d3df4670374eec9b5fff8b9b7cead7c3720ccfc57bc05e9f15e005420a019a74161a408604546f401b8616c50dcd38a10efca83d1a5f0bc87f43ba3359670187130c30b7023f7d9a80fe75fcc1c7864007ae29e63c9a85dff0cc60e9055f5201d7a644ef58eeed93ab36be7ef27d90dbe43696ce2c0c2d1969e3de9f1cc9e51c013dc034d5b0eff46aa39a5461cdc285a75705eadf417e14fcb6a07feb4ed3a0530001f3a3cab7e488e34450427f914284cff92aca699a2e3bee42c42212419bb72a00000001c9c6bba2bb2a19be1e993af5f9bc35979454afa6237a44ad46ed0cd06c7eca4901e1a9472dadbe9da7956818483b066069ad0030dee06a787200e5519a5e0b96600001757bfb5957a0fa9b4fc9e8c105df1e9f0f9e156d653ba54a5fd2ebaa237afa510190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01fcefe6872d4fa4b8bf4cde106616dd137593b43fd761478eb4d1dd6715a4cc21001f0000000118ef33685241afb399b6d9c0697404998528096ae550dcd75365912cfb64953100019ccc0dc23d60dddcadb86ecabcfe1e5d42829a741f7e6705a368ead59ac492260158cda00196a2f05b86d770724944d4db0ff55d7442962dec634e3968822df01b019f815362606a86082ca9c3a332d4fbea2faef7c6be9cf4f248fa09672ab6012301ce1619c190c7fb2f62560a1723b9daf6ab97d03406bc4537ef4da5dbe4ea993e0000000001cbd9a766f5802ce880a9741919503a6b5b2e58104d1331b7c477d758aad8fe1400018ea310e6e8006b04172395acba6ef11cbef90cf053489a7f8b3287be3935ed3d0000019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2000000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2000000.json new file mode 100644 index 00000000..70647786 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2000000.json @@ -0,0 +1,8 @@ +{ + "network": "test", + "height": "2000000", + "hash": "000bb3c6d575a155a56b1aab0e7f14a1141d25c1f36aa6f15c1cb1ef1d7d498c", + "time": 1660621966, + "saplingTree": "0133914238639e4f22ddc8ca2cab7e2d7ec676b06f12a0bf42bc757909cec4f47200100103217f60b7d9bdc625ecd5d6170e3ab57a81eb29f7beacecd07c4f3f0d61ef2801066673d6ab6b62a91f6e04a49fc0e74083cfcccdc851db681e01db333ee1cb470000018da55ded8e55006c2a6c41474da220a9a179a85a99a48df0a31a74291317720e00012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39", + "orchardTree": "01a682706317caa5aec999385ac580445ff4eff6347e4a3c844ac18fcb5fe9bf1c01cca6f37237f27037fa7f8fe5ec8d2cc251b791cfb9cdd08cd1215229fa9435221f0001590d3e7e3f4cd572274f79f4a95b41fa72ed9b42a7c6dbcaec9637eaf368ac0e0000018843337920418307fa7699d506bb0f47a79aea7f6fe8efc1e25b9dde8966e22f013b5a8ef020d8b30fa8beb8406dd30b2a1944755f5549713e4fe24de78ab72e12000001a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json new file mode 100644 index 00000000..2e7d62f6 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json @@ -0,0 +1,8 @@ +{ + "network": "test", + "height": "2010000", + "hash": "0013159a578c874aeecddad8707b8e274a018078fd80fc3b9e2d04065abeb05d", + "time": 1661274815, + "saplingTree": "012bd51ef4da530bb488d43a0f770109df7186ef164a64f618038bb00b7861840a00100001d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39", + "orchardTree": "014b0ab9afb38a6c819d64806b3de5397c6469ca0f7e58274f5c779ecb75d94217018646890daed861a8f66a3966e884da1b1c8240bb47327a348fae9169eb8205051f000118681f82320257f5a5cfc5c846fd4e88641d4ed7b87fcd5ec8e65e68e3ab0636000155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000" +} diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 9bf7862f..907fea8d 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.16.7-beta' + s.version = '0.16.8-beta' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC diff --git a/changelog.md b/changelog.md index 01d28f34..48b2a04a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,20 @@ +# 0.16.8-beta +Checkpoints added: +Mainnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1777500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1780000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1782500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1785000.json +```` + +Testnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2000000.json +Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json +```` + # 0.16.7-beta - [#455] revert queue priority downgrade changes from [#435] (#456) From 742e6bd8ec5a2d1214c15c2a32c7c96c338383da Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Fri, 26 Aug 2022 19:52:12 +0200 Subject: [PATCH 04/15] [#464] CompactBlockStorage To async/await (#494) - await/async APIs provided - async throws unit tests using new API implemented [464] CompactBlockStorage To async/await (494) - removed deprecated closure APIs - upgraded use of the async APIs - tests updated --- .../DatabaseStorage/CompactBlockStorage.swift | 39 +++++-------- .../Block/Downloader/BlockDownloader.swift | 57 ++++++++++--------- .../Repository/CompactBlockRepository.swift | 16 ++---- .../DarksideTests/BlockDownloaderTests.swift | 21 ++++--- .../CompactBlockStorageTests.swift | 51 ++++++++++++++++- Tests/TestUtils/FakeStorage.swift | 32 ++++------- Tests/TestUtils/Stubs.swift | 4 ++ 7 files changed, 129 insertions(+), 91 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift index 9a2166a9..3afb6d3f 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift @@ -85,39 +85,28 @@ extension CompactBlockStorage: CompactBlockRepository { try latestBlockHeight() } - func latestHeight(result: @escaping (Swift.Result) -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - do { - result(.success(try self.latestBlockHeight())) - } catch { - result(.failure(error)) - } + func latestHeightAsync() async throws -> BlockHeight { + let task = Task(priority: .userInitiated) { + try latestBlockHeight() } + return try await task.value } - + func write(blocks: [ZcashCompactBlock]) throws { try insert(blocks) } - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) { - DispatchQueue.global(qos: .userInitiated).async { - do { - try self.insert(blocks) - completion?(nil) - } catch { - completion?(error) - } + func writeAsync(blocks: [ZcashCompactBlock]) async throws { + let task = Task(priority: .userInitiated) { + try insert(blocks) } + try await task.value } - - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) { - DispatchQueue.global(qos: .userInitiated).async { - do { - try self.rewind(to: height) - completion?(nil) - } catch { - completion?(error) - } + + func rewindAsync(to height: BlockHeight) async throws { + let task = Task(priority: .userInitiated) { + try rewind(to: height) } + try await task.value } } diff --git a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift index f6da6a85..9511d63a 100644 --- a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift +++ b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift @@ -16,6 +16,13 @@ enum CompactBlockDownloadError: Error { Represents what a compact block downloaded should provide to its clients */ public protocol CompactBlockDownloading { + + /** + Downloads and stores the given block range. + Blocking + */ + func downloadBlockRange(_ range: CompactBlockRange) throws + /** Downloads and stores the given block range. Non-Blocking @@ -46,12 +53,6 @@ public protocol CompactBlockDownloading { */ func latestBlockHeight(result: @escaping (Result) -> Void) - /** - Downloads and stores the given block range. - Blocking - */ - func downloadBlockRange(_ range: CompactBlockRange) throws - /** Restore the download progress up to the given height. */ @@ -176,18 +177,17 @@ extension CompactBlockDownloader: CompactBlockDownloading { _ heightRange: CompactBlockRange, completion: @escaping (Error?) -> Void ) { - lightwalletService.blockRange(heightRange) { [weak self] result in - guard let self = self else { - return - } - - switch result { - case .failure(let error): - completion(error) - case .success(let compactBlocks): - self.storage.write(blocks: compactBlocks) { storeError in - completion(storeError) + let stream: AsyncThrowingStream = lightwalletService.blockRange(heightRange) + Task { + do { + var compactBlocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + compactBlocks.append(compactBlock) } + try await self.storage.writeAsync(blocks: compactBlocks) + completion(nil) + } catch { + completion(error) } } } @@ -198,20 +198,25 @@ extension CompactBlockDownloader: CompactBlockDownloading { } func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) { - storage.rewind(to: height) { e in - completion(e) + Task { + do { + try await storage.rewindAsync(to: height) + completion(nil) + } catch { + completion(error) + } } } func lastDownloadedBlockHeight(result: @escaping (Result) -> Void) { - storage.latestHeight { heightResult in - switch heightResult { - case .failure(let e): - result(.failure(CompactBlockDownloadError.generalError(error: e))) - return - case .success(let height): - result(.success(height)) + Task { + do { + let latestHeight = try await storage.latestHeightAsync() + result(.success(latestHeight)) + } catch { + result(.failure(CompactBlockDownloadError.generalError(error: error))) } + } } diff --git a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift index caddd86f..94473b52 100644 --- a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift +++ b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift @@ -28,11 +28,9 @@ protocol CompactBlockRepository { /** Gets the highest block that is currently stored. Non-Blocking - - - Parameter result: closure resulting on either the latest height or an error */ - func latestHeight(result: @escaping (Result) -> Void) - + func latestHeightAsync() async throws -> BlockHeight + /** Write the given blocks to this store, which may be anything from an in-memory cache to a DB. Blocking @@ -46,10 +44,9 @@ protocol CompactBlockRepository { Non-Blocking - Parameters: - Parameter blocks: array of blocks to be written to storage - - Parameter completion: a closure that will be called after storing the blocks */ - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) - + func writeAsync(blocks: [ZcashCompactBlock]) async throws + /** Remove every block above and including the given height. @@ -66,10 +63,7 @@ protocol CompactBlockRepository { After this operation, the data store will look the same as one that has not yet stored the given block height. Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49. - - Parameters: - Parameter height: the height to rewind to - - Parameter completion: a closure that will be called after storing the blocks - */ - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) + func rewindAsync(to height: BlockHeight) async throws } diff --git a/Tests/DarksideTests/BlockDownloaderTests.swift b/Tests/DarksideTests/BlockDownloaderTests.swift index dfc38abb..d625b503 100644 --- a/Tests/DarksideTests/BlockDownloaderTests.swift +++ b/Tests/DarksideTests/BlockDownloaderTests.swift @@ -51,15 +51,20 @@ class BlockDownloaderTests: XCTestCase { expect.fulfill() XCTAssertNil(error) - // check what was 'stored' - self.storage.latestHeight { result in - expect.fulfill() - - XCTAssertTrue(self.validate(result: result, against: upperRange)) - - self.downloader.lastDownloadedBlockHeight { resultHeight in + Task { + do { + // check what was 'stored' + let latestHeight = try await self.storage.latestHeightAsync() expect.fulfill() - XCTAssertTrue(self.validate(result: resultHeight, against: upperRange)) + + XCTAssertEqual(latestHeight, upperRange) + + self.downloader.lastDownloadedBlockHeight { resultHeight in + expect.fulfill() + XCTAssertTrue(self.validate(result: resultHeight, against: upperRange)) + } + } catch { + XCTFail("testSmallDownloadAsync() shouldn't fail") } } } diff --git a/Tests/OfflineTests/CompactBlockStorageTests.swift b/Tests/OfflineTests/CompactBlockStorageTests.swift index 6479a0ec..74d26887 100644 --- a/Tests/OfflineTests/CompactBlockStorageTests.swift +++ b/Tests/OfflineTests/CompactBlockStorageTests.swift @@ -19,7 +19,12 @@ class CompactBlockStorageTests: XCTestCase { func testEmptyStorage() { XCTAssertEqual(try! compactBlockDao.latestHeight(), BlockHeight.empty()) } - + + func testEmptyStorageAsync() async throws { + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(latestHeight, BlockHeight.empty()) + } + func testStoreThousandBlocks() { let initialHeight = try! compactBlockDao.latestHeight() let startHeight = self.network.constants.saplingActivationHeight @@ -38,6 +43,19 @@ class CompactBlockStorageTests: XCTestCase { XCTAssertEqual(latestHeight, finalHeight) } + func testStoreThousandBlocksAsync() async throws { + let initialHeight = try! compactBlockDao.latestHeight() + let startHeight = self.network.constants.saplingActivationHeight + let blockCount = Int(1_000) + let finalHeight = startHeight + blockCount + + try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertNotEqual(initialHeight, latestHeight) + XCTAssertEqual(latestHeight, finalHeight) + } + func testStoreOneBlockFromEmpty() { let initialHeight = try! compactBlockDao.latestHeight() guard initialHeight == BlockHeight.empty() else { @@ -61,6 +79,24 @@ class CompactBlockStorageTests: XCTestCase { } } + func testStoreOneBlockFromEmptyAsync() async throws { + let initialHeight = try await compactBlockDao.latestHeightAsync() + guard initialHeight == BlockHeight.empty() else { + XCTFail("database not empty, latest height: \(initialHeight)") + return + } + + let expectedHeight = BlockHeight(123_456) + guard let block = StubBlockCreator.createRandomDataBlock(with: expectedHeight) else { + XCTFail("could not create randem block with height: \(expectedHeight)") + return + } + try await compactBlockDao.writeAsync(blocks: [block]) + + let result = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(result, expectedHeight) + } + func testRewindTo() { let startHeight = self.network.constants.saplingActivationHeight let blockCount = Int(1_000) @@ -82,4 +118,17 @@ class CompactBlockStorageTests: XCTestCase { XCTFail("Rewind latest block failed with error: \(error)") } } + + func testRewindToAsync() async throws { + let startHeight = self.network.constants.saplingActivationHeight + let blockCount = Int(1_000) + let finalHeight = startHeight + blockCount + + try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + let rewindHeight = BlockHeight(finalHeight - 233) + + try await compactBlockDao.rewindAsync(to: rewindHeight) + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(latestHeight, rewindHeight - 1) + } } diff --git a/Tests/TestUtils/FakeStorage.swift b/Tests/TestUtils/FakeStorage.swift index 8ba79084..1f7613fb 100644 --- a/Tests/TestUtils/FakeStorage.swift +++ b/Tests/TestUtils/FakeStorage.swift @@ -10,6 +10,18 @@ import Foundation @testable import ZcashLightClientKit class ZcashConsoleFakeStorage: CompactBlockRepository { + func latestHeightAsync() async throws -> BlockHeight { + latestBlockHeight + } + + func writeAsync(blocks: [ZcashCompactBlock]) async throws { + fakeSave(blocks: blocks) + } + + func rewindAsync(to height: BlockHeight) async throws { + fakeRewind(to: height) + } + func latestHeight() throws -> Int { return self.latestBlockHeight } @@ -29,12 +41,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { self.latestBlockHeight = latestBlockHeight } - func latestHeight(result: @escaping (Result) -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - result(.success(self.latestBlockHeight)) - } - } - private func fakeSave(blocks: [ZcashCompactBlock]) { blocks.forEach { LoggerProxy.debug("saving block \($0)") @@ -42,20 +48,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { } } - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - self.fakeSave(blocks: blocks) - completion?(nil) - } - } - - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - self.fakeRewind(to: height) - completion?(nil) - } - } - private func fakeRewind(to height: BlockHeight) { LoggerProxy.debug("rewind to \(height)") self.latestBlockHeight = min(self.latestBlockHeight, height) diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 70ea9a36..4dc044c2 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -33,6 +33,10 @@ class AwfulLightWalletService: MockLightWalletService { } } + override func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in continuation.finish(throwing: LightWalletServiceError.invalidBlock) } + } + override func submit(spendTransaction: Data, result: @escaping(Result) -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { result(.failure(LightWalletServiceError.invalidBlock)) From 16d1948b5b19ea826ef6404c409459bacfdc671f Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 29 Aug 2022 21:31:01 +0200 Subject: [PATCH 05/15] [#465] CompactBlockDownloading to Async/Await (#507) Closes #465 - CompactBlockDownloading closures removed - CompactBlockDownloading new async API provided --- .../Block/Downloader/BlockDownloader.swift | 165 ++++++++---------- .../CompactBlockDownloadOperation.swift | 31 +++- .../FetchUnspentTxOutputsOperation.swift | 4 +- .../Synchronizer/SDKSynchronizer.swift | 9 +- .../DarksideTests/BlockDownloaderTests.swift | 47 ++--- 5 files changed, 128 insertions(+), 128 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift index 9511d63a..d6bc7b3a 100644 --- a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift +++ b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift @@ -27,49 +27,44 @@ public protocol CompactBlockDownloading { Downloads and stores the given block range. Non-Blocking */ - func downloadBlockRange( - _ heightRange: CompactBlockRange, - completion: @escaping (Error?) -> Void - ) - - /** - Remove newer blocks and go back to the given height - - Parameters: - - height: the given height to rewind to - - completion: block to be executed after completing rewind - */ - func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) - - /** - returns the height of the latest compact block stored locally - BlockHeight.empty() if no blocks are stored yet - non-blocking - */ - func lastDownloadedBlockHeight(result: @escaping (Result) -> Void) - - /** - Returns the last height on the blockchain - Non-blocking - */ - func latestBlockHeight(result: @escaping (Result) -> Void) - + func downloadBlockRangeAsync(_ heightRange: CompactBlockRange) async throws + /** Restore the download progress up to the given height. */ func rewind(to height: BlockHeight) throws + /** + Remove newer blocks and go back to the given height + - Parameter height: the given height to rewind to + */ + func rewindAsync(to height: BlockHeight) async throws + /** Returns the height of the latest compact block stored locally. BlockHeight.empty() if no blocks are stored yet Blocking */ func lastDownloadedBlockHeight() throws -> BlockHeight - + + /** + returns the height of the latest compact block stored locally + BlockHeight.empty() if no blocks are stored yet + non-blocking + */ + func lastDownloadedBlockHeightAsync() async throws -> BlockHeight + /** Returns the latest block height Blocking */ func latestBlockHeight() throws -> BlockHeight + + /** + Returns the last height on the blockchain + Non-blocking + */ + func latestBlockHeightAsync() async throws -> BlockHeight /** Gets the transaction for the Id given @@ -82,17 +77,30 @@ public protocol CompactBlockDownloading { /** Gets the transaction for the Id given - Parameter txId: Data representing the transaction Id - - Parameter result: a handler for the result of the operation */ - func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) - + func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity + func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] + // TODO: will be removed with the issue 474 + // https://github.com/zcash/ZcashLightClientKit/issues/474 + // Use the new API fetchUnspentTransactionOutputs(...) -> AsyncThrowingStream func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) + + func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] - func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) + // TODO: will be removed with the issue 474 + // https://github.com/zcash/ZcashLightClientKit/issues/474 + // Use the new API fetchUnspentTransactionOutputs(...) -> AsyncThrowingStream + func fetchUnspentTransactionOutputs( + tAddresses: [String], + startHeight: BlockHeight, + result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void + ) + + func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) -> AsyncThrowingStream func closeConnection() } @@ -138,6 +146,10 @@ extension CompactBlockDownloader: CompactBlockDownloading { } } } + + func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight ) -> AsyncThrowingStream { + lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight) + } func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] { try lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) @@ -154,72 +166,54 @@ extension CompactBlockDownloader: CompactBlockDownloading { } } - func latestBlockHeight(result: @escaping (Result) -> Void) { - lightwalletService.latestBlockHeight { fetchResult in - switch fetchResult { - case .failure(let error): - result(.failure(error)) - case .success(let height): - result(.success(height)) - } - } + func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream { + lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) + } + + func latestBlockHeightAsync() async throws -> BlockHeight { + try await lightwalletService.latestBlockHeightAsync() } func latestBlockHeight() throws -> BlockHeight { try lightwalletService.latestBlockHeight() } - /** - Downloads and stores the given block range. - Non-Blocking - */ - func downloadBlockRange( - _ heightRange: CompactBlockRange, - completion: @escaping (Error?) -> Void - ) { - let stream: AsyncThrowingStream = lightwalletService.blockRange(heightRange) - Task { - do { - var compactBlocks: [ZcashCompactBlock] = [] - for try await compactBlock in stream { - compactBlocks.append(compactBlock) - } - try await self.storage.writeAsync(blocks: compactBlocks) - completion(nil) - } catch { - completion(error) - } - } - } - func downloadBlockRange(_ range: CompactBlockRange) throws { let blocks = try lightwalletService.blockRange(range) try storage.write(blocks: blocks) } - - func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) { - Task { - do { - try await storage.rewindAsync(to: height) - completion(nil) - } catch { - completion(error) + + func downloadBlockRangeAsync( _ heightRange: CompactBlockRange) async throws { + let stream: AsyncThrowingStream = lightwalletService.blockRange(heightRange) + do { + var compactBlocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + compactBlocks.append(compactBlock) } + try await self.storage.writeAsync(blocks: compactBlocks) + } catch { + throw error } } - func lastDownloadedBlockHeight(result: @escaping (Result) -> Void) { - Task { - do { - let latestHeight = try await storage.latestHeightAsync() - result(.success(latestHeight)) - } catch { - result(.failure(CompactBlockDownloadError.generalError(error: error))) - } - + func rewindAsync(to height: BlockHeight) async throws { + do { + try await storage.rewindAsync(to: height) + } catch { + throw error } } + func lastDownloadedBlockHeightAsync() async throws -> BlockHeight { + do { + let latestHeight = try await storage.latestHeightAsync() + return latestHeight + } catch { + throw CompactBlockDownloadError.generalError(error: error) + } + } + + func rewind(to height: BlockHeight) throws { try self.storage.rewind(to: height) } @@ -232,14 +226,7 @@ extension CompactBlockDownloader: CompactBlockDownloading { try lightwalletService.fetchTransaction(txId: txId) } - func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { - lightwalletService.fetchTransaction(txId: txId) { txResult in - switch txResult { - case .failure(let error): - result(.failure(error)) - case .success(let transaction): - result(.success(transaction)) - } - } + func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { + try await lightwalletService.fetchTransactionAsync(txId: txId) } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift index 5a2ce023..8055b71b 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift @@ -14,7 +14,9 @@ class CompactBlockDownloadOperation: ZcashOperation { private var downloader: CompactBlockDownloading private var range: CompactBlockRange - + private var cancelableTask: Task? + private var done = false + required init(downloader: CompactBlockDownloading, range: CompactBlockRange) { self.range = range self.downloader = downloader @@ -28,12 +30,29 @@ class CompactBlockDownloadOperation: ZcashOperation { return } self.startedHandler?() - do { - try downloader.downloadBlockRange(range) - } catch { - self.error = error - self.fail() + + cancelableTask = Task { + do { + try await downloader.downloadBlockRangeAsync(range) + self.done = true + } catch { + self.fail(error: error) + } } + + while !done && !isCancelled { + sleep(1) + } + } + + override func fail(error: Error? = nil) { + self.cancelableTask?.cancel() + super.fail(error: error) + } + + override func cancel() { + self.cancelableTask?.cancel() + super.cancel() } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift index fd7a8210..ad644a4e 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift @@ -66,7 +66,9 @@ class FetchUnspentTxOutputsOperation: ZcashOperation { throw FetchUTXOError.clearingFailed(error) } - let utxos = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) + // TODO: will be replaced by new async API, issue 474 + // https://github.com/zcash/ZcashLightClientKit/issues/474 + let utxos: [UnspentTransactionOutputEntity] = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) let result = storeUTXOs(utxos, in: dataDb) diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index dd682704..66c3e1f8 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -631,7 +631,14 @@ public class SDKSynchronizer: Synchronizer { } public func latestHeight(result: @escaping (Result) -> Void) { - blockProcessor.downloader.latestBlockHeight(result: result) + Task { + do { + let latestBlockHeight = try await blockProcessor.downloader.latestBlockHeightAsync() + result(.success(latestBlockHeight)) + } catch { + result(.failure(error)) + } + } } public func latestHeight() throws -> BlockHeight { diff --git a/Tests/DarksideTests/BlockDownloaderTests.swift b/Tests/DarksideTests/BlockDownloaderTests.swift index d625b503..b89eee9f 100644 --- a/Tests/DarksideTests/BlockDownloaderTests.swift +++ b/Tests/DarksideTests/BlockDownloaderTests.swift @@ -40,36 +40,23 @@ class BlockDownloaderTests: XCTestCase { try? FileManager.default.removeItem(at: cacheDB) } - func testSmallDownloadAsync() { - let expect = XCTestExpectation(description: self.description) - expect.expectedFulfillmentCount = 3 + func testSmallDownloadAsync() async { let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99 let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) - downloader.downloadBlockRange(range) { error in - expect.fulfill() - XCTAssertNil(error) + do { + try await downloader.downloadBlockRangeAsync(range) - Task { - do { - // check what was 'stored' - let latestHeight = try await self.storage.latestHeightAsync() - expect.fulfill() - - XCTAssertEqual(latestHeight, upperRange) - - self.downloader.lastDownloadedBlockHeight { resultHeight in - expect.fulfill() - XCTAssertTrue(self.validate(result: resultHeight, against: upperRange)) - } - } catch { - XCTFail("testSmallDownloadAsync() shouldn't fail") - } - } + // check what was 'stored' + let latestHeight = try await self.storage.latestHeightAsync() + XCTAssertEqual(latestHeight, upperRange) + + let resultHeight = try await self.downloader.lastDownloadedBlockHeightAsync() + XCTAssertEqual(resultHeight, upperRange) + } catch { + XCTFail("testSmallDownloadAsync() shouldn't fail") } - - wait(for: [expect], timeout: 2) } func testSmallDownload() { @@ -99,7 +86,7 @@ class BlockDownloaderTests: XCTestCase { XCTAssertEqual(currentLatest, upperRange ) } - func testFailure() { + func testFailure() async { let awfulDownloader = CompactBlockDownloader( service: AwfulLightWalletService( latestBlockHeight: self.network.constants.saplingActivationHeight + 1000, @@ -108,18 +95,16 @@ class BlockDownloaderTests: XCTestCase { storage: ZcashConsoleFakeStorage() ) - let expect = XCTestExpectation(description: self.description) - expect.expectedFulfillmentCount = 1 let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99 let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) - - awfulDownloader.downloadBlockRange(range) { error in - expect.fulfill() + + do { + try await awfulDownloader.downloadBlockRangeAsync(range) + } catch { XCTAssertNotNil(error) } - wait(for: [expect], timeout: 2) } } From be24044b5101729e59af444637aa707165b62d49 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 29 Aug 2022 21:53:49 +0200 Subject: [PATCH 06/15] [#470] CompactBlockStreamDownloadOperation to async (#506) - using new sync APIs for storage and service - whole logic wrapped in the Task Closes #470 --- .../CompactBlockDownloadOperation.swift | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift index 8055b71b..93ec2412 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift @@ -71,7 +71,6 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { private var storage: CompactBlockStorage private var service: LightWalletService private var done = false - private var cancelable: CancellableCall? private var cancelableTask: Task? private var startHeight: BlockHeight? private var targetHeight: BlockHeight? @@ -120,59 +119,53 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { } self.startedHandler?() - do { - if self.targetHeight == nil { - self.targetHeight = try service.latestBlockHeight() - } - guard let latestHeight = self.targetHeight else { - throw LightWalletServiceError.generalError(message: "missing target height on block stream operation") - } - let latestDownloaded = try storage.latestHeight() - let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded) - - let stream = service.blockStream( - startHeight: startHeight, - endHeight: latestHeight - ) - - cancelableTask = Task { - do { - for try await zcashCompactBlock in stream { - try self.cache(zcashCompactBlock, flushCache: false) - let progress = BlockProgress( - startHeight: startHeight, - targetHeight: latestHeight, - progressHeight: zcashCompactBlock.height - ) - self.progressDelegate?.progressUpdated(.download(progress)) - } - try self.flush() + cancelableTask = Task { + do { + if self.targetHeight == nil { + self.targetHeight = try await service.latestBlockHeightAsync() + } + guard let latestHeight = self.targetHeight else { + throw LightWalletServiceError.generalError(message: "missing target height on block stream operation") + } + let latestDownloaded = try await storage.latestHeightAsync() + let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded) + + let stream = service.blockStream( + startHeight: startHeight, + endHeight: latestHeight + ) + + for try await zcashCompactBlock in stream { + try self.cache(zcashCompactBlock, flushCache: false) + let progress = BlockProgress( + startHeight: startHeight, + targetHeight: latestHeight, + progressHeight: zcashCompactBlock.height + ) + self.progressDelegate?.progressUpdated(.download(progress)) + } + try self.flush() + self.done = true + } catch { + if let err = error as? LightWalletServiceError, case .userCancelled = err { self.done = true - } catch { - if let err = error as? LightWalletServiceError, case .userCancelled = err { - self.done = true - } else { - self.fail(error: error) - } + } else { + self.fail(error: error) } } - - while !done && !isCancelled { - sleep(1) - } - } catch { - self.fail(error: error) + } + + while !done && !isCancelled { + sleep(1) } } override func fail(error: Error? = nil) { - self.cancelable?.cancel() self.cancelableTask?.cancel() super.fail(error: error) } override func cancel() { - self.cancelable?.cancel() self.cancelableTask?.cancel() super.cancel() } From 29f159845d55abaefe4aabdb9886dc7d4badd340 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 30 Aug 2022 15:56:39 +0200 Subject: [PATCH 07/15] [#471] CompactBlockValidationOperation to async (#515) - CompactBlockValidationOperation wrapped to Task --- .../CompactBlockValidationInformation.swift | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift index fdcf11cc..b778bcdb 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift @@ -22,7 +22,9 @@ class CompactBlockValidationOperation: ZcashOperation { private var cacheDb: URL private var dataDb: URL private var network: NetworkType - + private var cancelableTask: Task? + private var done = false + init( rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, @@ -44,23 +46,40 @@ class CompactBlockValidationOperation: ZcashOperation { self.startedHandler?() - let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network) - - switch result { - case 0: - let error = CompactBlockValidationError.failedWithError(rustBackend.lastError()) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + cancelableTask = Task { + let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network) - case ZcashRustBackendWeldingConstants.validChain: - break - - default: - let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result)) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + switch result { + case 0: + let error = CompactBlockValidationError.failedWithError(rustBackend.lastError()) + self.error = error + LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") + self.fail(error: error) + + case ZcashRustBackendWeldingConstants.validChain: + self.done = true + break + + default: + let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result)) + self.error = error + LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") + self.fail(error: error) + } + } + + while !done && !isCancelled { + sleep(1) } } + + override func fail(error: Error? = nil) { + self.cancelableTask?.cancel() + super.fail(error: error) + } + + override func cancel() { + self.cancelableTask?.cancel() + super.cancel() + } } From b684a2f4864934cbd547159ac5922dbc3b54e27d Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 30 Aug 2022 16:00:48 +0200 Subject: [PATCH 08/15] [#474] FetchUnspentTxOutputsOperation to async/await (#514) - wrapped to Task - downloader uses the new async API --- .../FetchUnspentTxOutputsOperation.swift | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift index ad644a4e..06765bc2 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift @@ -24,7 +24,9 @@ class FetchUnspentTxOutputsOperation: ZcashOperation { private var startHeight: BlockHeight private var network: NetworkType private var dataDb: URL - + private var cancelableTask: Task? + private var done = false + init( accountRepository: AccountRepository, downloader: CompactBlockDownloading, @@ -49,32 +51,41 @@ class FetchUnspentTxOutputsOperation: ZcashOperation { self.startedHandler?() - do { - let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) + cancelableTask = Task { do { - for tAddress in tAddresses { - guard try self.rustbackend.clearUtxos( - dbData: dataDb, - address: tAddress, - sinceHeight: startHeight - 1, - networkType: network - ) >= 0 else { - throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") + let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) + do { + for tAddress in tAddresses { + guard try self.rustbackend.clearUtxos( + dbData: dataDb, + address: tAddress, + sinceHeight: startHeight - 1, + networkType: network + ) >= 0 else { + throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") + } } + } catch { + throw FetchUTXOError.clearingFailed(error) } + + var utxos: [UnspentTransactionOutputEntity] = [] + let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) + for try await transaction in stream { + utxos.append(transaction) + } + + let result = storeUTXOs(utxos, in: dataDb) + + self.fetchedUTXOsHandler?(result) + self.done = true } catch { - throw FetchUTXOError.clearingFailed(error) + self.fail(error: error) } - - // TODO: will be replaced by new async API, issue 474 - // https://github.com/zcash/ZcashLightClientKit/issues/474 - let utxos: [UnspentTransactionOutputEntity] = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) - - let result = storeUTXOs(utxos, in: dataDb) - - self.fetchedUTXOsHandler?(result) - } catch { - self.fail(error: error) + } + + while !done && !isCancelled { + sleep(1) } } @@ -102,4 +113,14 @@ class FetchUnspentTxOutputsOperation: ZcashOperation { return (inserted: refreshed, skipped: skipped) } + + override func fail(error: Error? = nil) { + self.cancelableTask?.cancel() + super.fail(error: error) + } + + override func cancel() { + self.cancelableTask?.cancel() + super.cancel() + } } From 87f50a796cdacd1ba2feab8199cdbfaf474705de Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 30 Aug 2022 16:07:36 +0200 Subject: [PATCH 09/15] [#473] CompactBlockEnhancementOperation to async/await (#513) - Wrapped to Task - Downloader APIs upgraded to async one --- .../CompactBlockEnhancementOperation.swift | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift index cb1fd950..1d19fca7 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift @@ -29,7 +29,9 @@ class CompactBlockEnhancementOperation: ZcashOperation { private(set) var network: NetworkType private weak var progressDelegate: CompactBlockProgressDelegate? private var dataDb: URL - + private var cancelableTask: Task? + private var done = false + init( rustWelding: ZcashRustBackendWelding.Type, dataDb: URL, @@ -58,44 +60,50 @@ class CompactBlockEnhancementOperation: ZcashOperation { self.startedHandler?() - // fetch transactions - do { - guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else { - LoggerProxy.debug("no transactions detected on range: \(range.printRange)") - return - } - - for index in 0 ..< transactions.count { - let transaction = transactions[index] - var retry = true - - while retry && self.retries < maxRetries { - do { - let confirmedTx = try enhance(transaction: transaction) - retry = false - self.reportProgress( - totalTransactions: transactions.count, - enhanced: index + 1, - txEnhanced: confirmedTx - ) - } catch { - self.retries += 1 - LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") - if retries > maxRetries { - throw error + cancelableTask = Task { + // fetch transactions + do { + guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else { + LoggerProxy.debug("no transactions detected on range: \(range.printRange)") + return + } + + for index in 0 ..< transactions.count { + let transaction = transactions[index] + var retry = true + + while retry && self.retries < maxRetries { + do { + let confirmedTx = try await enhance(transaction: transaction) + retry = false + self.reportProgress( + totalTransactions: transactions.count, + enhanced: index + 1, + txEnhanced: confirmedTx + ) + } catch { + self.retries += 1 + LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") + if retries > maxRetries { + throw error + } } } } + } catch { + LoggerProxy.error("error enhancing transactions! \(error)") + self.fail(error: error) + return } - } catch { - LoggerProxy.error("error enhancing transactions! \(error)") - self.error = error - self.fail() - return + + if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) { + handler(foundTxs, self.range) + } + self.done = true } - if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) { - handler(foundTxs, self.range) + while !done && !isCancelled { + sleep(1) } } @@ -112,10 +120,10 @@ class CompactBlockEnhancementOperation: ZcashOperation { ) } - func enhance(transaction: TransactionEntity) throws -> ConfirmedTransactionEntity { + func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") - let transaction = try downloader.fetchTransaction(txId: transaction.transactionId) + let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) let transactionID = transaction.transactionId.toHexStringTxId() let block = String(describing: transaction.minedHeight) @@ -148,6 +156,16 @@ class CompactBlockEnhancementOperation: ZcashOperation { } return confirmedTx } + + override func fail(error: Error? = nil) { + self.cancelableTask?.cancel() + super.fail(error: error) + } + + override func cancel() { + self.cancelableTask?.cancel() + super.cancel() + } } private extension BlockRange { From 7b90e598ad5f151b8c92acfea138df32b4c5409c Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 30 Aug 2022 16:28:00 +0200 Subject: [PATCH 10/15] [#472] CompactBlockBatchScanningOperation to async (#505) - CompactBlockBatchScanningOperation operation's main reimplemented to be Task based [472] CompactBlockBatchScanningOperation to async cleanup [472] CompactBlockBatchScanningOperation to async (505) - CompactBlockBatchScanningOperation wrapped to Task --- .../CompactBlockDownloadOperation.swift | 108 +++++++------ .../CompactBlockScanningOperation.swift | 144 ++++++++++-------- 2 files changed, 140 insertions(+), 112 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift index 93ec2412..61d1734c 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift @@ -194,10 +194,11 @@ class CompactBlockBatchDownloadOperation: ZcashOperation { override var isAsynchronous: Bool { false } private var batch: Int + private var done = false private var maxRetries: Int private var storage: CompactBlockStorage private var service: LightWalletService - private var cancelable: CancellableCall? + private var cancelableTask: Task? private var startHeight: BlockHeight private var targetHeight: BlockHeight @@ -229,72 +230,85 @@ class CompactBlockBatchDownloadOperation: ZcashOperation { return } self.startedHandler?() - do { - let localDownloadedHeight = try self.storage.latestHeight() - - if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { - LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") - startHeight = localDownloadedHeight + 1 - } - - var currentHeight = startHeight - self.progressDelegate?.progressUpdated( - .download( - BlockProgress( - startHeight: currentHeight, - targetHeight: targetHeight, - progressHeight: currentHeight - ) - ) - ) - - while !isCancelled && currentHeight <= targetHeight { - var retries = 0 - var success = true - var localError: Error? - - let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight) + + cancelableTask = Task { + do { + let localDownloadedHeight = try await self.storage.latestHeightAsync() - repeat { - do { - let blocks = try service.blockRange(range) - try storage.insert(blocks) - success = true - } catch { - success = false - localError = error - retries += 1 - } - } while !isCancelled && !success && retries < maxRetries - - if retries >= maxRetries { - throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError) + if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { + LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") + startHeight = localDownloadedHeight + 1 } + var currentHeight = startHeight self.progressDelegate?.progressUpdated( .download( BlockProgress( - startHeight: startHeight, + startHeight: currentHeight, targetHeight: targetHeight, - progressHeight: range.upperBound + progressHeight: currentHeight ) ) ) + + while !isCancelled && currentHeight <= targetHeight { + var retries = 0 + var success = true + var localError: Error? + + let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight) + + repeat { + do { + let stream: AsyncThrowingStream = service.blockRange(range) - currentHeight = range.upperBound + 1 + var blocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + blocks.append(compactBlock) + } + try storage.insert(blocks) + success = true + } catch { + success = false + localError = error + retries += 1 + } + } while !isCancelled && !success && retries < maxRetries + + if retries >= maxRetries { + throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError) + } + + self.progressDelegate?.progressUpdated( + .download( + BlockProgress( + startHeight: startHeight, + targetHeight: targetHeight, + progressHeight: range.upperBound + ) + ) + ) + + currentHeight = range.upperBound + 1 + } + self.done = true + } catch { + self.fail(error: error) } - } catch { - self.fail(error: error) + } + + while !done && !isCancelled { + sleep(1) } } override func fail(error: Error? = nil) { - self.cancelable?.cancel() + self.cancelableTask?.cancel() super.fail(error: error) } override func cancel() { - self.cancelable?.cancel() + self.cancelableTask?.cancel() super.cancel() } diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift index 1ba32318..5afc17d3 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift @@ -125,6 +125,8 @@ class CompactBlockBatchScanningOperation: ZcashOperation { private var blockRange: CompactBlockRange private var transactionRepository: TransactionRepository private var network: NetworkType + private var cancelableTask: Task? + private var done = false private weak var progressDelegate: CompactBlockProgressDelegate? @@ -157,82 +159,94 @@ class CompactBlockBatchScanningOperation: ZcashOperation { self.startedHandler?() - do { - if batchSize == 0 { - let scanStartTime = Date() - guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else { - self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) - return - } - let scanFinishTime = Date() - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: BlockProgress( - startHeight: self.blockRange.lowerBound, - targetHeight: self.blockRange.upperBound, - progressHeight: self.blockRange.upperBound - ), - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) - ) - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds") - } else { - let scanStartHeight = try transactionRepository.lastScannedHeight() - let targetScanHeight = blockRange.upperBound - - var scannedNewBlocks = false - var lastScannedHeight = scanStartHeight - - repeat { - guard !shouldCancel() else { - cancel() - return - } - let previousScannedHeight = lastScannedHeight + cancelableTask = Task { + do { + if batchSize == 0 { let scanStartTime = Date() - guard self.rustBackend.scanBlocks( - dbCache: self.cacheDb, - dbData: self.dataDb, - limit: batchSize, - networkType: network - ) else { + guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else { self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) return } let scanFinishTime = Date() - - lastScannedHeight = try transactionRepository.lastScannedHeight() - - scannedNewBlocks = previousScannedHeight != lastScannedHeight - if scannedNewBlocks { - let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) - progressDelegate?.progressUpdated(.scan(progress)) - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: progress, - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: BlockProgress( + startHeight: self.blockRange.lowerBound, + targetHeight: self.blockRange.upperBound, + progressHeight: self.blockRange.upperBound + ), + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks ) - - let heightCount = lastScannedHeight - previousScannedHeight - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") - } - } while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight + ) + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds") + } else { + let scanStartHeight = try transactionRepository.lastScannedHeight() + let targetScanHeight = blockRange.upperBound + + var scannedNewBlocks = false + var lastScannedHeight = scanStartHeight + + repeat { + guard !shouldCancel() else { + cancel() + return + } + let previousScannedHeight = lastScannedHeight + let scanStartTime = Date() + guard self.rustBackend.scanBlocks( + dbCache: self.cacheDb, + dbData: self.dataDb, + limit: batchSize, + networkType: network + ) else { + self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) + return + } + let scanFinishTime = Date() + + lastScannedHeight = try transactionRepository.lastScannedHeight() + + scannedNewBlocks = previousScannedHeight != lastScannedHeight + if scannedNewBlocks { + let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) + progressDelegate?.progressUpdated(.scan(progress)) + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: progress, + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks + ) + ) + + let heightCount = lastScannedHeight - previousScannedHeight + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") + } + } while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight + self.done = true + } + } catch { + scanFailed(error) } - } catch { - scanFailed(error) + } + + while !done && !isCancelled { + sleep(1) } } func scanFailed(_ error: Error) { - self.error = error + self.cancelableTask?.cancel() LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail() + super.fail(error: error) + } + + override func cancel() { + self.cancelableTask?.cancel() + super.cancel() } } From 4a2fcde3539aef691d56886310d60b926497c72d Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 12 Sep 2022 13:27:52 +0200 Subject: [PATCH 11/15] [483] OutboundTransactionManager To Async/Await - protocol methods updated from closure to async APIs - PersistentTransactionManager updated to implement async APIs - PersistentTransactionManager deleted closure APIs - SDKSynchronizer updated to use async APIs --- .../Synchronizer/SDKSynchronizer.swift | 57 ++-- .../PersistentTransactionManager.swift | 250 ++++++++---------- .../Transaction/TransactionManager.swift | 12 +- 3 files changed, 133 insertions(+), 186 deletions(-) diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 66c3e1f8..d22df368 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -527,27 +527,18 @@ public class SDKSynchronizer: Synchronizer { let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: zAddr, memo: memo, from: 0) - transactionManager.encodeShieldingTransaction( - spendingKey: spendingKey, - tsk: transparentSecretKey, - pendingTransaction: shieldingSpend - ) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let transaction): - self.transactionManager.submit(pendingTransaction: transaction) { submitResult in - switch submitResult { - case .success(let submittedTx): - resultBlock(.success(submittedTx)) - case .failure(let submissionError): - DispatchQueue.main.async { - resultBlock(.failure(submissionError)) - } - } - } + // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 + Task { + do { + let transaction = try await transactionManager.encodeShieldingTransaction( + spendingKey: spendingKey, + tsk: transparentSecretKey, + pendingTransaction: shieldingSpend + ) - case .failure(let error): + let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) + resultBlock(.success(submittedTx)) + } catch { resultBlock(.failure(error)) } } @@ -574,22 +565,16 @@ public class SDKSynchronizer: Synchronizer { from: accountIndex ) - transactionManager.encode(spendingKey: spendingKey, pendingTransaction: spend) { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let transaction): - self.transactionManager.submit(pendingTransaction: transaction) { submitResult in - switch submitResult { - case .success(let submittedTx): - resultBlock(.success(submittedTx)) - case .failure(let submissionError): - DispatchQueue.main.async { - resultBlock(.failure(submissionError)) - } - } - } - - case .failure(let error): + // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 + Task { + do { + let transaction = try await transactionManager.encode( + spendingKey: spendingKey, + pendingTransaction: spend + ) + let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) + resultBlock(.success(submittedTx)) + } catch { resultBlock(.failure(error)) } } diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index c93db928..b1e5fdab 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -67,167 +67,129 @@ class PersistentTransactionManager: OutboundTransactionManager { func encodeShieldingTransaction( spendingKey: String, tsk: String, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { - queue.async { [weak self] in - guard let self = self else { return } - - let derivationTool = DerivationTool(networkType: self.network) - - guard - let viewingKey = try? derivationTool.deriveViewingKey(spendingKey: spendingKey), - let zAddr = try? derivationTool.deriveShieldedAddress(viewingKey: viewingKey) - else { - result( - .failure( - TransactionManagerError.shieldingEncodingFailed( - pendingTransaction, - reason: "There was an error Deriving your keys" - ) - ) - ) - return - } + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { + let derivationTool = DerivationTool(networkType: self.network) + + guard + let viewingKey = try? derivationTool.deriveViewingKey(spendingKey: spendingKey), + let zAddr = try? derivationTool.deriveShieldedAddress(viewingKey: viewingKey) + else { + throw TransactionManagerError.shieldingEncodingFailed( + pendingTransaction, + reason: "There was an error Deriving your keys" + ) + } + + guard pendingTransaction.toAddress == zAddr else { + throw TransactionManagerError.shieldingEncodingFailed( + pendingTransaction, + reason: """ + the recipient address does not match your + derived shielded address. Shielding transactions + addresses must match the ones derived from your keys. + This is a serious error. We are not letting you encode + this shielding transaction because it can lead to loss + of funds + """ + ) + } + + do { + let encodedTransaction = try self.encoder.createShieldingTransaction( + spendingKey: spendingKey, + tSecretKey: tsk, + memo: pendingTransaction.memo?.asZcashTransactionMemo(), + from: pendingTransaction.accountIndex + ) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) - guard pendingTransaction.toAddress == zAddr else { - result( - .failure( - TransactionManagerError.shieldingEncodingFailed( - pendingTransaction, - reason: """ - the recipient address does not match your - derived shielded address. Shielding transactions - addresses must match the ones derived from your keys. - This is a serious error. We are not letting you encode - this shielding transaction because it can lead to loss - of funds - """ - ) - ) - ) - return - } - do { - let encodedTransaction = try self.encoder.createShieldingTransaction( - spendingKey: spendingKey, - tSecretKey: tsk, - memo: pendingTransaction.memo?.asZcashTransactionMemo(), - from: pendingTransaction.accountIndex - ) - let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = encodedTransaction.raw - pending.rawTransactionId = encodedTransaction.transactionId - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - result(.success(pending)) - } catch StorageError.updateFailed { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } - } catch { - DispatchQueue.main.async { - result(.failure(error)) - } - } + var pending = pendingTransaction + pending.encodeAttempts += 1 + pending.raw = encodedTransaction.raw + pending.rawTransactionId = encodedTransaction.transactionId + pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() + pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() + + try self.repository.update(pending) + + return pending + } catch StorageError.updateFailed { + throw TransactionManagerError.updateFailed(pendingTransaction) + } catch { + throw error } } func encode( spendingKey: String, - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { - queue.async { [weak self] in - guard let self = self else { return } + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { + do { + let encodedTransaction = try self.encoder.createTransaction( + spendingKey: spendingKey, + zatoshi: pendingTransaction.intValue, + to: pendingTransaction.toAddress, + memo: pendingTransaction.memo?.asZcashTransactionMemo(), + from: pendingTransaction.accountIndex + ) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) + + var pending = pendingTransaction + pending.encodeAttempts += 1 + pending.raw = encodedTransaction.raw + pending.rawTransactionId = encodedTransaction.transactionId + pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() + pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() + + try self.repository.update(pending) + + return pending + } catch StorageError.updateFailed { + throw TransactionManagerError.updateFailed(pendingTransaction) + } catch { do { - let encodedTransaction = try self.encoder.createTransaction( - spendingKey: spendingKey, - zatoshi: pendingTransaction.intValue, - to: pendingTransaction.toAddress, - memo: pendingTransaction.memo?.asZcashTransactionMemo(), - from: pendingTransaction.accountIndex - ) - let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) - - var pending = pendingTransaction - pending.encodeAttempts += 1 - pending.raw = encodedTransaction.raw - pending.rawTransactionId = encodedTransaction.transactionId - pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty() - pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty() - - try self.repository.update(pending) - - result(.success(pending)) - } catch StorageError.updateFailed { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } + try self.updateOnFailure(transaction: pendingTransaction, error: error) } catch { - do { - try self.updateOnFailure(transaction: pendingTransaction, error: error) - } catch { - DispatchQueue.main.async { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) - } - } - DispatchQueue.main.async { - result(.failure(error)) - } + throw TransactionManagerError.updateFailed(pendingTransaction) } + throw error } } func submit( - pendingTransaction: PendingTransactionEntity, - result: @escaping (Result) -> Void - ) { + pendingTransaction: PendingTransactionEntity + ) async throws -> PendingTransactionEntity { guard let txId = pendingTransaction.id else { - result(.failure(TransactionManagerError.notPending(pendingTransaction)))// this transaction is not stored - return + throw TransactionManagerError.notPending(pendingTransaction) // this transaction is not stored } - queue.async { [weak self] in - guard let self = self else { return } - - do { - guard let storedTx = try self.repository.find(by: txId) else { - result(.failure(TransactionManagerError.notPending(pendingTransaction))) - return - } - - guard !storedTx.isCancelled else { - LoggerProxy.debug("ignoring cancelled transaction \(storedTx)") - result(.failure(TransactionManagerError.cancelled(storedTx))) - return - } - - guard let raw = storedTx.raw else { - LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data") - result(.failure(TransactionManagerError.internalInconsistency(storedTx))) - return - } - - let response = try self.service.submit(spendTransaction: raw) - let transaction = try self.update(transaction: storedTx, on: response) - - guard response.errorCode >= 0 else { - result(.failure(TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode)))) - return - } - - result(.success(transaction)) - } catch { - try? self.updateOnFailure(transaction: pendingTransaction, error: error) - result(.failure(error)) + do { + guard let storedTx = try self.repository.find(by: txId) else { + throw TransactionManagerError.notPending(pendingTransaction) } + + guard !storedTx.isCancelled else { + LoggerProxy.debug("ignoring cancelled transaction \(storedTx)") + throw TransactionManagerError.cancelled(storedTx) + } + + guard let raw = storedTx.raw else { + LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data") + throw TransactionManagerError.internalInconsistency(storedTx) + } + + let response = try self.service.submit(spendTransaction: raw) + let transaction = try self.update(transaction: storedTx, on: response) + + guard response.errorCode >= 0 else { + throw TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode)) + } + + return transaction + } catch { + try? self.updateOnFailure(transaction: pendingTransaction, error: error) + throw error } } diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift index c2273879..956a7f46 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift @@ -16,12 +16,12 @@ transactions through to completion. protocol OutboundTransactionManager { func initSpend(zatoshi: Zatoshi, toAddress: String, memo: String?, from accountIndex: Int) throws -> PendingTransactionEntity - func encodeShieldingTransaction(spendingKey: String, tsk: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result) -> Void) - - func encode(spendingKey: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result) -> Void) - - func submit(pendingTransaction: PendingTransactionEntity, result: @escaping (Result) -> Void) - + func encodeShieldingTransaction(spendingKey: String, tsk: String, pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity + + func encode(spendingKey: String, pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity + + func submit(pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity + func applyMinedHeight(pendingTransaction: PendingTransactionEntity, minedHeight: BlockHeight) throws -> PendingTransactionEntity func monitorChanges(byId: Int, observer: Any) // check this out. code smell From a05990b4174b470070072ea48231b8d20feda1e2 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Mon, 12 Sep 2022 13:18:40 -0300 Subject: [PATCH 12/15] [#525] Release 0.16.9-beta --- .../Resources/checkpoints/mainnet/1787500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1790000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1792500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1795000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1797500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1800000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1802500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1805000.json | 8 ++++++++ ZcashLightClientKit.podspec | 2 +- changelog.md | 13 +++++++++++++ 10 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json new file mode 100644 index 00000000..be24e89d --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1787500", + "hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa", + "time": 1661588407, + "saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json new file mode 100644 index 00000000..ab42998e --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1790000", + "hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4", + "time": 1661777577, + "saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json new file mode 100644 index 00000000..892f919b --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1792500", + "hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48", + "time": 1661965969, + "saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json new file mode 100644 index 00000000..6f9f8be5 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1795000", + "hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1", + "time": 1662152700, + "saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json new file mode 100644 index 00000000..c9b787c3 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1797500", + "hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0", + "time": 1662341718, + "saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json new file mode 100644 index 00000000..8a0cab04 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1800000", + "hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961", + "time": 1662529994, + "saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json new file mode 100644 index 00000000..07a7e6fa --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1802500", + "hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91", + "time": 1662718546, + "saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json new file mode 100644 index 00000000..7a068b5e --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1805000", + "hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb", + "time": 1662907506, + "saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 907fea8d..9f9c6728 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.16.8-beta' + s.version = '0.16.9-beta' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC diff --git a/changelog.md b/changelog.md index 48b2a04a..935b6aec 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,16 @@ +# 0.16.9-beta +Checkpoints added: +Mainnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json +```` # 0.16.8-beta Checkpoints added: Mainnet From 1a73985307bd4129969d03bfe06194d2cf576dc8 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Mon, 12 Sep 2022 14:36:30 -0300 Subject: [PATCH 13/15] Fix Merge Mistakes --- .../Synchronizer/SDKSynchronizer.swift | 1 + .../PersistentTransactionManager.swift | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index de177241..aebd6ab3 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -555,6 +555,7 @@ public class SDKSynchronizer: Synchronizer { Task { do { let transaction = try await transactionManager.encode( + spendingKey: spendingKey, pendingTransaction: spend ) let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index 021bb23a..dc51726d 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -19,7 +19,6 @@ enum TransactionManagerError: Error { } class PersistentTransactionManager: OutboundTransactionManager { - var repository: PendingTransactionRepository var encoder: TransactionEncoder var service: LightWalletService @@ -88,26 +87,26 @@ class PersistentTransactionManager: OutboundTransactionManager { return pending } catch StorageError.updateFailed { - result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) + throw TransactionManagerError.updateFailed(pendingTransaction) } catch MemoBytes.Errors.invalidUTF8 { - result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo contains invalid UTF-8 bytes"))) + throw TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo contains invalid UTF-8 bytes") } catch MemoBytes.Errors.tooLong(let length) { - result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo is too long. expected 512 bytes, received \(length)"))) + throw TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo is too long. expected 512 bytes, received \(length)") } catch { - result(.failure(error)) + throw error } } func encode( - spendingKey: String, + spendingKey: SaplingExtendedSpendingKey, pendingTransaction: PendingTransactionEntity ) async throws -> PendingTransactionEntity { do { let encodedTransaction = try self.encoder.createTransaction( spendingKey: spendingKey, - zatoshi: pendingTransaction.intValue, + zatoshi: pendingTransaction.value, to: pendingTransaction.toAddress, - memo: pendingTransaction.memo?.asZcashTransactionMemo(), + memoBytes: try pendingTransaction.memo.intoMemoBytes(), from: pendingTransaction.accountIndex ) let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) From 2b65bd46e467cd13ad0cfbcfcdd432efba9726f7 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 1 Sep 2022 14:58:41 +0200 Subject: [PATCH 14/15] [476] CompactBlockProcessor to async/await - getting rid of the Operation Queue - the cleanup is needed - the update of tests is needed - tested and it successfully finishes the sync process [476] CompactBlockProcessor to async/await - old processNewBlocks() removed [476] CompactBlockProcessor to async/await - unused operations removed [476] CompactBlockProcessor to async/await - unit tests update [476] CompactBlockProcessor to async/await - unit tests refactored [476] CompactBlockProcessor to async/await - cleanup of deprecated method [476] CompactBlockProcessor to async/await - fail(error) was called even for canceled tasks but that must be excluded [476] CompactBlockProcessor to async/await - removal of all ZcashOperations from the code (unit test will follow) [476] CompactBlockProcessor to async/await - network tests in building and success order again [476] CompactBlockProcessor to async/await - offline tests in building and success order [476] CompactBlockProcessor to async/await (519) - cleanup of suspending the task [476] CompactBlockProcessor to async/await (519) - most comments resolved [476] CompactBlockProcessor to async/await (519) - thread safe state for both sync and async context [476] CompactBlockProcessor to async/await (519) - fixed build for a sample project [476] CompactBlockProcessor to async/await (519) - func testStartNotifiesSuscriptors() reverted [476] CompactBlockProcessor to async/await (519) - TODO added to track why we used NSLock instead of an Actor - Task priority enhanced [476] CompactBlockProcessor to async/await (519) - cleanup in Tasks and priorities --- .../SyncBlocksViewController.swift | 8 +- .../Processor/CompactBlockDownload.swift | 160 ++++++ .../CompactBlockDownloadOperation.swift | 318 ------------ .../Processor/CompactBlockEnhancement.swift | 119 +++++ .../CompactBlockEnhancementOperation.swift | 175 ------- .../Processor/CompactBlockProcessor.swift | 476 ++++-------------- .../Processor/CompactBlockScanning.swift | 183 +++++++ .../CompactBlockScanningOperation.swift | 252 ---------- .../CompactBlockValidationInformation.swift | 98 ++-- .../Processor/FetchUnspentTxOutputs.swift | 82 +++ .../FetchUnspentTxOutputsOperation.swift | 126 ----- .../Block/Processor/FigureNextBatch.swift | 34 ++ .../Processor/FigureNextBatchOperation.swift | 58 --- .../Block/Processor/ZcashOperation.swift | 68 --- .../Service/LightWalletGRPCService.swift | 6 +- .../ZcashLightClientKit/Synchronizer.swift | 2 +- .../Synchronizer/SDKSynchronizer.swift | 7 +- .../BlockScanOperationTests.swift | 265 ---------- Tests/NetworkTests/BlockScanTests.swift | 195 +++++++ Tests/NetworkTests/BlockStreamingTest.swift | 172 +++---- ...erationTests.swift => DownloadTests.swift} | 38 +- .../BlockBatchValidationTests.swift | 291 +++++------ 22 files changed, 1138 insertions(+), 1995 deletions(-) create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift delete mode 100644 Tests/NetworkTests/BlockScanOperationTests.swift create mode 100644 Tests/NetworkTests/BlockScanTests.swift rename Tests/NetworkTests/{DownloadOperationTests.swift => DownloadTests.swift} (54%) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift index f3c039dd..2e7760c2 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift @@ -31,7 +31,7 @@ class SyncBlocksViewController: UIViewController { // swiftlint:disable:next force_try try! wallet.initialize() processor = CompactBlockProcessor(initializer: wallet) - statusLabel.text = textFor(state: processor?.state ?? .stopped) + statusLabel.text = textFor(state: processor?.state.getState() ?? .stopped) progressBar.progress = 0 NotificationCenter.default.addObserver( @@ -70,7 +70,7 @@ class SyncBlocksViewController: UIViewController { @IBAction func startStop() { guard let processor = processor else { return } - switch processor.state { + switch processor.state.getState() { case .stopped: startProcessor() default: @@ -92,7 +92,7 @@ class SyncBlocksViewController: UIViewController { func stopProcessor() { guard let processor = processor else { return } - processor.stop(cancelTasks: true) + processor.stop() updateUI() } @@ -114,7 +114,7 @@ class SyncBlocksViewController: UIViewController { } func updateUI() { - guard let state = processor?.state else { return } + guard let state = processor?.state.getState() else { return } statusLabel.text = textFor(state: state) startPause.setTitle(buttonText(for: state), for: .normal) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift new file mode 100644 index 00000000..58eefb02 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift @@ -0,0 +1,160 @@ +// +// CompactBlockDownload.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 10/16/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import Foundation + +extension CompactBlockProcessor { + func compactBlockStreamDownload( + blockBufferSize: Int, + startHeight: BlockHeight? = nil, + targetHeight: BlockHeight? = nil + ) async throws { + try Task.checkCancellation() + + setState(.downloading) + + var buffer: [ZcashCompactBlock] = [] + var targetHeightInternal: BlockHeight? + + do { + targetHeightInternal = targetHeight + if targetHeight == nil { + targetHeightInternal = try await service.latestBlockHeightAsync() + } + guard let latestHeight = targetHeightInternal else { + throw LightWalletServiceError.generalError(message: "missing target height on compactBlockStreamDownload") + } + let latestDownloaded = try await storage.latestHeightAsync() + let startHeight = max(startHeight ?? BlockHeight.empty(), latestDownloaded) + + let stream = service.blockStream( + startHeight: startHeight, + endHeight: latestHeight + ) + + for try await zcashCompactBlock in stream { + buffer.append(zcashCompactBlock) + if buffer.count >= blockBufferSize { + // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact + try storage.write(blocks: buffer) + buffer.removeAll(keepingCapacity: true) + } + + let progress = BlockProgress( + startHeight: startHeight, + targetHeight: latestHeight, + progressHeight: zcashCompactBlock.height + ) + notifyProgress(.download(progress)) + } + // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact + try storage.write(blocks: buffer) + buffer.removeAll(keepingCapacity: true) + } catch { + guard let err = error as? LightWalletServiceError, case .userCancelled = err else { + throw error + } + } + } +} + +extension CompactBlockProcessor { + func compactBlockDownload( + downloader: CompactBlockDownloading, + range: CompactBlockRange + ) async throws { + try Task.checkCancellation() + + do { + try await downloader.downloadBlockRangeAsync(range) + } catch { + throw error + } + } +} + +extension CompactBlockProcessor { + enum CompactBlockBatchDownloadError: Error { + case startHeightMissing + case batchDownloadFailed(range: CompactBlockRange, error: Error?) + } + + func compactBlockBatchDownload( + range: CompactBlockRange, + batchSize: Int = 100, + maxRetries: Int = 5 + ) async throws { + try Task.checkCancellation() + + var startHeight = range.lowerBound + let targetHeight = range.upperBound + + do { + let localDownloadedHeight = try await self.storage.latestHeightAsync() + + if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { + LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") + startHeight = localDownloadedHeight + 1 + } + + var currentHeight = startHeight + notifyProgress( + .download( + BlockProgress( + startHeight: currentHeight, + targetHeight: targetHeight, + progressHeight: currentHeight + ) + ) + ) + + while !Task.isCancelled && currentHeight <= targetHeight { + var retries = 0 + var success = true + var localError: Error? + + let range = CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batchSize, targetHeight))) + + repeat { + do { + let stream: AsyncThrowingStream = service.blockRange(range) + + var blocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + blocks.append(compactBlock) + } + try storage.insert(blocks) + success = true + } catch { + success = false + localError = error + retries += 1 + } + } while !Task.isCancelled && !success && retries < maxRetries + + if retries >= maxRetries { + throw CompactBlockBatchDownloadError.batchDownloadFailed(range: range, error: localError) + } + + notifyProgress( + .download( + BlockProgress( + startHeight: startHeight, + targetHeight: targetHeight, + progressHeight: range.upperBound + ) + ) + ) + + currentHeight = range.upperBound + 1 + } + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift deleted file mode 100644 index 61d1734c..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ /dev/null @@ -1,318 +0,0 @@ -// -// CompactBlockDownloadOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/16/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -class CompactBlockDownloadOperation: ZcashOperation { - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var downloader: CompactBlockDownloading - private var range: CompactBlockRange - private var cancelableTask: Task? - private var done = false - - required init(downloader: CompactBlockDownloading, range: CompactBlockRange) { - self.range = range - self.downloader = downloader - super.init() - self.name = "Download Operation: \(range)" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - try await downloader.downloadBlockRangeAsync(range) - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} - -protocol CompactBlockProgressDelegate: AnyObject { - func progressUpdated(_ progress: CompactBlockProgress) -} - -class CompactBlockStreamDownloadOperation: ZcashOperation { - enum CompactBlockStreamDownloadOperationError: Error { - case startHeightMissing - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var storage: CompactBlockStorage - private var service: LightWalletService - private var done = false - private var cancelableTask: Task? - private var startHeight: BlockHeight? - private var targetHeight: BlockHeight? - private var blockBufferSize: Int - private var buffer: [ZcashCompactBlock] = [] - - private weak var progressDelegate: CompactBlockProgressDelegate? - - /// Creates an Compact Block Stream Download Operation Operation - /// - Parameters: - /// - service: instance that conforms to `LightWalletService` - /// - storage: instance that conforms to `CompactBlockStorage` - /// - blockBufferSize: the number of blocks that the stream downloader will store in memory - /// before writing them to disk. Making this number smaller makes the downloader easier on RAM - /// memory while being less efficient on disk writes. Making it bigger takes up more RAM memory - /// but is less straining on Disk Writes. Too little or too big buffer will make this less efficient. - /// - startHeight: the height this downloader will start downloading from. If `nil`, - /// it will start from the latest height found on the local cacheDb - /// - targetHeight: the upper bound for this stream download. If `nil`, the - /// streamer will call `service.latestBlockHeight()` - /// - progressDelegate: Optional delegate to report ongoing progress conforming to - /// `CompactBlockProgressDelegate` - /// - required init( - service: LightWalletService, - storage: CompactBlockStorage, - blockBufferSize: Int, - startHeight: BlockHeight? = nil, - targetHeight: BlockHeight? = nil, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - self.storage = storage - self.service = service - self.startHeight = startHeight - self.targetHeight = targetHeight - self.progressDelegate = progressDelegate - self.blockBufferSize = blockBufferSize - super.init() - self.name = "Download Stream Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - if self.targetHeight == nil { - self.targetHeight = try await service.latestBlockHeightAsync() - } - guard let latestHeight = self.targetHeight else { - throw LightWalletServiceError.generalError(message: "missing target height on block stream operation") - } - let latestDownloaded = try await storage.latestHeightAsync() - let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded) - - let stream = service.blockStream( - startHeight: startHeight, - endHeight: latestHeight - ) - - for try await zcashCompactBlock in stream { - try self.cache(zcashCompactBlock, flushCache: false) - let progress = BlockProgress( - startHeight: startHeight, - targetHeight: latestHeight, - progressHeight: zcashCompactBlock.height - ) - self.progressDelegate?.progressUpdated(.download(progress)) - } - try self.flush() - self.done = true - } catch { - if let err = error as? LightWalletServiceError, case .userCancelled = err { - self.done = true - } else { - self.fail(error: error) - } - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } - - func cache(_ block: ZcashCompactBlock, flushCache: Bool) throws { - self.buffer.append(block) - - if flushCache || buffer.count >= blockBufferSize { - try flush() - } - } - - func flush() throws { - try self.storage.write(blocks: self.buffer) - self.buffer.removeAll(keepingCapacity: true) - } -} - -class CompactBlockBatchDownloadOperation: ZcashOperation { - enum CompactBlockBatchDownloadOperationError: Error { - case startHeightMissing - case batchDownloadFailed(range: CompactBlockRange, error: Error?) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var batch: Int - private var done = false - private var maxRetries: Int - private var storage: CompactBlockStorage - private var service: LightWalletService - private var cancelableTask: Task? - private var startHeight: BlockHeight - private var targetHeight: BlockHeight - - private weak var progressDelegate: CompactBlockProgressDelegate? - - required init( - service: LightWalletService, - storage: CompactBlockStorage, - startHeight: BlockHeight, - targetHeight: BlockHeight, - batchSize: Int = 100, - maxRetries: Int = 5, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - self.storage = storage - self.service = service - self.startHeight = startHeight - self.targetHeight = targetHeight - self.progressDelegate = progressDelegate - self.batch = batchSize - self.maxRetries = maxRetries - super.init() - self.name = "Download Batch Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - let localDownloadedHeight = try await self.storage.latestHeightAsync() - - if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { - LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") - startHeight = localDownloadedHeight + 1 - } - - var currentHeight = startHeight - self.progressDelegate?.progressUpdated( - .download( - BlockProgress( - startHeight: currentHeight, - targetHeight: targetHeight, - progressHeight: currentHeight - ) - ) - ) - - while !isCancelled && currentHeight <= targetHeight { - var retries = 0 - var success = true - var localError: Error? - - let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight) - - repeat { - do { - let stream: AsyncThrowingStream = service.blockRange(range) - - var blocks: [ZcashCompactBlock] = [] - for try await compactBlock in stream { - blocks.append(compactBlock) - } - try storage.insert(blocks) - success = true - } catch { - success = false - localError = error - retries += 1 - } - } while !isCancelled && !success && retries < maxRetries - - if retries >= maxRetries { - throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError) - } - - self.progressDelegate?.progressUpdated( - .download( - BlockProgress( - startHeight: startHeight, - targetHeight: targetHeight, - progressHeight: range.upperBound - ) - ) - ) - - currentHeight = range.upperBound + 1 - } - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } - - func nextRange(currentHeight: BlockHeight, targetHeight: BlockHeight) -> CompactBlockRange { - CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batch, targetHeight))) - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift new file mode 100644 index 00000000..29b921ea --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift @@ -0,0 +1,119 @@ +// +// CompactBlockEnhancement.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 4/10/20. +// + +import Foundation + +extension CompactBlockProcessor { + enum EnhancementError: Error { + case noRawData(message: String) + case unknownError + case decryptError(error: Error) + case txIdNotFound(txId: Data) + } + + private func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { + LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") + + let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) + + let transactionID = transaction.transactionId.toHexStringTxId() + let block = String(describing: transaction.minedHeight) + LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)") + + guard let rawBytes = transaction.raw?.bytes else { + let error = EnhancementError.noRawData( + message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data" + ) + LoggerProxy.error("\(error)") + throw error + } + + guard let minedHeight = transaction.minedHeight else { + let error = EnhancementError.noRawData( + message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())" + ) + LoggerProxy.error("\(error)") + throw error + } + + guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else { + if let rustError = rustBackend.lastError() { + throw EnhancementError.decryptError(error: rustError) + } + throw EnhancementError.unknownError + } + guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else { + throw EnhancementError.txIdNotFound(txId: transaction.transactionId) + } + return confirmedTx + } + + func compactBlockEnhancement(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + LoggerProxy.debug("Started Enhancing range: \(range)") + setState(.enhancing) + + let blockRange = range.blockRange() + var retries = 0 + let maxRetries = 5 + + // fetch transactions + do { + guard let transactions = try transactionRepository.findTransactions(in: blockRange, limit: Int.max), !transactions.isEmpty else { + LoggerProxy.debug("no transactions detected on range: \(blockRange.printRange)") + return + } + + for index in 0 ..< transactions.count { + let transaction = transactions[index] + var retry = true + + while retry && retries < maxRetries { + try Task.checkCancellation() + do { + let confirmedTx = try await enhance(transaction: transaction) + retry = false + notifyProgress( + .enhance( + EnhancementStreamProgress( + totalTransactions: transactions.count, + enhancedTransactions: index + 1, + lastFoundTransaction: confirmedTx, + range: range + ) + ) + ) + } catch { + retries += 1 + LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") + if retries > maxRetries { + throw error + } + } + } + } + } catch { + LoggerProxy.error("error enhancing transactions! \(error)") + throw error + } + + if let foundTxs = try? transactionRepository.findConfirmedTransactions(in: blockRange, offset: 0, limit: Int.max) { + notifyTransactions(foundTxs, in: blockRange) + } + + if Task.isCancelled { + LoggerProxy.debug("Warning: compactBlockEnhancement on range \(range) cancelled") + } + } +} + +private extension BlockRange { + var printRange: String { + "\(self.start.height) ... \(self.end.height)" + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift deleted file mode 100644 index 1d19fca7..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// CompactBlockEnhancementOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 4/10/20. -// - -import Foundation - -class CompactBlockEnhancementOperation: ZcashOperation { - enum EnhancementError: Error { - case noRawData(message: String) - case unknownError - case decryptError(error: Error) - case txIdNotFound(txId: Data) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - var txFoundHandler: (([ConfirmedTransactionEntity], BlockRange) -> Void)? - var downloader: CompactBlockDownloading - var repository: TransactionRepository - var range: BlockRange - var maxRetries: Int = 5 - var retries: Int = 0 - - private(set) var network: NetworkType - private weak var progressDelegate: CompactBlockProgressDelegate? - private var dataDb: URL - private var cancelableTask: Task? - private var done = false - - init( - rustWelding: ZcashRustBackendWelding.Type, - dataDb: URL, - downloader: CompactBlockDownloading, - repository: TransactionRepository, - range: BlockRange, - networkType: NetworkType, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - rustBackend = rustWelding - self.dataDb = dataDb - self.downloader = downloader - self.repository = repository - self.range = range - self.progressDelegate = progressDelegate - self.network = networkType - - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - // fetch transactions - do { - guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else { - LoggerProxy.debug("no transactions detected on range: \(range.printRange)") - return - } - - for index in 0 ..< transactions.count { - let transaction = transactions[index] - var retry = true - - while retry && self.retries < maxRetries { - do { - let confirmedTx = try await enhance(transaction: transaction) - retry = false - self.reportProgress( - totalTransactions: transactions.count, - enhanced: index + 1, - txEnhanced: confirmedTx - ) - } catch { - self.retries += 1 - LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") - if retries > maxRetries { - throw error - } - } - } - } - } catch { - LoggerProxy.error("error enhancing transactions! \(error)") - self.fail(error: error) - return - } - - if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) { - handler(foundTxs, self.range) - } - self.done = true - } - - while !done && !isCancelled { - sleep(1) - } - } - - func reportProgress(totalTransactions: Int, enhanced: Int, txEnhanced: ConfirmedTransactionEntity) { - self.progressDelegate?.progressUpdated( - .enhance( - EnhancementStreamProgress( - totalTransactions: totalTransactions, - enhancedTransactions: enhanced, - lastFoundTransaction: txEnhanced, - range: self.range.compactBlockRange - ) - ) - ) - } - - func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { - LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") - - let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) - - let transactionID = transaction.transactionId.toHexStringTxId() - let block = String(describing: transaction.minedHeight) - LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)") - - guard let rawBytes = transaction.raw?.bytes else { - let error = EnhancementError.noRawData( - message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data" - ) - LoggerProxy.error("\(error)") - throw error - } - - guard let minedHeight = transaction.minedHeight else { - let error = EnhancementError.noRawData( - message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())" - ) - LoggerProxy.error("\(error)") - throw error - } - - guard rustBackend.decryptAndStoreTransaction(dbData: dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: network) else { - if let rustError = rustBackend.lastError() { - throw EnhancementError.decryptError(error: rustError) - } - throw EnhancementError.unknownError - } - guard let confirmedTx = try self.repository.findConfirmedTransactionBy(rawId: transaction.transactionId) else { - throw EnhancementError.txIdNotFound(txId: transaction.transactionId) - } - return confirmedTx - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} - -private extension BlockRange { - var printRange: String { - "\(self.start.height) ... \(self.end.height)" - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index 73990491..d2767c80 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -30,6 +30,7 @@ public enum CompactBlockProcessorError: Error { case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID) case networkMismatch(expected: NetworkType, found: NetworkType) case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight) + case unknown } /** @@ -311,11 +312,30 @@ public class CompactBlockProcessor { case synced } - public private(set) var state: State = .stopped { - didSet { - transitionState(from: oldValue, to: self.state) + // TODO: this isn't an Actor even though it looks like a good candidate, the reason: + // `state` lives in both sync and async environments. An Actor is demanding async context only + // so we can't take the advantage unless we encapsulate all `state` reads/writes to async context. + // Therefore solution with class + lock works for us butr eventually will be replaced. + // The future of CompactBlockProcessor is an actor (we won't need to encapsulate the state separately), issue 523, + // https://github.com/zcash/ZcashLightClientKit/issues/523 + public class ThreadSafeState { + private var state: State = .stopped + let lock = NSLock() + + func setState(_ newState: State) { + lock.lock() + defer { lock.unlock() } + state = newState + } + + public func getState() -> State { + lock.lock() + defer { lock.unlock() } + return state } } + + public internal(set) var state = ThreadSafeState() var config: Configuration { willSet { @@ -328,7 +348,7 @@ public class CompactBlockProcessor { } var shouldStart: Bool { - switch self.state { + switch self.state.getState() { case .stopped, .synced, .error: return !maxAttemptsReached default: @@ -336,19 +356,19 @@ public class CompactBlockProcessor { } } - private var service: LightWalletService + var service: LightWalletService private(set) var downloader: CompactBlockDownloading - private var storage: CompactBlockStorage - private var transactionRepository: TransactionRepository - private var accountRepository: AccountRepository - private var rustBackend: ZcashRustBackendWelding.Type + var storage: CompactBlockStorage + var transactionRepository: TransactionRepository + var accountRepository: AccountRepository + var rustBackend: ZcashRustBackendWelding.Type private var retryAttempts: Int = 0 private var backoffTimer: Timer? private var lowerBoundHeight: BlockHeight? private var latestBlockHeight: BlockHeight private var lastChainValidationFailure: BlockHeight? private var consecutiveChainValidationErrors: Int = 0 - private var processingError: Error? + var processingError: Error? private var foundBlocks = false private var maxAttempts: Int { config.retries @@ -358,13 +378,7 @@ public class CompactBlockProcessor { BlockHeight(self.config.downloadBatchSize) } - private var operationQueue: OperationQueue = { - let queue = OperationQueue() - queue.name = "CompactBlockProcessorQueue" - queue.maxConcurrentOperationCount = 1 - return queue - }() - + private var cancelableTask: Task? /// Initializes a CompactBlockProcessor instance /// - Parameters: @@ -431,7 +445,13 @@ public class CompactBlockProcessor { } deinit { - self.operationQueue.cancelAllOperations() + cancelableTask?.cancel() + } + + func setState(_ newState: State) { + let oldValue = state.getState() + state.setState(newState) + transitionState(from: oldValue, to: newState) } static func validateServerInfo( @@ -487,19 +507,14 @@ public class CompactBlockProcessor { self.backoffTimer?.invalidate() self.backoffTimer = nil } - guard !operationQueue.isSuspended else { - LoggerProxy.debug("restarting suspended queue") - operationQueue.isSuspended = false - return - } guard shouldStart else { - switch self.state { + switch self.state.getState() { case .error(let e): // max attempts have been reached LoggerProxy.info("max retry attempts reached with error: \(e)") notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts)) - self.state = .stopped + setState(.stopped) case .stopped: // max attempts have been reached LoggerProxy.info("max retry attempts reached") @@ -514,7 +529,7 @@ public class CompactBlockProcessor { return } - self.nextBatchTask() + self.nextBatch() } /** @@ -523,18 +538,14 @@ public class CompactBlockProcessor { Note: retry count is reset - Parameter cancelTasks: cancel the pending tasks. Defaults to true */ - public func stop(cancelTasks: Bool = true) { + public func stop() { self.backoffTimer?.invalidate() self.backoffTimer = nil - if cancelTasks { - operationQueue.cancelAllOperations() - } else { - self.operationQueue.isSuspended = true - } + cancelableTask?.cancel() self.retryAttempts = 0 - self.state = .stopped + setState(.stopped) } /** @@ -577,7 +588,7 @@ public class CompactBlockProcessor { - Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started */ func setStartHeight(_ startHeight: BlockHeight) throws { - guard self.state == .stopped, startHeight >= config.network.constants.saplingActivationHeight else { + guard self.state.getState() == .stopped, startHeight >= config.network.constants.saplingActivationHeight else { throw CompactBlockProcessorError.invalidConfiguration } @@ -612,243 +623,34 @@ public class CompactBlockProcessor { }) } - /** - processes new blocks on the given range based on the configuration set for this instance - the way operations are queued is implemented based on the following good practice https://forums.developer.apple.com/thread/25761 - - */ - // swiftlint:disable cyclomatic_complexity + + /// Processes new blocks on the given range based on the configuration set for this instance func processNewBlocks(range: CompactBlockRange) { self.foundBlocks = true self.backoffTimer?.invalidate() self.backoffTimer = nil - let cfg = self.config - let downloadBlockOperation = CompactBlockStreamDownloadOperation( - service: self.service, - storage: self.storage, - blockBufferSize: self.config.downloadBufferSize, - startHeight: range.lowerBound, - targetHeight: range.upperBound, - progressDelegate: self - ) - - downloadBlockOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { - self?.state = .downloading - } - } - - downloadBlockOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { - guard let self = self else { return } - self.processingError = error - self.fail(error) - } - } - - let validateChainOperation = CompactBlockValidationOperation( - rustWelding: self.rustBackend, - cacheDb: cfg.cacheDb, - dataDb: cfg.dataDb, - networkType: self.config.network.networkType - ) - - let downloadValidateAdapterOperation = BlockOperation { [weak validateChainOperation, weak downloadBlockOperation] in - validateChainOperation?.error = downloadBlockOperation?.error - } - - validateChainOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - DispatchQueue.main.async { - self?.state = .stopped - LoggerProxy.debug("Warning: validateChainOperation operation cancelled") - } - return - } - - LoggerProxy.debug("validateChainFinished") - } - - validateChainOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - guard let validationError = error as? CompactBlockValidationError else { - LoggerProxy.error("Warning: validateChain operation returning generic error: \(error)") - return - } - - switch validationError { - case .validationFailed(let height): - LoggerProxy.debug("chain validation at height: \(height)") - self.validationFailed(at: height) - case .failedWithError(let e): - guard let validationFailure = e else { - LoggerProxy.error("validation failed without a specific error") - self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error")) - return - } + cancelableTask = Task(priority: .userInitiated) { + do { + try await compactBlockStreamDownload( + blockBufferSize: config.downloadBufferSize, + startHeight: range.lowerBound, + targetHeight: range.upperBound + ) + try await compactBlockValidation() + try await compactBlockBatchScanning(range: range) + try await compactBlockEnhancement(range: range) + try await fetchUnspentTxOutputs(range: range) + } catch { + if error is CancellationError { - self.fail(validationFailure) + } + + if !(Task.isCancelled) { + fail(error) } } } - - validateChainOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .validating - } - } - - let scanBlocksOperation = CompactBlockBatchScanningOperation( - rustWelding: rustBackend, - cacheDb: config.cacheDb, - dataDb: config.dataDb, - transactionRepository: transactionRepository, - range: range, - batchSize: UInt32(self.config.scanningBatchSize), - networkType: self.config.network.networkType, - progressDelegate: self - ) - - let validateScanningAdapterOperation = BlockOperation { [weak scanBlocksOperation, weak validateChainOperation] in - scanBlocksOperation?.error = validateChainOperation?.error - } - - scanBlocksOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .scanning - } - } - - scanBlocksOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - DispatchQueue.main.async { [weak self] in - self?.state = .stopped - LoggerProxy.debug("Warning: scanBlocksOperation operation cancelled") - } - return - } - } - - scanBlocksOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.processingError = error - self.fail(error) - } - } - - let enhanceOperation = CompactBlockEnhancementOperation( - rustWelding: rustBackend, - dataDb: config.dataDb, - downloader: downloader, - repository: transactionRepository, - range: range.blockRange(), - networkType: self.config.network.networkType - ) - - enhanceOperation.startedHandler = { - LoggerProxy.debug("Started Enhancing range: \(range)") - DispatchQueue.main.async { [weak self] in - self?.state = .enhancing - } - } - - enhanceOperation.txFoundHandler = { [weak self] txs, range in - self?.notifyTransactions(txs, in: range) - } - - enhanceOperation.completionHandler = { _, cancelled in - guard !cancelled else { - LoggerProxy.debug("Warning: enhance operation on range \(range) cancelled") - return - } - } - - enhanceOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.processingError = error - self.fail(error) - } - } - - let scanEnhanceAdapterOperation = BlockOperation { [weak enhanceOperation, weak scanBlocksOperation] in - enhanceOperation?.error = scanBlocksOperation?.error - } - - let fetchOperation = FetchUnspentTxOutputsOperation( - accountRepository: accountRepository, - downloader: self.downloader, - rustbackend: rustBackend, - dataDb: config.dataDb, - startHeight: config.walletBirthday, - networkType: self.config.network.networkType - ) - - fetchOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .fetching - } - } - - fetchOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - LoggerProxy.debug("Warning: fetch operation on range \(range) cancelled") - return - } - DispatchQueue.main.async { [weak self] in - self?.processBatchFinished(range: range) - } - } - - fetchOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.processingError = error - self.fail(error) - } - } - - fetchOperation.fetchedUTXOsHandler = { result in - NotificationCenter.default.post( - name: .blockProcessorStoredUTXOs, - object: self, - userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] - ) - } - - let enhanceFetchAdapterOperation = BlockOperation { [weak fetchOperation, weak enhanceOperation] in - fetchOperation?.error = enhanceOperation?.error - } - - downloadValidateAdapterOperation.addDependency(downloadBlockOperation) - validateChainOperation.addDependency(downloadValidateAdapterOperation) - validateScanningAdapterOperation.addDependency(validateChainOperation) - scanBlocksOperation.addDependency(validateScanningAdapterOperation) - scanEnhanceAdapterOperation.addDependency(scanBlocksOperation) - enhanceOperation.addDependency(scanEnhanceAdapterOperation) - enhanceFetchAdapterOperation.addDependency(enhanceOperation) - fetchOperation.addDependency(enhanceFetchAdapterOperation) - - operationQueue.addOperations( - [ - downloadBlockOperation, - downloadValidateAdapterOperation, - validateChainOperation, - validateScanningAdapterOperation, - scanBlocksOperation, - scanEnhanceAdapterOperation, - enhanceOperation, - enhanceFetchAdapterOperation, - fetchOperation - ], - waitUntilFinished: false - ) } func calculateProgress(start: BlockHeight, current: BlockHeight, latest: BlockHeight) -> Float { @@ -892,35 +694,35 @@ public class CompactBlockProcessor { } func severeFailure(_ error: Error) { - operationQueue.cancelAllOperations() + cancelableTask?.cancel() LoggerProxy.error("show stoppper failure: \(error)") self.backoffTimer?.invalidate() self.retryAttempts = config.retries self.processingError = error - self.state = .error(error) + setState(.error(error)) self.notifyError(error) } func fail(_ error: Error) { // todo specify: failure LoggerProxy.error("\(error)") - operationQueue.cancelAllOperations() + cancelableTask?.cancel() self.retryAttempts += 1 self.processingError = error - switch self.state { + switch self.state.getState() { case .error: notifyError(error) default: break } - self.state = .error(error) + setState(.error(error)) guard self.maxAttemptsReached else { return } // don't set a new timer if there are no more attempts. self.setTimer() } func retryProcessing(range: CompactBlockRange) { - operationQueue.cancelAllOperations() + cancelableTask?.cancel() // update retries self.retryAttempts += 1 self.processingError = nil @@ -935,7 +737,7 @@ public class CompactBlockProcessor { // process next batch // processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday)) - nextBatchTask() + nextBatch() } catch { self.fail(error) } @@ -972,48 +774,9 @@ public class CompactBlockProcessor { } } - @available(*, deprecated, message: "This static method will be removed soon, use `nextBatchTask()` instead.") private func nextBatch() { - self.state = .downloading - NextStateHelper.nextState( - service: self.service, - downloader: self.downloader, - config: self.config, - rustBackend: self.rustBackend, - queue: nil - ) { result in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - switch result { - case .success(let nextState): - switch nextState { - case .finishProcessing(let height): - self.latestBlockHeight = height - self.processingFinished(height: height) - case .processNewBlocks(let range): - self.latestBlockHeight = range.upperBound - self.lowerBoundHeight = range.lowerBound - self.processNewBlocks(range: range) - case let .wait(latestHeight, latestDownloadHeight): - // Lightwalletd might be syncing - self.lowerBoundHeight = latestDownloadHeight - self.latestBlockHeight = latestHeight - LoggerProxy.info( - "Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight)" + - "while latest blockheight is reported at: \(latestHeight)" - ) - self.processingFinished(height: latestDownloadHeight) - } - case .failure(let error): - self.severeFailure(error) - } - } - } - } - - private func nextBatchTask() { - self.state = .downloading - Task { [self] in + setState(.downloading) + Task { @MainActor [self] in do { let nextState = try await NextStateHelper.nextStateAsync( service: self.service, @@ -1045,9 +808,9 @@ public class CompactBlockProcessor { } } - private func validationFailed(at height: BlockHeight) { + internal func validationFailed(at height: BlockHeight) { // cancel all Tasks - operationQueue.cancelAllOperations() + cancelableTask?.cancel() // register latest failure self.lastChainValidationFailure = height @@ -1078,13 +841,13 @@ public class CompactBlockProcessor { ) // process next batch - self.nextBatchTask() + self.nextBatch() } catch { self.fail(error) } } - private func processBatchFinished(range: CompactBlockRange) { + internal func processBatchFinished(range: CompactBlockRange) { guard processingError == nil else { retryProcessing(range: range) return @@ -1098,7 +861,7 @@ public class CompactBlockProcessor { return } - nextBatchTask() + nextBatch() } private func processingFinished(height: BlockHeight) { @@ -1110,7 +873,7 @@ public class CompactBlockProcessor { CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks ] ) - self.state = .synced + setState(.synced) setTimer() } @@ -1297,33 +1060,27 @@ extension UnifiedAddressShim: UnifiedAddress { } extension CompactBlockProcessor { - func refreshUTXOs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result) -> Void) { + func refreshUTXOs(tAddress: String, startHeight: BlockHeight) async throws -> RefreshedUTXOs { let dataDb = self.config.dataDb - self.downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) { [weak self] fetchResult in - switch fetchResult { - case .success(let utxos): - DispatchQueue.main.async { - self?.operationQueue.addOperation { [self] in - guard let self = self else { return } - do { - guard try self.rustBackend.clearUtxos( - dbData: dataDb, - address: tAddress, - sinceHeight: startHeight - 1, - networkType: self.config.network.networkType - ) >= 0 else { - result(.failure(CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned"))) - return - } - } catch { - result(.failure(self.mapError(error))) - } - result(.success(self.storeUTXOs(utxos, in: dataDb))) - } - } - case .failure(let error): - result(.failure(self?.mapError(error) ?? error)) + + let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) + var utxos: [UnspentTransactionOutputEntity] = [] + + do { + for try await utxo in stream { + utxos.append(utxo) } + guard try rustBackend.clearUtxos( + dbData: dataDb, + address: tAddress, + sinceHeight: startHeight - 1, + networkType: self.config.network.networkType + ) >= 0 else { + throw CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned") + } + return storeUTXOs(utxos, in: dataDb) + } catch { + throw mapError(error) } } @@ -1386,6 +1143,7 @@ extension CompactBlockProcessorError: LocalizedError { case let .wrongConsensusBranchId(expectedLocally, found): // swiftlint:disable:next line_length return "The remote server you are connecting to is publishing a different branch ID \(found) than the one your App is expecting to be (\(expectedLocally)). This could be caused by your App being out of date or the server you are connecting you being either on a different network or out of date after a network upgrade." + case .unknown: return "Unknown error occured." } } @@ -1405,12 +1163,6 @@ extension CompactBlockProcessorError: LocalizedError { } } -extension CompactBlockProcessor: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - notifyProgress(progress) - } -} - extension CompactBlockProcessor: EnhancementStreamDelegate { func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) { NotificationCenter.default.post( @@ -1423,38 +1175,12 @@ extension CompactBlockProcessor: EnhancementStreamDelegate { extension CompactBlockProcessor { enum NextStateHelper { - // swiftlint:disable:next function_parameter_count - static func nextState( - service: LightWalletService, - downloader: CompactBlockDownloading, - config: Configuration, - rustBackend: ZcashRustBackendWelding.Type, - queue: DispatchQueue?, - result: @escaping (Result) -> Void - ) { - let dispatchQueue = queue ?? DispatchQueue.global(qos: .userInitiated) - - dispatchQueue.async { - do { - let nextResult = try self.nextState( - service: service, - downloader: downloader, - config: config, - rustBackend: rustBackend - ) - result(.success(nextResult)) - } catch { - result(.failure(error)) - } - } - } - static func nextStateAsync( service: LightWalletService, downloader: CompactBlockDownloading, config: Configuration, rustBackend: ZcashRustBackendWelding.Type - ) async throws -> FigureNextBatchOperation.NextState { + ) async throws -> NextState { let task = Task(priority: .userInitiated) { // TODO: refactor to async call, issue 463, PR 493 // https://github.com/zcash/ZcashLightClientKit/issues/463 @@ -1473,7 +1199,7 @@ extension CompactBlockProcessor { downloader: CompactBlockDownloading, config: Configuration, rustBackend: ZcashRustBackendWelding.Type - ) throws -> FigureNextBatchOperation.NextState { + ) throws -> NextState { let info = try service.getInfo() try CompactBlockProcessor.validateServerInfo( diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift new file mode 100644 index 00000000..f85f1b8d --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift @@ -0,0 +1,183 @@ +// +// CompactBlockProcessing.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 10/15/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import Foundation + +extension CompactBlockProcessor { + func compactBlockBatchScanning(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + setState(.scanning) + let batchSize = UInt32(config.scanningBatchSize) + + do { + if batchSize == 0 { + let scanStartTime = Date() + guard self.rustBackend.scanBlocks(dbCache: config.cacheDb, dbData: config.dataDb, limit: batchSize, networkType: config.network.networkType) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + let scanFinishTime = Date() + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: BlockProgress( + startHeight: range.lowerBound, + targetHeight: range.upperBound, + progressHeight: range.upperBound + ), + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks + ) + ) + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(range.count) blocks in \(seconds) seconds") + } else { + let scanStartHeight = try transactionRepository.lastScannedHeight() + let targetScanHeight = range.upperBound + + var scannedNewBlocks = false + var lastScannedHeight = scanStartHeight + + repeat { + try Task.checkCancellation() + + let previousScannedHeight = lastScannedHeight + let scanStartTime = Date() + guard self.rustBackend.scanBlocks( + dbCache: config.cacheDb, + dbData: config.dataDb, + limit: batchSize, + networkType: config.network.networkType + ) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + let scanFinishTime = Date() + + lastScannedHeight = try transactionRepository.lastScannedHeight() + + scannedNewBlocks = previousScannedHeight != lastScannedHeight + if scannedNewBlocks { + let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) + notifyProgress(.scan(progress)) + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: progress, + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks + ) + ) + + let heightCount = lastScannedHeight - previousScannedHeight + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") + } + } while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight + if Task.isCancelled { + setState(.stopped) + LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled") + } + } + } catch { + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } +} + +extension CompactBlockProcessor { + func compactBlockScanning( + rustWelding: ZcashRustBackendWelding.Type, + cacheDb: URL, + dataDb: URL, + limit: UInt32 = 0, + networkType: NetworkType + ) throws { + try Task.checkCancellation() + + guard rustBackend.scanBlocks(dbCache: cacheDb, dbData: dataDb, limit: limit, networkType: networkType) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } +} + +public enum SDKMetrics { + struct BlockMetricReport { + var startHeight: BlockHeight + var targetHeight: BlockHeight + var duration: TimeInterval + var task: TaskReported + } + + enum TaskReported: String { + case scanBlocks + } + + static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey" + static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey" + static let progressHeightKey = "SDKMetrics.progressHeight" + static let startDateKey = "SDKMetrics.startDateKey" + static let endDateKey = "SDKMetrics.endDateKey" + static let taskReportedKey = "SDKMetrics.taskReported" + static let notificationName = Notification.Name("SDKMetrics.Notification") + + static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? { + guard + notification.name == notificationName, + let info = notification.userInfo, + let startHeight = info[startBlockHeightKey] as? BlockHeight, + let targetHeight = info[targetBlockHeightKey] as? BlockHeight, + let task = info[taskReportedKey] as? TaskReported, + let startDate = info[startDateKey] as? Date, + let endDate = info[endDateKey] as? Date + else { + return nil + } + + return BlockMetricReport( + startHeight: startHeight, + targetHeight: targetHeight, + duration: abs( + startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate + ), + task: task + ) + } + + static func progressReportNotification( + progress: BlockProgress, + start: Date, + end: Date, + task: SDKMetrics.TaskReported + ) -> Notification { + var notification = Notification(name: notificationName) + notification.userInfo = [ + startBlockHeightKey: progress.startHeight, + targetBlockHeightKey: progress.targetHeight, + progressHeightKey: progress.progressHeight, + startDateKey: start, + endDateKey: end, + taskReportedKey: task + ] + + return notification + } +} + +extension String.StringInterpolation { + mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) { + let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds" + appendLiteral(literal) + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift deleted file mode 100644 index 5afc17d3..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift +++ /dev/null @@ -1,252 +0,0 @@ -// -// CompactBlockProcessingOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/15/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -class CompactBlockScanningOperation: ZcashOperation { - override var isConcurrent: Bool { false } - - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var limit: UInt32 - private var network: NetworkType - init(rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, dataDb: URL, limit: UInt32 = 0, networkType: NetworkType) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.limit = limit - self.network = networkType - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: limit, networkType: network) else { - self.error = self.rustBackend.lastError() ?? ZcashOperationError.unknown - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail() - return - } - } -} - -public enum SDKMetrics { - struct BlockMetricReport { - var startHeight: BlockHeight - var targetHeight: BlockHeight - var duration: TimeInterval - var task: TaskReported - } - - enum TaskReported: String { - case scanBlocks - } - - static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey" - static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey" - static let progressHeightKey = "SDKMetrics.progressHeight" - static let startDateKey = "SDKMetrics.startDateKey" - static let endDateKey = "SDKMetrics.endDateKey" - static let taskReportedKey = "SDKMetrics.taskReported" - static let notificationName = Notification.Name("SDKMetrics.Notification") - - static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? { - guard - notification.name == notificationName, - let info = notification.userInfo, - let startHeight = info[startBlockHeightKey] as? BlockHeight, - let targetHeight = info[targetBlockHeightKey] as? BlockHeight, - let task = info[taskReportedKey] as? TaskReported, - let startDate = info[startDateKey] as? Date, - let endDate = info[endDateKey] as? Date - else { - return nil - } - - return BlockMetricReport( - startHeight: startHeight, - targetHeight: targetHeight, - duration: abs( - startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate - ), - task: task - ) - } - - static func progressReportNotification( - progress: BlockProgress, - start: Date, - end: Date, - task: SDKMetrics.TaskReported - ) -> Notification { - var notification = Notification(name: notificationName) - notification.userInfo = [ - startBlockHeightKey: progress.startHeight, - targetBlockHeightKey: progress.targetHeight, - progressHeightKey: progress.progressHeight, - startDateKey: start, - endDateKey: end, - taskReportedKey: task - ] - - return notification - } -} - -extension String.StringInterpolation { - mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) { - let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds" - appendLiteral(literal) - } -} - -class CompactBlockBatchScanningOperation: ZcashOperation { - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var batchSize: UInt32 - private var blockRange: CompactBlockRange - private var transactionRepository: TransactionRepository - private var network: NetworkType - private var cancelableTask: Task? - private var done = false - - private weak var progressDelegate: CompactBlockProgressDelegate? - - init( - rustWelding: ZcashRustBackendWelding.Type, - cacheDb: URL, - dataDb: URL, - transactionRepository: TransactionRepository, - range: CompactBlockRange, - batchSize: UInt32, - networkType: NetworkType, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.transactionRepository = transactionRepository - self.blockRange = range - self.batchSize = batchSize - self.progressDelegate = progressDelegate - self.network = networkType - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - do { - if batchSize == 0 { - let scanStartTime = Date() - guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else { - self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) - return - } - let scanFinishTime = Date() - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: BlockProgress( - startHeight: self.blockRange.lowerBound, - targetHeight: self.blockRange.upperBound, - progressHeight: self.blockRange.upperBound - ), - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) - ) - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds") - } else { - let scanStartHeight = try transactionRepository.lastScannedHeight() - let targetScanHeight = blockRange.upperBound - - var scannedNewBlocks = false - var lastScannedHeight = scanStartHeight - - repeat { - guard !shouldCancel() else { - cancel() - return - } - let previousScannedHeight = lastScannedHeight - let scanStartTime = Date() - guard self.rustBackend.scanBlocks( - dbCache: self.cacheDb, - dbData: self.dataDb, - limit: batchSize, - networkType: network - ) else { - self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) - return - } - let scanFinishTime = Date() - - lastScannedHeight = try transactionRepository.lastScannedHeight() - - scannedNewBlocks = previousScannedHeight != lastScannedHeight - if scannedNewBlocks { - let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) - progressDelegate?.progressUpdated(.scan(progress)) - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: progress, - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) - ) - - let heightCount = lastScannedHeight - previousScannedHeight - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") - } - } while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight - self.done = true - } - } catch { - scanFailed(error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - func scanFailed(_ error: Error) { - self.cancelableTask?.cancel() - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift index b778bcdb..fccf1fec 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift @@ -8,78 +8,58 @@ import Foundation -enum CompactBlockValidationError: Error { - case validationFailed(height: BlockHeight) - case failedWithError(_ error: Error?) -} -class CompactBlockValidationOperation: ZcashOperation { - override var isConcurrent: Bool { false } - - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var network: NetworkType - private var cancelableTask: Task? - private var done = false - - init( - rustWelding: ZcashRustBackendWelding.Type, - cacheDb: URL, - dataDb: URL, - networkType: NetworkType - ) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.network = networkType - super.init() +extension CompactBlockProcessor { + enum CompactBlockValidationError: Error { + case validationFailed(height: BlockHeight) + case failedWithError(_ error: Error?) } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() + func compactBlockValidation() async throws { + try Task.checkCancellation() + + setState(.validating) - cancelableTask = Task { - let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network) - + let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType) + + do { switch result { case 0: let error = CompactBlockValidationError.failedWithError(rustBackend.lastError()) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error case ZcashRustBackendWeldingConstants.validChain: - self.done = true + if Task.isCancelled { + setState(.stopped) + LoggerProxy.debug("Warning: compactBlockValidation cancelled") + } + LoggerProxy.debug("validateChainFinished") break default: let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result)) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } catch { + guard let validationError = error as? CompactBlockValidationError else { + LoggerProxy.error("Warning: compactBlockValidation returning generic error: \(error)") + return + } + + switch validationError { + case .validationFailed(let height): + LoggerProxy.debug("chain validation at height: \(height)") + validationFailed(at: height) + case .failedWithError(let err): + guard let validationFailure = err else { + LoggerProxy.error("validation failed without a specific error") + self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error")) + return + } + + throw validationFailure } } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift new file mode 100644 index 00000000..2e5e5265 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift @@ -0,0 +1,82 @@ +// +// FetchUnspentTxOutputs.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 6/2/21. +// + +import Foundation + +extension CompactBlockProcessor { + enum FetchUTXOError: Error { + case clearingFailed(_ error: Error?) + case fetchFailed(error: Error) + } + + func fetchUnspentTxOutputs(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + setState(.fetching) + + do { + let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) + do { + for tAddress in tAddresses { + guard try rustBackend.clearUtxos( + dbData: config.dataDb, + address: tAddress, + sinceHeight: config.walletBirthday - 1, + networkType: config.network.networkType + ) >= 0 else { + throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") + } + } + } catch { + throw FetchUTXOError.clearingFailed(error) + } + + var utxos: [UnspentTransactionOutputEntity] = [] + let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: config.walletBirthday) + for try await transaction in stream { + utxos.append(transaction) + } + + var refreshed: [UnspentTransactionOutputEntity] = [] + var skipped: [UnspentTransactionOutputEntity] = [] + + for utxo in utxos { + do { + try rustBackend.putUnspentTransparentOutput( + dbData: config.dataDb, + address: utxo.address, + txid: utxo.txid.bytes, + index: utxo.index, + script: utxo.script.bytes, + value: Int64(utxo.valueZat), + height: utxo.height, + networkType: config.network.networkType + ) ? refreshed.append(utxo) : skipped.append(utxo) + } catch { + LoggerProxy.error("failed to put utxo - error: \(error)") + skipped.append(utxo) + } + } + + let result = (inserted: refreshed, skipped: skipped) + + NotificationCenter.default.post( + name: .blockProcessorStoredUTXOs, + object: self, + userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] + ) + + if Task.isCancelled { + LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled") + } else { + processBatchFinished(range: range) + } + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift deleted file mode 100644 index 06765bc2..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// FetchUnspentTxOutputsOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 6/2/21. -// - -import Foundation - -class FetchUnspentTxOutputsOperation: ZcashOperation { - enum FetchUTXOError: Error { - case clearingFailed(_ error: Error?) - case fetchFailed(error: Error) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var fetchedUTXOsHandler: ((RefreshedUTXOs) -> Void)? - - private var accountRepository: AccountRepository - private var downloader: CompactBlockDownloading - private var rustbackend: ZcashRustBackendWelding.Type - private var startHeight: BlockHeight - private var network: NetworkType - private var dataDb: URL - private var cancelableTask: Task? - private var done = false - - init( - accountRepository: AccountRepository, - downloader: CompactBlockDownloading, - rustbackend: ZcashRustBackendWelding.Type, - dataDb: URL, - startHeight: BlockHeight, - networkType: NetworkType - ) { - self.dataDb = dataDb - self.accountRepository = accountRepository - self.downloader = downloader - self.rustbackend = rustbackend - self.startHeight = startHeight - self.network = networkType - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - do { - let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) - do { - for tAddress in tAddresses { - guard try self.rustbackend.clearUtxos( - dbData: dataDb, - address: tAddress, - sinceHeight: startHeight - 1, - networkType: network - ) >= 0 else { - throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") - } - } - } catch { - throw FetchUTXOError.clearingFailed(error) - } - - var utxos: [UnspentTransactionOutputEntity] = [] - let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) - for try await transaction in stream { - utxos.append(transaction) - } - - let result = storeUTXOs(utxos, in: dataDb) - - self.fetchedUTXOsHandler?(result) - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - private func storeUTXOs(_ utxos: [UnspentTransactionOutputEntity], in dataDb: URL) -> RefreshedUTXOs { - var refreshed: [UnspentTransactionOutputEntity] = [] - var skipped: [UnspentTransactionOutputEntity] = [] - - for utxo in utxos { - do { - try self.rustbackend.putUnspentTransparentOutput( - dbData: dataDb, - address: utxo.address, - txid: utxo.txid.bytes, - index: utxo.index, - script: utxo.script.bytes, - value: Int64(utxo.valueZat), - height: utxo.height, - networkType: network - ) ? refreshed.append(utxo) : skipped.append(utxo) - } catch { - LoggerProxy.error("failed to put utxo - error: \(error)") - skipped.append(utxo) - } - } - - return (inserted: refreshed, skipped: skipped) - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift new file mode 100644 index 00000000..10fb5243 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift @@ -0,0 +1,34 @@ +// +// FigureNextBatch.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 6/17/21. +// + +import Foundation + +extension CompactBlockProcessor { + enum NextState { + case finishProcessing(height: BlockHeight) + case processNewBlocks(range: CompactBlockRange) + case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight) + } + + @discardableResult + func figureNextBatch( + downloader: CompactBlockDownloading + ) async throws -> NextState { + try Task.checkCancellation() + + do { + return try await CompactBlockProcessor.NextStateHelper.nextStateAsync( + service: service, + downloader: downloader, + config: config, + rustBackend: rustBackend + ) + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift deleted file mode 100644 index cecd8bf7..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// FigureNextBatchOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 6/17/21. -// - -import Foundation - -class FigureNextBatchOperation: ZcashOperation { - enum NextState { - case finishProcessing(height: BlockHeight) - case processNewBlocks(range: CompactBlockRange) - case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight) - } - - private var service: LightWalletService - private var downloader: CompactBlockDownloading - private var config: CompactBlockProcessor.Configuration - private var rustBackend: ZcashRustBackendWelding.Type - private(set) var result: NextState? - - required init( - downloader: CompactBlockDownloading, - service: LightWalletService, - config: CompactBlockProcessor.Configuration, - rustBackend: ZcashRustBackendWelding.Type - ) { - self.service = service - self.config = config - self.downloader = downloader - self.rustBackend = rustBackend - - super.init() - - self.name = "Next Batch Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - do { - result = try CompactBlockProcessor.NextStateHelper.nextState( - service: self.service, - downloader: self.downloader, - config: self.config, - rustBackend: self.rustBackend - ) - } catch { - self.fail(error: error) - } - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift deleted file mode 100644 index 0b5fb6f1..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ZcashOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/27/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -typealias ZcashOperationCompletionBlock = (_ finished: Bool, _ cancelled: Bool) -> Void -typealias ZcashOperationStartedBlock = () -> Void -typealias ZcashOperationErrorBlock = (_ error: Error) -> Void - -enum ZcashOperationError: Error { - case unknown -} - -class ZcashOperation: Operation { - var error: Error? - var startedHandler: ZcashOperationStartedBlock? - var errorHandler: ZcashOperationErrorBlock? - var completionHandler: ZcashOperationCompletionBlock? - var handlerDispatchQueue = DispatchQueue.main - - override init() { - super.init() - - completionBlock = { [weak self] in - guard let self = self, let handler = self.completionHandler else { return } - - handler(self.isFinished, self.isCancelled) - } - } - - convenience init(completionDispatchQueue: DispatchQueue = DispatchQueue.main) { - self.init() - self.handlerDispatchQueue = completionDispatchQueue - } - - func shouldCancel() -> Bool { - self.error != nil || isCancelled || dependencyCancelled() - } - - func dependencyCancelled() -> Bool { - self.dependencies.first { $0.isCancelled } != nil - } - - func fail(error: Error? = nil) { - defer { - self.cancel() - } - - if let error = error { - self.error = error - } - LoggerProxy.debug("\(self) failed") - - guard let errorHandler = self.errorHandler else { - return - } - - self.handlerDispatchQueue.async { [weak self] in - let error = error ?? (self?.error ?? ZcashOperationError.unknown) - errorHandler(error) - } - } -} diff --git a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift index 276b598a..b023a30d 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -369,7 +369,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } @@ -551,7 +551,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } @@ -619,7 +619,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 67f11235..c7948e47 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -201,7 +201,7 @@ public protocol Synchronizer { /// Returns the latests UTXOs for the given address from the specified height on - func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result) -> Void) + func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs /// Returns the last stored unshielded balance func getTransparentBalance(accountIndex: Int) throws -> WalletBalance diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 66c3e1f8..04c8e869 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -192,7 +192,7 @@ public class SDKSynchronizer: Synchronizer { return } - blockProcessor.stop(cancelTasks: true) + blockProcessor.stop() self.status = .stopped } @@ -668,8 +668,8 @@ public class SDKSynchronizer: Synchronizer { } } - public func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result) -> Void) { - self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result) + public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs { + try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height) } @available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead") public func getShieldedBalance(accountIndex: Int = 0) -> Int64 { @@ -885,6 +885,7 @@ public class SDKSynchronizer: Synchronizer { return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError) case .saplingActivationMismatch: return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError) + case .unknown: break } } diff --git a/Tests/NetworkTests/BlockScanOperationTests.swift b/Tests/NetworkTests/BlockScanOperationTests.swift deleted file mode 100644 index 39208a01..00000000 --- a/Tests/NetworkTests/BlockScanOperationTests.swift +++ /dev/null @@ -1,265 +0,0 @@ -// -// BlockScanOperationTests.swift -// ZcashLightClientKitTests -// -// Created by Francisco Gindre on 10/17/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import XCTest -import SQLite -@testable import TestUtils -@testable import ZcashLightClientKit - -// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage -class BlockScanOperationTests: XCTestCase { - let rustWelding = ZcashRustBackend.self - - var operationQueue = OperationQueue() - var cacheDbURL: URL! - var dataDbURL: URL! - - var uvk = UVFakeKey( - extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length - extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61" - ) - - var walletBirthDay = Checkpoint.birthday( - with: 1386000, - network: ZcashNetworkBuilder.network(for: .testnet) - ) - - var network = ZcashNetworkBuilder.network(for: .testnet) - var blockRepository: BlockRepository! - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - super.setUp() - self.cacheDbURL = try! __cacheDbURL() - self.dataDbURL = try! __dataDbURL() - - deleteDBs() - operationQueue.maxConcurrentOperationCount = 1 - } - - private func deleteDBs() { - try? FileManager.default.removeItem(at: cacheDbURL) - try? FileManager.default.removeItem(at: dataDbURL) - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - operationQueue.cancelAllOperations() - - try? FileManager.default.removeItem(at: cacheDbURL) - try? FileManager.default.removeItem(at: dataDbURL) - } - - func testSingleDownloadAndScanOperation() { - logger = SampleLogger(logLevel: .debug) - - XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)) - - let downloadStartedExpect = XCTestExpectation(description: "\(self.description) download started") - let downloadExpect = XCTestExpectation(description: "\(self.description) download") - let scanStartedExpect = XCTestExpectation(description: "\(self.description) scan started") - let scanExpect = XCTestExpectation(description: "\(self.description) scan") - let latestScannedBlockExpect = XCTestExpectation(description: "\(self.description) latestScannedHeight") - let service = LightWalletGRPCService( - endpoint: LightWalletEndpoint( - address: "lightwalletd.testnet.electriccoin.co", - port: 9067 - ) - ) - let blockCount = 100 - let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount - let downloadOperation = CompactBlockDownloadOperation( - downloader: CompactBlockDownloader.sqlDownloader( - service: service, - at: cacheDbURL - )!, - range: range - ) - let scanOperation = CompactBlockScanningOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - networkType: network.networkType - ) - - downloadOperation.startedHandler = { - downloadStartedExpect.fulfill() - } - - downloadOperation.completionHandler = { finished, cancelled in - downloadExpect.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - downloadOperation.errorHandler = { error in - XCTFail("Download Operation failed with Error: \(error)") - } - - scanOperation.startedHandler = { - scanStartedExpect.fulfill() - } - - scanOperation.completionHandler = { finished, cancelled in - scanExpect.fulfill() - XCTAssertFalse(cancelled) - XCTAssertTrue(finished) - } - - scanOperation.errorHandler = { error in - XCTFail("Scan Operation failed with Error: \(error)") - } - - scanOperation.addDependency(downloadOperation) - var latestScannedheight = BlockHeight.empty() - let latestScannedBlockOperation = BlockOperation { - let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true)) - latestScannedheight = repository.lastScannedBlockHeight() - } - - latestScannedBlockOperation.completionBlock = { - latestScannedBlockExpect.fulfill() - XCTAssertEqual(latestScannedheight, range.upperBound) - } - - latestScannedBlockOperation.addDependency(scanOperation) - - operationQueue.addOperations( - [downloadOperation, scanOperation, latestScannedBlockOperation], - waitUntilFinished: false - ) - - wait( - for: [downloadStartedExpect, downloadExpect, scanStartedExpect, scanExpect, latestScannedBlockExpect], - timeout: 10, - enforceOrder: true - ) - } - @objc func observeBenchmark(_ notification: Notification) { - guard let report = SDKMetrics.blockReportFromNotification(notification) else { - return - } - - print("observed benchmark: \(report)") - } - func testScanValidateDownload() throws { - logger = SampleLogger(logLevel: .debug) - - NotificationCenter.default.addObserver( - self, - selector: #selector(observeBenchmark(_:)), - name: SDKMetrics.notificationName, - object: nil - ) - - try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType) - - guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else { - XCTFail("failed to init account table") - return - } - - try self.rustWelding.initBlocksTable( - dbData: dataDbURL, - height: Int32(walletBirthDay.height), - hash: walletBirthDay.hash, - time: walletBirthDay.time, - saplingTree: walletBirthDay.saplingTree, - networkType: network.networkType - ) - - let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) - let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) - try storage.createTable() - - let downloadExpectation = XCTestExpectation(description: "download expectation") - let validateExpectation = XCTestExpectation(description: "validate expectation") - let scanExpectation = XCTestExpectation(description: "scan expectation") - - let downloadOperation = CompactBlockStreamDownloadOperation( - service: service, - storage: storage, - blockBufferSize: 10, - startHeight: walletBirthDay.height, - targetHeight: walletBirthDay.height + 10000, - progressDelegate: self - ) - - downloadOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - downloadExpectation.fulfill() - } - - downloadOperation.errorHandler = { error in - if let lwdError = error as? LightWalletServiceError { - switch lwdError { - case .timeOut: - XCTAssert(true) - default: - XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)") - } - } else { - XCTFail("Error should have been a timeLimit reached Error - \(error)") - } - } - - let validationOperation = CompactBlockValidationOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - networkType: network.networkType - ) - - validationOperation.errorHandler = { error in - self.operationQueue.cancelAllOperations() - XCTFail("failed with error \(error)") - } - - validationOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - validateExpectation.fulfill() - } - - let transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL) - let scanningOperation = CompactBlockBatchScanningOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - transactionRepository: transactionRepository, - range: CompactBlockRange( - uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000) - ), - batchSize: 1000, - networkType: network.networkType, - progressDelegate: self - ) - - scanningOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - scanExpectation.fulfill() - } - - operationQueue.addOperations([downloadOperation, validationOperation, scanningOperation], waitUntilFinished: false) - - wait(for: [downloadExpectation, validateExpectation, scanExpectation], timeout: 300, enforceOrder: true) - } -} - -extension BlockScanOperationTests: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - } -} - -struct UVFakeKey: UnifiedViewingKey { - var extfvk: ExtendedFullViewingKey - var extpub: ExtendedPublicKey -} diff --git a/Tests/NetworkTests/BlockScanTests.swift b/Tests/NetworkTests/BlockScanTests.swift new file mode 100644 index 00000000..920fb0e5 --- /dev/null +++ b/Tests/NetworkTests/BlockScanTests.swift @@ -0,0 +1,195 @@ +// +// BlockScanTests.swift +// ZcashLightClientKitTests +// +// Created by Francisco Gindre on 10/17/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import XCTest +import SQLite +@testable import TestUtils +@testable import ZcashLightClientKit + +// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage +class BlockScanTests: XCTestCase { + let rustWelding = ZcashRustBackend.self + + var cacheDbURL: URL! + var dataDbURL: URL! + + var uvk = UVFakeKey( + extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length + extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61" + ) + + var walletBirthDay = Checkpoint.birthday( + with: 1386000, + network: ZcashNetworkBuilder.network(for: .testnet) + ) + + var network = ZcashNetworkBuilder.network(for: .testnet) + var blockRepository: BlockRepository! + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + super.setUp() + self.cacheDbURL = try! __cacheDbURL() + self.dataDbURL = try! __dataDbURL() + + deleteDBs() + } + + private func deleteDBs() { + try? FileManager.default.removeItem(at: cacheDbURL) + try? FileManager.default.removeItem(at: dataDbURL) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + + try? FileManager.default.removeItem(at: cacheDbURL) + try? FileManager.default.removeItem(at: dataDbURL) + } + + func testSingleDownloadAndScan() async throws { + logger = SampleLogger(logLevel: .debug) + + XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)) + + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() + let service = LightWalletGRPCService( + endpoint: LightWalletEndpoint( + address: "lightwalletd.testnet.electriccoin.co", + port: 9067 + ) + ) + let blockCount = 100 + let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount + let downloader = CompactBlockDownloader.sqlDownloader( + service: service, + at: cacheDbURL + )! + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: network, + walletBirthday: network.constants.saplingActivationHeight + ) + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: ZcashRustBackend.self, + config: processorConfig + ) + + let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true)) + var latestScannedheight = BlockHeight.empty() + do { + try await compactBlockProcessor.compactBlockDownload( + downloader: downloader, + range: range + ) + XCTAssertFalse(Task.isCancelled) + try compactBlockProcessor.compactBlockScanning( + rustWelding: rustWelding, + cacheDb: cacheDbURL, + dataDb: dataDbURL, + networkType: network.networkType + ) + latestScannedheight = repository.lastScannedBlockHeight() + XCTAssertEqual(latestScannedheight, range.upperBound) + } catch { + XCTFail("Download failed with error: \(error)") + } + } + + @objc func observeBenchmark(_ notification: Notification) { + guard let report = SDKMetrics.blockReportFromNotification(notification) else { + return + } + + print("observed benchmark: \(report)") + } + + func testScanValidateDownload() async throws { + logger = SampleLogger(logLevel: .debug) + + NotificationCenter.default.addObserver( + self, + selector: #selector(observeBenchmark(_:)), + name: SDKMetrics.notificationName, + object: nil + ) + + try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType) + + guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else { + XCTFail("failed to init account table") + return + } + + try self.rustWelding.initBlocksTable( + dbData: dataDbURL, + height: Int32(walletBirthDay.height), + hash: walletBirthDay.hash, + time: walletBirthDay.time, + saplingTree: walletBirthDay.saplingTree, + networkType: network.networkType + ) + + let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) + let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) + try storage.createTable() + + var processorConfig = CompactBlockProcessor.Configuration( + cacheDb: cacheDbURL, + dataDb: dataDbURL, + walletBirthday: network.constants.saplingActivationHeight, + network: network + ) + processorConfig.scanningBatchSize = 1000 + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: rustWelding, + config: processorConfig + ) + + let range = CompactBlockRange( + uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000) + ) + + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: range.lowerBound, + targetHeight: range.upperBound + ) + XCTAssertFalse(Task.isCancelled) + + try await compactBlockProcessor.compactBlockValidation() + XCTAssertFalse(Task.isCancelled) + + try await compactBlockProcessor.compactBlockBatchScanning(range: range) + XCTAssertFalse(Task.isCancelled) + } catch { + if let lwdError = error as? LightWalletServiceError { + switch lwdError { + case .timeOut: + XCTAssert(true) + default: + XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)") + } + } else { + XCTFail("Error should have been a timeLimit reached Error - \(error)") + } + } + } +} + +struct UVFakeKey: UnifiedViewingKey { + var extfvk: ExtendedFullViewingKey + var extpub: ExtendedPublicKey +} diff --git a/Tests/NetworkTests/BlockStreamingTest.swift b/Tests/NetworkTests/BlockStreamingTest.swift index 57220a82..9d509a43 100644 --- a/Tests/NetworkTests/BlockStreamingTest.swift +++ b/Tests/NetworkTests/BlockStreamingTest.swift @@ -11,12 +11,6 @@ import XCTest // swiftlint:disable print_function_usage class BlockStreamingTest: XCTestCase { - var queue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - return queue - }() - override func setUpWithError() throws { try super.setUpWithError() logger = SampleLogger(logLevel: .debug) @@ -27,7 +21,7 @@ class BlockStreamingTest: XCTestCase { try? FileManager.default.removeItem(at: __dataDbURL()) } - func testStreamOperation() throws { + func testStream() throws { let expectation = XCTestExpectation(description: "blockstream expectation") let service = LightWalletGRPCService( @@ -61,9 +55,7 @@ class BlockStreamingTest: XCTestCase { wait(for: [expectation], timeout: 1000) } - func testStreamOperationCancellation() throws { - let expectation = XCTestExpectation(description: "blockstream expectation") - + func testStreamCancellation() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -71,37 +63,38 @@ class BlockStreamingTest: XCTestCase { singleCallTimeout: 10000, streamingCallTimeout: 10000 ) + let storage = try TestDbBuilder.inMemoryCompactBlockStorage() - let startHeight = try service.latestBlockHeight() - 100_000 - let operation = CompactBlockStreamDownloadOperation( + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - blockBufferSize: 10, - startHeight: startHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - XCTAssert(cancelled) - expectation.fulfill() + let cancelableTask = Task { + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: startHeight + ) + XCTAssertTrue(Task.isCancelled) + } catch { + XCTFail("failed with error: \(error)") + } } - operation.errorHandler = { error in - XCTFail("failed with error: \(error)") - expectation.fulfill() - } - - queue.addOperation(operation) - DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: { - self.queue.cancelAllOperations() - }) - wait(for: [expectation], timeout: 1000) + try await Task.sleep(nanoseconds: 3_000_000_000) + cancelableTask.cancel() } - func testStreamOperationTimeout() throws { - let expectation = XCTestExpectation(description: "blockstream expectation") - let errorExpectation = XCTestExpectation(description: "blockstream error expectation") + func testStreamTimeout() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -109,49 +102,49 @@ class BlockStreamingTest: XCTestCase { singleCallTimeout: 1000, streamingCallTimeout: 3000 ) + let storage = try TestDbBuilder.inMemoryCompactBlockStorage() - let startHeight = try service.latestBlockHeight() - 100_000 - let operation = CompactBlockStreamDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - blockBufferSize: 10, - startHeight: startHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { finished, _ in - XCTAssert(finished) - - expectation.fulfill() - } + let date = Date() - operation.errorHandler = { error in + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: startHeight + ) + } catch { if let lwdError = error as? LightWalletServiceError { switch lwdError { case .timeOut: XCTAssert(true) default: - XCTFail("LWD Service erro found, but should have been a timeLimit reached Error") + XCTFail("LWD Service error found, but should have been a timeLimit reached Error") } } else { XCTFail("Error should have been a timeLimit reached Error") } - errorExpectation.fulfill() } - queue.addOperation(operation) - let date = Date() - wait(for: [errorExpectation], timeout: 4) let now = Date() let elapsed = now.timeIntervalSince(date) print("took \(elapsed) seconds") } - func testBatchOperation() throws { - let expectation = XCTestExpectation(description: "blockbatch expectation") - + func testBatch() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -162,34 +155,29 @@ class BlockStreamingTest: XCTestCase { let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() ) let targetHeight = try service.latestBlockHeight() let startHeight = targetHeight - 10_000 - let operation = CompactBlockBatchDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - startHeight: startHeight, - targetHeight: targetHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - if cancelled { - XCTFail("operation cancelled") - } - expectation.fulfill() - } - - operation.errorHandler = { error in + let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight)) + do { + try await compactBlockProcessor.compactBlockBatchDownload(range: range) + XCTAssertFalse(Task.isCancelled) + } catch { XCTFail("failed with error: \(error)") - expectation.fulfill() } - - queue.addOperation(operation) - - wait(for: [expectation], timeout: 120) } - func testBatchOperationCancellation() throws { - let expectation = XCTestExpectation(description: "blockbatch expectation") - + func testBatchCancellation() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -200,36 +188,30 @@ class BlockStreamingTest: XCTestCase { let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() ) let targetHeight = try service.latestBlockHeight() let startHeight = targetHeight - 100_000 - let operation = CompactBlockBatchDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - startHeight: startHeight, - targetHeight: targetHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - XCTAssert(cancelled) - expectation.fulfill() + let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight)) + let cancelableTask = Task { + do { + try await compactBlockProcessor.compactBlockBatchDownload(range: range) + XCTAssertTrue(Task.isCancelled) + } catch { + XCTFail("failed with error: \(error)") + } } - operation.errorHandler = { error in - XCTFail("failed with error: \(error)") - expectation.fulfill() - } - - queue.addOperation(operation) - DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: { - self.queue.cancelAllOperations() - }) - wait(for: [expectation], timeout: 1000) - } -} - -extension BlockStreamingTest: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - print("progressHeight: \(String(describing: progress.progressHeight))") - print("startHeight: \(progress.progress)") - print("targetHeight: \(String(describing: progress.targetHeight))") + try await Task.sleep(nanoseconds: 3_000_000_000) + cancelableTask.cancel() } } diff --git a/Tests/NetworkTests/DownloadOperationTests.swift b/Tests/NetworkTests/DownloadTests.swift similarity index 54% rename from Tests/NetworkTests/DownloadOperationTests.swift rename to Tests/NetworkTests/DownloadTests.swift index 34f47012..442e7d95 100644 --- a/Tests/NetworkTests/DownloadOperationTests.swift +++ b/Tests/NetworkTests/DownloadTests.swift @@ -1,5 +1,5 @@ // -// DownloadOperationTests.swift +// DownloadTests.swift // ZcashLightClientKitTests // // Created by Francisco Gindre on 10/16/19. @@ -12,40 +12,38 @@ import SQLite @testable import ZcashLightClientKit // swiftlint:disable force_try -class DownloadOperationTests: XCTestCase { - var operationQueue = OperationQueue() +class DownloadTests: XCTestCase { var network = ZcashNetworkBuilder.network(for: .testnet) override func tearDown() { super.tearDown() - operationQueue.cancelAllOperations() } - func testSingleOperation() { - let expect = XCTestExpectation(description: self.description) - + func testSingleDownload() async throws { let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let downloader = CompactBlockDownloader(service: service, storage: storage) let blockCount = 100 let activationHeight = network.constants.saplingActivationHeight let range = activationHeight ... activationHeight + blockCount - let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range) - downloadOperation.completionHandler = { finished, cancelled in - expect.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: network, + walletBirthday: network.constants.saplingActivationHeight + ) + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: ZcashRustBackend.self, + config: processorConfig + ) + + do { + try await compactBlockProcessor.compactBlockDownload(downloader: downloader, range: range) + } catch { + XCTFail("Download failed with error: \(error)") } - downloadOperation.errorHandler = { error in - XCTFail("Donwload Operation failed with error: \(error)") - } - - operationQueue.addOperation(downloadOperation) - - wait(for: [expect], timeout: 10) - XCTAssertEqual(try! storage.latestHeight(), range.upperBound) } } diff --git a/Tests/OfflineTests/BlockBatchValidationTests.swift b/Tests/OfflineTests/BlockBatchValidationTests.swift index faea5486..ec8178eb 100644 --- a/Tests/OfflineTests/BlockBatchValidationTests.swift +++ b/Tests/OfflineTests/BlockBatchValidationTests.swift @@ -11,29 +11,13 @@ import XCTest // swiftlint:disable force_try type_body_length class BlockBatchValidationTests: XCTestCase { - var queue: OperationQueue = { - let queue = OperationQueue() - queue.name = "Test Queue" - queue.maxConcurrentOperationCount = 1 - return queue - }() - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - super.setUp() - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testBranchIdFailure() throws { + func testBranchIdFailure() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -59,17 +43,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = Int32(0xd34d) - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.wrongConsensusBranchId: break @@ -77,19 +62,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)") } } - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testBranchNetworkMismatchFailure() throws { + func testBranchNetworkMismatchFailure() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -115,17 +96,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet): break @@ -133,20 +115,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testBranchNetworkTypeWrongFailure() throws { + func testBranchNetworkTypeWrongFailure() async throws { let network = ZcashNetworkBuilder.network(for: .testnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -172,17 +149,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.generalError: break @@ -190,20 +168,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testSaplingActivationHeightMismatch() throws { + func testSaplingActivationHeightMismatch() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -230,17 +203,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.saplingActivationMismatch( expected: network.constants.saplingActivationHeight, @@ -251,15 +225,9 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testResultIsWait() throws { + func testResultIsWait() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1210000) @@ -268,10 +236,11 @@ class BlockBatchValidationTests: XCTestCase { service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) let expectedStoreLatestHeight = BlockHeight(1220000) - let expectedResult = FigureNextBatchOperation.NextState.wait( + let expectedResult = CompactBlockProcessor.NextState.wait( latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -298,50 +267,41 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { XCTFail("this shouldn't happen: \(error)") } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + switch nextBatch { case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } - func testResultProcessNew() throws { + func testResultProcessNew() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1230000) let service = MockLightWalletService( @@ -350,13 +310,14 @@ class BlockBatchValidationTests: XCTestCase { ) let expectedStoreLatestHeight = BlockHeight(1220000) let walletBirthday = BlockHeight(1210000) - let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks( + let expectedResult = CompactBlockProcessor.NextState.processNewBlocks( range: CompactBlockProcessor.nextBatchBlockRange( latestHeight: expectedLatestHeight, latestDownloadedHeight: expectedStoreLatestHeight, walletBirthday: walletBirthday ) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -383,50 +344,41 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { _ in - XCTFail("this shouldn't happen") - } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { + XCTFail("this shouldn't happen: \(error)") + } + + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + switch nextBatch { case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } - func testResultProcessorFinished() throws { + func testResultProcessorFinished() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1230000) let service = MockLightWalletService( @@ -435,7 +387,8 @@ class BlockBatchValidationTests: XCTestCase { ) let expectedStoreLatestHeight = BlockHeight(1230000) let walletBirthday = BlockHeight(1210000) - let expectedResult = FigureNextBatchOperation.NextState.finishProcessing(height: expectedStoreLatestHeight) + let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoreLatestHeight) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -462,46 +415,38 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { _ in - XCTFail("this shouldn't happen") - } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { + XCTFail("this shouldn't happen: \(error)") + } + + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + + switch nextBatch { case .finishProcessing(height: expectedLatestHeight): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } } From e3743f8ba09b772b1654ebd6f902f49c62163b2b Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Thu, 15 Sep 2022 11:10:10 -0300 Subject: [PATCH 15/15] Fix commit SHA pointing to inexintent hash. now pointing to `fcf3d4b652b67d6a9b9bdc8e5565eb8dd4b46145` --- Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index af5aca43..2b90ee68 100644 --- a/Package.resolved +++ b/Package.resolved @@ -87,7 +87,7 @@ "repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state": { "branch": "bin/librustzcash_0_7", - "revision": "61534023777235cc5b76d4ec44e27edc1658eea6", + "revision": "fcf3d4b652b67d6a9b9bdc8e5565eb8dd4b46145", "version": null } }