From 29e06d0b8a091c430b5bf4ccfcd78911a46aaf2d Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 24 Aug 2022 14:18:28 -0700 Subject: [PATCH 01/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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/10] [#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() } }