From 46d8c04afc83eacfa8fd53a302ba6956b9ca0ad0 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 3 Oct 2022 12:44:33 +0200 Subject: [PATCH 1/3] [#546] TransactionEncoder To Async/Await (#547) - async/await API update - code cleaned up from the unused APIs --- .../PersistentTransactionManager.swift | 4 +- .../Transaction/TransactionEncoder.swift | 50 +------------------ .../WalletTransactionEncoder.swift | 47 +---------------- 3 files changed, 6 insertions(+), 95 deletions(-) diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index b1e5fdab..8cae2b8a 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -96,7 +96,7 @@ class PersistentTransactionManager: OutboundTransactionManager { } do { - let encodedTransaction = try self.encoder.createShieldingTransaction( + let encodedTransaction = try await self.encoder.createShieldingTransaction( spendingKey: spendingKey, tSecretKey: tsk, memo: pendingTransaction.memo?.asZcashTransactionMemo(), @@ -126,7 +126,7 @@ class PersistentTransactionManager: OutboundTransactionManager { pendingTransaction: PendingTransactionEntity ) async throws -> PendingTransactionEntity { do { - let encodedTransaction = try self.encoder.createTransaction( + let encodedTransaction = try await self.encoder.createTransaction( spendingKey: spendingKey, zatoshi: pendingTransaction.intValue, to: pendingTransaction.toAddress, diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift index f8fcdc80..f6d1dc91 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -39,31 +39,7 @@ protocol TransactionEncoder { to address: String, memo: String?, from accountIndex: Int - ) throws -> EncodedTransaction - - /** - Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation - doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using - double-bangs for things). - Non-blocking - - - Parameters: - - Parameter spendingKey: a string containing the spending key - - Parameter zatoshi: the amount to send in zatoshis - - Parameter to: string containing the recipient address - - Parameter memo: string containing the memo (optional) - - Parameter accountIndex: index of the account that will be used to send the funds - - Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a TransactionEncoderError - */ - // swiftlint:disable:next function_parameter_count - func createTransaction( - spendingKey: String, - zatoshi: Int, - to address: String, - memo: String?, - from accountIndex: Int, - result: @escaping TransactionEncoderResultBlock - ) + ) async throws -> EncodedTransaction /** Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things). @@ -82,29 +58,7 @@ protocol TransactionEncoder { tSecretKey: String, memo: String?, from accountIndex: Int - ) throws -> EncodedTransaction - - /** - Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things). - - Non-Blocking - - - Parameters: - - Parameter spendingKey: a string containing the spending key - - Parameter tSecretKey: transparent secret key to spend the UTXOs - - Parameter memo: string containing the memo (optional) - - Parameter accountIndex: index of the account that will be used to send the funds - - - Returns: a TransactionEncoderResultBlock - */ - - func createShieldingTransaction( - spendingKey: String, - tSecretKey: String, - memo: String?, - from accountIndex: Int, - result: @escaping TransactionEncoderResultBlock - ) + ) async throws -> EncodedTransaction /** Fetch the Transaction Entity from the encoded representation diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 9394d004..d973cf8e 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -10,7 +10,6 @@ import Foundation class WalletTransactionEncoder: TransactionEncoder { var rustBackend: ZcashRustBackendWelding.Type var repository: TransactionRepository - var queue: DispatchQueue private var outputParamsURL: URL private var spendParamsURL: URL @@ -34,7 +33,6 @@ class WalletTransactionEncoder: TransactionEncoder { self.outputParamsURL = outputParams self.spendParamsURL = spendParams self.networkType = networkType - self.queue = DispatchQueue(label: "wallet.transaction.encoder.serial.queue") } convenience init(initializer: Initializer) { @@ -55,7 +53,7 @@ class WalletTransactionEncoder: TransactionEncoder { to address: String, memo: String?, from accountIndex: Int - ) throws -> EncodedTransaction { + ) async throws -> EncodedTransaction { let txId = try createSpend( spendingKey: spendingKey, zatoshi: zatoshi, @@ -77,35 +75,6 @@ class WalletTransactionEncoder: TransactionEncoder { throw TransactionEncoderError.notFound(transactionId: txId) } } - - // swiftlint:disable:next function_parameter_count - func createTransaction( - spendingKey: String, - zatoshi: Int, - to address: String, - memo: String?, - from accountIndex: Int, - result: @escaping TransactionEncoderResultBlock - ) { - queue.async { [weak self] in - guard let self = self else { return } - do { - result( - .success( - try self.createTransaction( - spendingKey: spendingKey, - zatoshi: zatoshi, - to: address, - memo: memo, - from: accountIndex - ) - ) - ) - } catch { - result(.failure(error)) - } - } - } func createSpend(spendingKey: String, zatoshi: Int, to address: String, memo: String?, from accountIndex: Int) throws -> Int { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { @@ -136,7 +105,7 @@ class WalletTransactionEncoder: TransactionEncoder { tSecretKey: String, memo: String?, from accountIndex: Int - ) throws -> EncodedTransaction { + ) async throws -> EncodedTransaction { let txId = try createShieldingSpend( spendingKey: spendingKey, tsk: tSecretKey, @@ -158,18 +127,6 @@ class WalletTransactionEncoder: TransactionEncoder { } } - func createShieldingTransaction( - spendingKey: String, - tSecretKey: String, - memo: String?, - from accountIndex: Int, - result: @escaping TransactionEncoderResultBlock - ) { - queue.async { - result(.failure(RustWeldingError.genericError(message: "not implemented"))) - } - } - func createShieldingSpend(spendingKey: String, tsk: String, memo: String?, accountIndex: Int) throws -> Int { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { throw TransactionEncoderError.missingParams From fa5bbbb2bf3e1ea9c8fecbeebf25a2aee301eb24 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 3 Oct 2022 20:54:43 +0200 Subject: [PATCH 2/3] [#541] Initialise gRPC on a separate thread (#545) MultiThreadedEventLoopGroup has been replaced with NIOTSEventLoopGroup. It's recommended by authors of grpc-swift especially for iOS platform and it allows us to set the priority because NIOTSEventLoopGroup is GCD based while MultiThreadedEventLoopGroup is pthread based. [#541] Initialise gRPC on a separate thread (#545) - priority increased to .default --- .../ZcashLightClientKit/Service/LightWalletGRPCService.swift | 5 +++-- Tests/TestUtils/Tests+Utils.swift | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift index b023a30d..3dbcfbbc 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -10,6 +10,7 @@ import Foundation import GRPC import NIO import NIOHPACK +import NIOTransportServices public typealias Channel = GRPC.GRPCChannel @@ -127,8 +128,8 @@ public class LightWalletGRPCService { self.singleCallTimeout = TimeLimit.timeout(.milliseconds(singleCallTimeout)) let connectionBuilder = secure ? - ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1)) : - ClientConnection.insecure(group: MultiThreadedEventLoopGroup(numberOfThreads: 1)) + ClientConnection.usingPlatformAppropriateTLS(for: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) : + ClientConnection.insecure(group: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) let channel = connectionBuilder .withConnectivityStateDelegate(connectionManager, executingOn: queue) diff --git a/Tests/TestUtils/Tests+Utils.swift b/Tests/TestUtils/Tests+Utils.swift index 4d110bc1..d86f11e0 100644 --- a/Tests/TestUtils/Tests+Utils.swift +++ b/Tests/TestUtils/Tests+Utils.swift @@ -11,6 +11,7 @@ import GRPC import ZcashLightClientKit import XCTest import NIO +import NIOTransportServices enum Environment { static let lightwalletdKey = "LIGHTWALLETD_ADDRESS" @@ -40,8 +41,8 @@ class ChannelProvider { let endpoint = LightWalletEndpointBuilder.default let connectionBuilder = secure ? - ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1)) : - ClientConnection.insecure(group: MultiThreadedEventLoopGroup(numberOfThreads: 1)) + ClientConnection.usingPlatformAppropriateTLS(for: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) : + ClientConnection.insecure(group: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) let channel = connectionBuilder .withKeepalive( From 9b6ff51b297d938d8894acc53dc3b3413828b26e Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 4 Oct 2022 01:05:11 +0200 Subject: [PATCH 3/3] [#492] Get rid of blocking API (#551) - blocking API removed, only latestBlockHeight() stayed - non-blocking closure based API removed - unit tests updated to use async API [#492] Get rid of blocking API (#551) - forgotten commented code cleaned up - some comments were still mentioning result (completion closure), removed --- .../DatabaseStorage/CompactBlockStorage.swift | 6 +- .../Block/Downloader/BlockDownloader.swift | 89 +----- .../Processor/CompactBlockDownload.swift | 8 +- .../Processor/CompactBlockEnhancement.swift | 2 +- .../Processor/CompactBlockProcessor.swift | 110 +++---- .../Repository/CompactBlockRepository.swift | 11 +- .../Service/LightWalletGRPCService.swift | 292 +----------------- .../Service/LightWalletService.swift | 89 +----- .../Synchronizer/SDKSynchronizer.swift | 29 +- .../PersistentTransactionManager.swift | 2 +- .../DarksideTests/BlockDownloaderTests.swift | 31 +- Tests/NetworkTests/BlockStreamingTest.swift | 30 +- .../LightWalletServiceTests.swift | 51 --- .../CompactBlockStorageTests.swift | 24 +- Tests/TestUtils/DarkSideWalletService.swift | 97 +----- Tests/TestUtils/FakeService.swift | 97 +----- Tests/TestUtils/FakeStorage.swift | 6 +- Tests/TestUtils/Stubs.swift | 42 +-- Tests/TestUtils/TestDbBuilder.swift | 4 +- 19 files changed, 141 insertions(+), 879 deletions(-) diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift index 3afb6d3f..c0f7b214 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift @@ -91,12 +91,8 @@ extension CompactBlockStorage: CompactBlockRepository { } return try await task.value } - - func write(blocks: [ZcashCompactBlock]) throws { - try insert(blocks) - } - func writeAsync(blocks: [ZcashCompactBlock]) async throws { + func write(blocks: [ZcashCompactBlock]) async throws { let task = Task(priority: .userInitiated) { try insert(blocks) } diff --git a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift index d6bc7b3a..e6349df1 100644 --- a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift +++ b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift @@ -16,18 +16,11 @@ 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 */ - func downloadBlockRangeAsync(_ heightRange: CompactBlockRange) async throws + func downloadBlockRange(_ heightRange: CompactBlockRange) async throws /** Restore the download progress up to the given height. @@ -69,36 +62,10 @@ public protocol CompactBlockDownloading { /** Gets the transaction for the Id given - Parameter txId: Data representing the transaction Id - - Returns: a transaction entity with the requested transaction - - Throws: An error if the fetch failed */ - func fetchTransaction(txId: Data) throws -> TransactionEntity - - /** - Gets the transaction for the Id given - - Parameter txId: Data representing the transaction Id - */ - 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 fetchTransaction(txId: Data) async throws -> TransactionEntity func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream - - func fetchUnspentTransactionOutputs(tAddresses: [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( - tAddresses: [String], - startHeight: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void - ) func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) -> AsyncThrowingStream @@ -127,45 +94,11 @@ extension CompactBlockDownloader: CompactBlockDownloading { func closeConnection() { lightwalletService.closeConnection() } - - func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - try lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight) - } - - func fetchUnspentTransactionOutputs( - tAddresses: [String], - startHeight: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void - ) { - lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight) { fetchResult in - switch fetchResult { - case .success(let utxos): - result(.success(utxos)) - case .failure(let error): - result(.failure(error)) - } - } - } 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) - } - - func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) { - lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) { fetchResult in - switch fetchResult { - case .success(let utxos): - result(.success(utxos)) - case .failure(let error): - result(.failure(error)) - } - } - } - func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream { lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) } @@ -177,20 +110,15 @@ extension CompactBlockDownloader: CompactBlockDownloading { func latestBlockHeight() throws -> BlockHeight { try lightwalletService.latestBlockHeight() } - - func downloadBlockRange(_ range: CompactBlockRange) throws { - let blocks = try lightwalletService.blockRange(range) - try storage.write(blocks: blocks) - } - func downloadBlockRangeAsync( _ heightRange: CompactBlockRange) async throws { + func downloadBlockRange( _ 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) + try await self.storage.write(blocks: compactBlocks) } catch { throw error } @@ -213,7 +141,6 @@ extension CompactBlockDownloader: CompactBlockDownloading { } } - func rewind(to height: BlockHeight) throws { try self.storage.rewind(to: height) } @@ -222,11 +149,7 @@ extension CompactBlockDownloader: CompactBlockDownloading { try self.storage.latestHeight() } - func fetchTransaction(txId: Data) throws -> TransactionEntity { - try lightwalletService.fetchTransaction(txId: txId) - } - - func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { - try await lightwalletService.fetchTransactionAsync(txId: txId) + func fetchTransaction(txId: Data) async throws -> TransactionEntity { + try await lightwalletService.fetchTransaction(txId: txId) } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift index db6cd260..4a78030e 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift @@ -42,8 +42,7 @@ extension CompactBlockProcessor { try Task.checkCancellation() buffer.append(zcashCompactBlock) if buffer.count >= blockBufferSize { - // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact - try storage.write(blocks: buffer) + try await storage.write(blocks: buffer) buffer.removeAll(keepingCapacity: true) } @@ -54,8 +53,7 @@ extension CompactBlockProcessor { ) notifyProgress(.download(progress)) } - // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact - try storage.write(blocks: buffer) + try await storage.write(blocks: buffer) buffer.removeAll(keepingCapacity: true) } catch { guard let err = error as? LightWalletServiceError, case .userCancelled = err else { @@ -73,7 +71,7 @@ extension CompactBlockProcessor { try Task.checkCancellation() do { - try await downloader.downloadBlockRangeAsync(range) + try await downloader.downloadBlockRange(range) } catch { throw error } diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift index 29b921ea..cb284414 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift @@ -18,7 +18,7 @@ extension CompactBlockProcessor { private func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") - let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) + let transaction = try await downloader.fetchTransaction(txId: transaction.transactionId) let transactionID = transaction.transactionId.toHexStringTxId() let block = String(describing: transaction.minedHeight) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index d2767c80..fe30bf6d 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -598,31 +598,23 @@ public class CompactBlockProcessor { } func validateServer(completionBlock: @escaping (() -> Void)) { - self.service.getInfo(result: { [weak self] result in - guard let self = self else { return } - - switch result { - case .success(let info): - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - do { - try Self.validateServerInfo( - info, - saplingActivation: self.config.saplingActivation, - localNetwork: self.config.network, - rustBackend: self.rustBackend - ) - completionBlock() - } catch { - self.severeFailure(error) - } - } - case .failure(let error): + Task { @MainActor in + do { + let info = try await self.service.getInfo() + try Self.validateServerInfo( + info, + saplingActivation: self.config.saplingActivation, + localNetwork: self.config.network, + rustBackend: self.rustBackend + ) + completionBlock() + } catch let error as LightWalletServiceError { self.severeFailure(error.mapToProcessorError()) + } catch { + self.severeFailure(error) } - }) + } } - /// Processes new blocks on the given range based on the configuration set for this instance func processNewBlocks(range: CompactBlockRange) { @@ -1182,51 +1174,39 @@ extension CompactBlockProcessor { rustBackend: ZcashRustBackendWelding.Type ) async throws -> NextState { let task = Task(priority: .userInitiated) { - // TODO: refactor to async call, issue 463, PR 493 - // https://github.com/zcash/ZcashLightClientKit/issues/463 - try nextState( - service: service, - downloader: downloader, - config: config, - rustBackend: rustBackend - ) + do { + let info = try await service.getInfo() + + try CompactBlockProcessor.validateServerInfo( + info, + saplingActivation: config.saplingActivation, + localNetwork: config.network, + rustBackend: rustBackend + ) + + // get latest block height + let latestDownloadedBlockHeight: BlockHeight = max(config.walletBirthday, try downloader.lastDownloadedBlockHeight()) + + let latestBlockheight = try service.latestBlockHeight() + + if latestDownloadedBlockHeight < latestBlockheight { + return NextState.processNewBlocks( + range: CompactBlockProcessor.nextBatchBlockRange( + latestHeight: latestBlockheight, + latestDownloadedHeight: latestDownloadedBlockHeight, + walletBirthday: config.walletBirthday + ) + ) + } else if latestBlockheight == latestDownloadedBlockHeight { + return .finishProcessing(height: latestBlockheight) + } + + return .wait(latestHeight: latestBlockheight, latestDownloadHeight: latestBlockheight) + } catch { + throw error + } } return try await task.value } - - static func nextState( - service: LightWalletService, - downloader: CompactBlockDownloading, - config: Configuration, - rustBackend: ZcashRustBackendWelding.Type - ) throws -> NextState { - let info = try service.getInfo() - - try CompactBlockProcessor.validateServerInfo( - info, - saplingActivation: config.saplingActivation, - localNetwork: config.network, - rustBackend: rustBackend - ) - - // get latest block height - let latestDownloadedBlockHeight: BlockHeight = max(config.walletBirthday, try downloader.lastDownloadedBlockHeight()) - - let latestBlockheight = try service.latestBlockHeight() - - if latestDownloadedBlockHeight < latestBlockheight { - return .processNewBlocks( - range: CompactBlockProcessor.nextBatchBlockRange( - latestHeight: latestBlockheight, - latestDownloadedHeight: latestDownloadedBlockHeight, - walletBirthday: config.walletBirthday - ) - ) - } else if latestBlockheight == latestDownloadedBlockHeight { - return .finishProcessing(height: latestBlockheight) - } - - return .wait(latestHeight: latestBlockheight, latestDownloadHeight: latestBlockheight) - } } } diff --git a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift index 94473b52..9621369f 100644 --- a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift +++ b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift @@ -31,21 +31,14 @@ protocol CompactBlockRepository { */ 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 - - Parameter blocks: the compact blocks that will be written to storage - - Throws: an error when there's a failure - */ - func write(blocks: [ZcashCompactBlock]) throws - /** Write the given blocks to this store, which may be anything from an in-memory cache to a DB. Non-Blocking - Parameters: - Parameter blocks: array of blocks to be written to storage + - Throws: an error when there's a failure */ - func writeAsync(blocks: [ZcashCompactBlock]) async throws + func write(blocks: [ZcashCompactBlock]) async throws /** Remove every block above and including the given height. diff --git a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift index 3dbcfbbc..d3f8828d 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -189,11 +189,11 @@ public class LightWalletGRPCService { } } -// MARK: - LightWalletServiceBlockingAPI +// MARK: - LightWalletService -extension LightWalletGRPCService: LightWalletServiceBlockingAPI { - public func getInfo() throws -> LightWalletdInfo { - try compactTxStreamer.getLightdInfo(Empty()).response.wait() +extension LightWalletGRPCService: LightWalletService { + public func getInfo() async throws -> LightWalletdInfo { + try await compactTxStreamerAsync.getLightdInfo(Empty()) } public func latestBlockHeight() throws -> BlockHeight { @@ -202,161 +202,13 @@ extension LightWalletGRPCService: LightWalletServiceBlockingAPI { } return height } - - public func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { - var blocks: [CompactBlock] = [] - - let response = compactTxStreamer.getBlockRange( - range.blockRange(), - handler: { blocks.append($0) } - ) - - let status = try response.status.wait() - switch status.code { - case .ok: - return blocks.asZcashCompactBlocks() - default: - throw LightWalletServiceError.mapCode(status) - } - } - - 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 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 fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - let arg = GetAddressUtxosArg.with { utxoArgs in - utxoArgs.addresses = [tAddress] - utxoArgs.startHeight = UInt64(height) - } - do { - return try self.compactTxStreamer.getAddressUtxos(arg).response.wait().addressUtxos.map { reply in - UTXO( - id: nil, - address: tAddress, - prevoutTxId: reply.txid, - prevoutIndex: Int(reply.index), - script: reply.script, - valueZat: Int(reply.valueZat), - height: Int(reply.height), - spentInTx: nil - ) - } - } catch { - throw error.mapToServiceError() - } - } - - 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())) - } + let blockID = try await compactTxStreamerAsync.getLatestBlock(ChainSpec()) + guard let blockHeight = Int(exactly: blockID.height) else { + throw LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)") } + return blockHeight } public func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { @@ -376,48 +228,16 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } } - public func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { + public func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse { 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) + let transaction = RawTransaction.with { $0.data = 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 { + public func fetchTransaction(txId: Data) async throws -> TransactionEntity { var txFilter = TxFilter() txFilter.hash = txId @@ -473,52 +293,6 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { return fetchUTXOs(for: [tAddress], height: height) } - public func fetchUTXOs( - for tAddresses: [String], - height: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) { - guard !tAddresses.isEmpty else { - return result(.success([])) // FIXME: throw a real error - } - - var utxos: [UnspentTransactionOutputEntity] = [] - self.queue.async { [weak self] in - guard let self = self else { return } - let args = GetAddressUtxosArg.with { utxoArgs in - utxoArgs.addresses = tAddresses - utxoArgs.startHeight = UInt64(height) - } - do { - let response = try self.compactTxStreamer.getAddressUtxosStream(args) { reply in - utxos.append( - 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 - ) - ) - } - .status - .wait() - - switch response.code { - case .ok: - result(.success(utxos)) - default: - result(.failure(.mapCode(response))) - } - } catch { - result(.failure(error.mapToServiceError())) - } - } - } - public func fetchUTXOs( for tAddresses: [String], height: BlockHeight @@ -558,48 +332,6 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } } - @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 @@ -625,9 +357,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } } } -} -extension LightWalletGRPCService: LightWalletService { public func closeConnection() { _ = channel.close() } diff --git a/Sources/ZcashLightClientKit/Service/LightWalletService.swift b/Sources/ZcashLightClientKit/Service/LightWalletService.swift index ce044c3e..d82477ce 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletService.swift @@ -101,117 +101,40 @@ public protocol LightWalletServiceResponse { extension SendResponse: LightWalletServiceResponse {} -/// Blocking API - used for the testing purposes -public protocol LightWalletServiceBlockingAPI { - /// Returns the info for this lightwalletd server (blocking) - func getInfo() throws -> LightWalletdInfo +public protocol LightWalletService { + /// Returns the info for this lightwalletd server + func getInfo() async throws -> LightWalletdInfo - /// /// Return the latest block height known to the service. - /// - Parameter result: a result containing the height or an Error + /// Blocking API 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. - 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 + func submit(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 + func fetchTransaction(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, - endHeight: BlockHeight, - result: @escaping (Result) -> Void, - handler: @escaping (ZcashCompactBlock) -> Void, - progress: @escaping (BlockProgress) -> Void - ) -> CancellableCall - func blockStream( startHeight: BlockHeight, endHeight: BlockHeight ) -> AsyncThrowingStream -} -public protocol LightWalletService: LightWalletServiceNonBlockingAPI, LightWalletServiceBlockingAPI { func closeConnection() } diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 2cf4053a..2512441b 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -585,26 +585,23 @@ public class SDKSynchronizer: Synchronizer { try blockProcessor.downloader.latestBlockHeight() } - public func latestUTXOs(address: String, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) { + public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] { guard initializer.isValidTransparentAddress(address) else { - result(.failure(SynchronizerError.generalError(message: "invalid t-address"))) - return + throw SynchronizerError.generalError(message: "invalid t-address") } - initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight) { [weak self] fetchResult in - guard let self = self else { return } - switch fetchResult { - case .success(let utxos): - do { - try self.utxoRepository.clearAll(address: address) - try self.utxoRepository.store(utxos: utxos) - result(.success(utxos)) - } catch { - result(.failure(SynchronizerError.generalError(message: "\(error)"))) - } - case .failure(let error): - result(.failure(SynchronizerError.connectionFailed(message: error))) + let stream = initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight) + + do { + var utxos: [UnspentTransactionOutputEntity] = [] + for try await transactionEntity in stream { + utxos.append(transactionEntity) } + try self.utxoRepository.clearAll(address: address) + try self.utxoRepository.store(utxos: utxos) + return utxos + } catch { + throw SynchronizerError.generalError(message: "\(error)") } } diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index 8cae2b8a..ef80589c 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -179,7 +179,7 @@ class PersistentTransactionManager: OutboundTransactionManager { throw TransactionManagerError.internalInconsistency(storedTx) } - let response = try self.service.submit(spendTransaction: raw) + let response = try await self.service.submit(spendTransaction: raw) let transaction = try self.update(transaction: storedTx, on: response) guard response.errorCode >= 0 else { diff --git a/Tests/DarksideTests/BlockDownloaderTests.swift b/Tests/DarksideTests/BlockDownloaderTests.swift index b89eee9f..b78ed5e1 100644 --- a/Tests/DarksideTests/BlockDownloaderTests.swift +++ b/Tests/DarksideTests/BlockDownloaderTests.swift @@ -46,7 +46,7 @@ class BlockDownloaderTests: XCTestCase { let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) do { - try await downloader.downloadBlockRangeAsync(range) + try await downloader.downloadBlockRange(range) // check what was 'stored' let latestHeight = try await self.storage.latestHeightAsync() @@ -59,33 +59,6 @@ class BlockDownloaderTests: XCTestCase { } } - func testSmallDownload() { - let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight - let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99 - - let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) - var latest: BlockHeight = 0 - - do { - latest = try downloader.lastDownloadedBlockHeight() - } catch { - XCTFail(error.localizedDescription) - } - - XCTAssertEqual(latest, BlockHeight.empty()) - XCTAssertNoThrow(try downloader.downloadBlockRange(range)) - - var currentLatest: BlockHeight = 0 - do { - currentLatest = try downloader.lastDownloadedBlockHeight() - } catch { - XCTFail("latest block failed") - return - } - - XCTAssertEqual(currentLatest, upperRange ) - } - func testFailure() async { let awfulDownloader = CompactBlockDownloader( service: AwfulLightWalletService( @@ -101,7 +74,7 @@ class BlockDownloaderTests: XCTestCase { let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) do { - try await awfulDownloader.downloadBlockRangeAsync(range) + try await awfulDownloader.downloadBlockRange(range) } catch { XCTAssertNotNil(error) } diff --git a/Tests/NetworkTests/BlockStreamingTest.swift b/Tests/NetworkTests/BlockStreamingTest.swift index 9d509a43..d174fe77 100644 --- a/Tests/NetworkTests/BlockStreamingTest.swift +++ b/Tests/NetworkTests/BlockStreamingTest.swift @@ -21,9 +21,7 @@ class BlockStreamingTest: XCTestCase { try? FileManager.default.removeItem(at: __dataDbURL()) } - func testStream() throws { - let expectation = XCTestExpectation(description: "blockstream expectation") - + func testStream() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -36,23 +34,19 @@ class BlockStreamingTest: XCTestCase { let startHeight = latestHeight - 100_000 var blocks: [ZcashCompactBlock] = [] - service.blockStream(startHeight: startHeight, endHeight: latestHeight) { result in - expectation.fulfill() - switch result { - case .success(let status): - XCTAssertEqual(GRPCResult.success, status) - case .failure(let error): - XCTFail("failed with error: \(error)") + let stream = service.blockStream(startHeight: startHeight, endHeight: latestHeight) + + do { + for try await compactBlock in stream { + print("received block \(compactBlock.height)") + blocks.append(compactBlock) + print("progressHeight: \(compactBlock.height)") + print("startHeight: \(startHeight)") + print("targetHeight: \(latestHeight)") } - } handler: { compactBlock in - print("received block \(compactBlock.height)") - blocks.append(compactBlock) - } progress: { progressReport in - print("progressHeight: \(progressReport.progressHeight)") - print("startHeight: \(progressReport.startHeight)") - print("targetHeight: \(progressReport.targetHeight)") + } catch { + XCTFail("failed with error: \(error)") } - wait(for: [expectation], timeout: 1000) } func testStreamCancellation() async throws { diff --git a/Tests/NetworkTests/LightWalletServiceTests.swift b/Tests/NetworkTests/LightWalletServiceTests.swift index fe23718c..affd3232 100644 --- a/Tests/NetworkTests/LightWalletServiceTests.swift +++ b/Tests/NetworkTests/LightWalletServiceTests.swift @@ -38,29 +38,6 @@ class LightWalletServiceTests: XCTestCase { // wait(for: [expect], timeout: 20) // } - func testHundredBlocks() { - let expect = XCTestExpectation(description: self.description) - let count = 99 - let lowerRange: BlockHeight = network.constants.saplingActivationHeight - let upperRange: BlockHeight = network.constants.saplingActivationHeight + count - let blockRange = lowerRange ... upperRange - - service.blockRange(blockRange) { result in - expect.fulfill() - switch result { - case .failure(let error): - XCTFail("failed with error \(error)") - - case .success(let blocks): - XCTAssertEqual(blocks.count, blockRange.count) - XCTAssertEqual(blocks[0].height, lowerRange) - XCTAssertEqual(blocks.last!.height, upperRange) - } - } - - wait(for: [expect], timeout: 10) - } - func testHundredBlocks() async throws { let count = 99 let lowerRange: BlockHeight = network.constants.saplingActivationHeight @@ -76,19 +53,6 @@ class LightWalletServiceTests: XCTestCase { XCTAssertEqual(blocks.last!.height, upperRange) } - func testSyncBlockRange() { - let lowerRange: BlockHeight = network.constants.saplingActivationHeight - let upperRange: BlockHeight = network.constants.saplingActivationHeight + 99 - let blockRange = lowerRange ... upperRange - - do { - let blocks = try service.blockRange(blockRange) - XCTAssertEqual(blocks.count, blockRange.count) - } catch { - XCTFail("\(error)") - } - } - func testSyncBlockRange() async throws { let lowerRange: BlockHeight = network.constants.saplingActivationHeight let upperRange: BlockHeight = network.constants.saplingActivationHeight + 99 @@ -101,21 +65,6 @@ class LightWalletServiceTests: XCTestCase { XCTAssertEqual(blocks.count, blockRange.count) } - func testLatestBlock() { - let expect = XCTestExpectation(description: self.description) - service.latestBlockHeight { result in - expect.fulfill() - switch result { - case .failure(let e): - XCTFail("error: \(e)") - case .success(let height): - XCTAssertTrue(height > self.network.constants.saplingActivationHeight) - } - } - - 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/OfflineTests/CompactBlockStorageTests.swift b/Tests/OfflineTests/CompactBlockStorageTests.swift index 74d26887..e4c8cb18 100644 --- a/Tests/OfflineTests/CompactBlockStorageTests.swift +++ b/Tests/OfflineTests/CompactBlockStorageTests.swift @@ -25,14 +25,14 @@ class CompactBlockStorageTests: XCTestCase { XCTAssertEqual(latestHeight, BlockHeight.empty()) } - func testStoreThousandBlocks() { + func testStoreThousandBlocks() async { let initialHeight = try! compactBlockDao.latestHeight() let startHeight = self.network.constants.saplingActivationHeight let blockCount = Int(1_000) let finalHeight = startHeight + blockCount do { - try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) } catch { XCTFail("seed faild with error: \(error)") return @@ -49,14 +49,14 @@ class CompactBlockStorageTests: XCTestCase { let blockCount = Int(1_000) let finalHeight = startHeight + blockCount - try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) let latestHeight = try await compactBlockDao.latestHeightAsync() XCTAssertNotEqual(initialHeight, latestHeight) XCTAssertEqual(latestHeight, finalHeight) } - func testStoreOneBlockFromEmpty() { + func testStoreOneBlockFromEmpty() async { let initialHeight = try! compactBlockDao.latestHeight() guard initialHeight == BlockHeight.empty() else { XCTFail("database not empty, latest height: \(initialHeight)") @@ -68,7 +68,11 @@ class CompactBlockStorageTests: XCTestCase { XCTFail("could not create randem block with height: \(expectedHeight)") return } - XCTAssertNoThrow(try compactBlockDao.write(blocks: [block])) + do { + try await compactBlockDao.write(blocks: [block]) + } catch { + XCTFail("unexpected testStoreOneBlockFromEmpty fail") + } do { let result = try compactBlockDao.latestHeight() @@ -91,27 +95,27 @@ class CompactBlockStorageTests: XCTestCase { XCTFail("could not create randem block with height: \(expectedHeight)") return } - try await compactBlockDao.writeAsync(blocks: [block]) + try await compactBlockDao.write(blocks: [block]) let result = try await compactBlockDao.latestHeightAsync() XCTAssertEqual(result, expectedHeight) } - func testRewindTo() { + func testRewindTo() async { let startHeight = self.network.constants.saplingActivationHeight let blockCount = Int(1_000) let finalHeight = startHeight + blockCount do { - try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) } catch { XCTFail("seed faild with error: \(error)") return } let rewindHeight = BlockHeight(finalHeight - 233) - XCTAssertNoThrow(try compactBlockDao.rewind(to: rewindHeight)) do { + try await compactBlockDao.rewindAsync(to: rewindHeight) let latestHeight = try compactBlockDao.latestHeight() XCTAssertEqual(latestHeight, rewindHeight - 1) } catch { @@ -124,7 +128,7 @@ class CompactBlockStorageTests: XCTestCase { let blockCount = Int(1_000) let finalHeight = startHeight + blockCount - try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) let rewindHeight = BlockHeight(finalHeight - 233) try await compactBlockDao.rewindAsync(to: rewindHeight) diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index b92f0035..121117d7 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -58,105 +58,25 @@ class DarksideWalletService: LightWalletService { self.init(endpoint: LightWalletEndpointBuilder.default) } - @discardableResult - func blockStream( - startHeight: BlockHeight, - endHeight: BlockHeight, - result: @escaping (Result) -> Void, - handler: @escaping (ZcashCompactBlock) -> Void, - progress: @escaping (BlockProgress) -> Void - ) -> CancellableCall { - return service.blockStream( - startHeight: startHeight, - endHeight: endHeight, - result: result, - handler: handler, - progress: progress - ) - } - func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream { service.blockStream(startHeight: startHeight, endHeight: endHeight) } - - func getInfo() throws -> LightWalletdInfo { - try service.getInfo() - } - - func getInfo(result: @escaping (Result) -> Void) { - service.getInfo(result: result) - } - - func closeConnection() { - } - - func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - 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( - for tAddress: String, - height: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) { - service.fetchUTXOs(for: tAddress, height: height, result: result) + func closeConnection() { } func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream { service.fetchUTXOs(for: tAddress, height: height) } - - func fetchUTXOs( - for tAddresses: [String], - height: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) { - 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) - } - - func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { - service.fetchTransaction(txId: txId, result: result) - } - - func latestBlockHeight(result: @escaping (Result) -> Void) { - service.latestBlockHeight(result: result) - } - func latestBlockHeight() throws -> BlockHeight { try service.latestBlockHeight() } - func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { - service.blockRange(range, result: result) - } - - func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { - try service.blockRange(range) - } - - /** - Darskside lightwalletd should do a fake submission, by sending over the tx, retrieving it and including it in a new block - */ - func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { - service.submit(spendTransaction: spendTransaction, result: result) - } - - func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { - try service.submit(spendTransaction: spendTransaction) - } - func useDataset(_ datasetUrl: String) throws { try useDataset(from: datasetUrl) } @@ -234,10 +154,10 @@ class DarksideWalletService: LightWalletService { _ = try darksideService.clearAddressUtxo(Empty(), callOptions: nil).response.wait() } - func getInfoAsync() async throws -> LightWalletdInfo { - try service.getInfo() + func getInfo() async throws -> LightWalletdInfo { + try await service.getInfo() } - + func latestBlockHeightAsync() async throws -> BlockHeight { try service.latestBlockHeight() } @@ -246,12 +166,13 @@ class DarksideWalletService: LightWalletService { service.blockRange(range) } - func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse { - try service.submit(spendTransaction: spendTransaction) + /// Darskside lightwalletd should do a fake submission, by sending over the tx, retrieving it and including it in a new block + func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse { + try await service.submit(spendTransaction: spendTransaction) } - func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { - try service.fetchTransaction(txId: txId) + func fetchTransaction(txId: Data) async throws -> TransactionEntity { + try await service.fetchTransaction(txId: txId) } } diff --git a/Tests/TestUtils/FakeService.swift b/Tests/TestUtils/FakeService.swift index 39c60bdc..db061395 100644 --- a/Tests/TestUtils/FakeService.swift +++ b/Tests/TestUtils/FakeService.swift @@ -24,67 +24,17 @@ class MockLightWalletService: LightWalletService { var mockLightDInfo: LightWalletdInfo? var queue = DispatchQueue(label: "mock service queue") - @discardableResult - func blockStream( - startHeight: BlockHeight, - endHeight: BlockHeight, - result: @escaping (Result) -> Void, - handler: @escaping (ZcashCompactBlock) -> Void, - progress: @escaping (BlockProgress) -> Void - ) -> CancellableCall { - 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") - } - return info - } - - func getInfo(result: @escaping (Result) -> Void) { - queue.async { [weak self] in - guard let info = self?.mockLightDInfo else { - result(.failure(LightWalletServiceError.generalError(message: "Not Implemented"))) - return - } - result(.success(info)) - } - } - func closeConnection() { } - - func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] { - [] - } - - 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 tAddress: String, height: BlockHeight) -> AsyncThrowingStream { AsyncThrowingStream { _ in } } - func fetchUTXOs( - for tAddresses: [String], - height: BlockHeight, - result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void - ) { - } - func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream { AsyncThrowingStream { _ in } } @@ -98,48 +48,17 @@ class MockLightWalletService: LightWalletService { self.service = service } - func latestBlockHeight(result: @escaping (Result) -> Void) { - DispatchQueue.global().asyncAfter(deadline: .now() + 1) { - result(.success(self.latestHeight)) - } - } - func latestBlockHeight() throws -> BlockHeight { return self.latestHeight } - func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { - self.service.blockRange(range, result: result) - } - - func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { - try self.service.blockRange(range) - } - - func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 1) { - result(.success(LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage()))) - } - } - - func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { - return LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage()) - } - - func fetchTransaction(txId: Data) throws -> TransactionEntity { - Transaction(id: 1, transactionId: Data(), created: "Today", transactionIndex: 1, expiryHeight: -1, minedHeight: -1, raw: nil) - } - - func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { - } - - func getInfoAsync() async throws -> LightWalletdInfo { + func getInfo() async throws -> LightWalletdInfo { guard let info = mockLightDInfo else { throw LightWalletServiceError.generalError(message: "Not Implemented") } return info } - + func latestBlockHeightAsync() async throws -> BlockHeight { latestHeight } @@ -148,19 +67,11 @@ class MockLightWalletService: LightWalletService { service.blockRange(range) } - func submitAsync(spendTransaction: Data) async throws -> LightWalletServiceResponse { + func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse { LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage()) } - func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity { + func fetchTransaction(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] { - [] - } } diff --git a/Tests/TestUtils/FakeStorage.swift b/Tests/TestUtils/FakeStorage.swift index 1f7613fb..108f5efc 100644 --- a/Tests/TestUtils/FakeStorage.swift +++ b/Tests/TestUtils/FakeStorage.swift @@ -14,7 +14,7 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { latestBlockHeight } - func writeAsync(blocks: [ZcashCompactBlock]) async throws { + func write(blocks: [ZcashCompactBlock]) async throws { fakeSave(blocks: blocks) } @@ -26,10 +26,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { return self.latestBlockHeight } - func write(blocks: [ZcashCompactBlock]) throws { - fakeSave(blocks: blocks) - } - func rewind(to height: BlockHeight) throws { fakeRewind(to: height) } diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 4dc044c2..4be67d74 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -17,52 +17,26 @@ class AwfulLightWalletService: MockLightWalletService { throw LightWalletServiceError.criticalError } - override func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { + override func latestBlockHeightAsync() async throws -> BlockHeight { throw LightWalletServiceError.invalidBlock } - override func latestBlockHeight(result: @escaping (Result) -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.failure(LightWalletServiceError.invalidBlock)) - } - } - - override func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.failure(LightWalletServiceError.invalidBlock)) - } - } - 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)) - } - } - - /** - Submits a raw transaction over lightwalletd. Blocking - */ - override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { + /// Submits a raw transaction over lightwalletd. + override func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse { throw LightWalletServiceError.invalidBlock } } -class SlightlyBadLightWalletService: MockLightWalletService { - override func submit(spendTransaction: Data, result: @escaping(Result) -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.success(LightWalletServiceMockResponse.error)) - } - } +extension LightWalletServiceMockResponse: Error { } - /** - Submits a raw transaction over lightwalletd. Blocking - */ - override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { - LightWalletServiceMockResponse.error +class SlightlyBadLightWalletService: MockLightWalletService { + /// Submits a raw transaction over lightwalletd. + override func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse { + throw LightWalletServiceMockResponse.error } } diff --git a/Tests/TestUtils/TestDbBuilder.swift b/Tests/TestUtils/TestDbBuilder.swift index 2317b469..876257c9 100644 --- a/Tests/TestUtils/TestDbBuilder.swift +++ b/Tests/TestUtils/TestDbBuilder.swift @@ -92,12 +92,12 @@ class TestDbBuilder { return ReceivedNotesSQLDAO(dbProvider: provider) } - static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) throws { + static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) async throws { guard let blocks = StubBlockCreator.createBlockRange(blockRange) else { throw TestBuilderError.generalError } - try db.write(blocks: blocks) + try await db.write(blocks: blocks) } }