From f1a570bbc23701edfd69103084f8cddbcc4b940b Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 25 Aug 2022 15:39:59 +0200 Subject: [PATCH] [#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] { + [] + } }