diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift index 9a2166a9..3afb6d3f 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift @@ -85,39 +85,28 @@ extension CompactBlockStorage: CompactBlockRepository { try latestBlockHeight() } - func latestHeight(result: @escaping (Swift.Result) -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - do { - result(.success(try self.latestBlockHeight())) - } catch { - result(.failure(error)) - } + func latestHeightAsync() async throws -> BlockHeight { + let task = Task(priority: .userInitiated) { + try latestBlockHeight() } + return try await task.value } - + func write(blocks: [ZcashCompactBlock]) throws { try insert(blocks) } - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) { - DispatchQueue.global(qos: .userInitiated).async { - do { - try self.insert(blocks) - completion?(nil) - } catch { - completion?(error) - } + func writeAsync(blocks: [ZcashCompactBlock]) async throws { + let task = Task(priority: .userInitiated) { + try insert(blocks) } + try await task.value } - - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) { - DispatchQueue.global(qos: .userInitiated).async { - do { - try self.rewind(to: height) - completion?(nil) - } catch { - completion?(error) - } + + func rewindAsync(to height: BlockHeight) async throws { + let task = Task(priority: .userInitiated) { + try rewind(to: height) } + try await task.value } } diff --git a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift index f6da6a85..9511d63a 100644 --- a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift +++ b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift @@ -16,6 +16,13 @@ enum CompactBlockDownloadError: Error { Represents what a compact block downloaded should provide to its clients */ public protocol CompactBlockDownloading { + + /** + Downloads and stores the given block range. + Blocking + */ + func downloadBlockRange(_ range: CompactBlockRange) throws + /** Downloads and stores the given block range. Non-Blocking @@ -46,12 +53,6 @@ public protocol CompactBlockDownloading { */ func latestBlockHeight(result: @escaping (Result) -> Void) - /** - Downloads and stores the given block range. - Blocking - */ - func downloadBlockRange(_ range: CompactBlockRange) throws - /** Restore the download progress up to the given height. */ @@ -176,18 +177,17 @@ extension CompactBlockDownloader: CompactBlockDownloading { _ heightRange: CompactBlockRange, completion: @escaping (Error?) -> Void ) { - lightwalletService.blockRange(heightRange) { [weak self] result in - guard let self = self else { - return - } - - switch result { - case .failure(let error): - completion(error) - case .success(let compactBlocks): - self.storage.write(blocks: compactBlocks) { storeError in - completion(storeError) + let stream: AsyncThrowingStream = lightwalletService.blockRange(heightRange) + Task { + do { + var compactBlocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + compactBlocks.append(compactBlock) } + try await self.storage.writeAsync(blocks: compactBlocks) + completion(nil) + } catch { + completion(error) } } } @@ -198,20 +198,25 @@ extension CompactBlockDownloader: CompactBlockDownloading { } func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) { - storage.rewind(to: height) { e in - completion(e) + Task { + do { + try await storage.rewindAsync(to: height) + completion(nil) + } catch { + completion(error) + } } } func lastDownloadedBlockHeight(result: @escaping (Result) -> Void) { - storage.latestHeight { heightResult in - switch heightResult { - case .failure(let e): - result(.failure(CompactBlockDownloadError.generalError(error: e))) - return - case .success(let height): - result(.success(height)) + Task { + do { + let latestHeight = try await storage.latestHeightAsync() + result(.success(latestHeight)) + } catch { + result(.failure(CompactBlockDownloadError.generalError(error: error))) } + } } diff --git a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift index caddd86f..94473b52 100644 --- a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift +++ b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift @@ -28,11 +28,9 @@ protocol CompactBlockRepository { /** Gets the highest block that is currently stored. Non-Blocking - - - Parameter result: closure resulting on either the latest height or an error */ - func latestHeight(result: @escaping (Result) -> Void) - + func latestHeightAsync() async throws -> BlockHeight + /** Write the given blocks to this store, which may be anything from an in-memory cache to a DB. Blocking @@ -46,10 +44,9 @@ protocol CompactBlockRepository { Non-Blocking - Parameters: - Parameter blocks: array of blocks to be written to storage - - Parameter completion: a closure that will be called after storing the blocks */ - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) - + func writeAsync(blocks: [ZcashCompactBlock]) async throws + /** Remove every block above and including the given height. @@ -66,10 +63,7 @@ protocol CompactBlockRepository { After this operation, the data store will look the same as one that has not yet stored the given block height. Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49. - - Parameters: - Parameter height: the height to rewind to - - Parameter completion: a closure that will be called after storing the blocks - */ - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) + func rewindAsync(to height: BlockHeight) async throws } diff --git a/Tests/DarksideTests/BlockDownloaderTests.swift b/Tests/DarksideTests/BlockDownloaderTests.swift index dfc38abb..d625b503 100644 --- a/Tests/DarksideTests/BlockDownloaderTests.swift +++ b/Tests/DarksideTests/BlockDownloaderTests.swift @@ -51,15 +51,20 @@ class BlockDownloaderTests: XCTestCase { expect.fulfill() XCTAssertNil(error) - // check what was 'stored' - self.storage.latestHeight { result in - expect.fulfill() - - XCTAssertTrue(self.validate(result: result, against: upperRange)) - - self.downloader.lastDownloadedBlockHeight { resultHeight in + Task { + do { + // check what was 'stored' + let latestHeight = try await self.storage.latestHeightAsync() expect.fulfill() - XCTAssertTrue(self.validate(result: resultHeight, against: upperRange)) + + XCTAssertEqual(latestHeight, upperRange) + + self.downloader.lastDownloadedBlockHeight { resultHeight in + expect.fulfill() + XCTAssertTrue(self.validate(result: resultHeight, against: upperRange)) + } + } catch { + XCTFail("testSmallDownloadAsync() shouldn't fail") } } } diff --git a/Tests/OfflineTests/CompactBlockStorageTests.swift b/Tests/OfflineTests/CompactBlockStorageTests.swift index 6479a0ec..74d26887 100644 --- a/Tests/OfflineTests/CompactBlockStorageTests.swift +++ b/Tests/OfflineTests/CompactBlockStorageTests.swift @@ -19,7 +19,12 @@ class CompactBlockStorageTests: XCTestCase { func testEmptyStorage() { XCTAssertEqual(try! compactBlockDao.latestHeight(), BlockHeight.empty()) } - + + func testEmptyStorageAsync() async throws { + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(latestHeight, BlockHeight.empty()) + } + func testStoreThousandBlocks() { let initialHeight = try! compactBlockDao.latestHeight() let startHeight = self.network.constants.saplingActivationHeight @@ -38,6 +43,19 @@ class CompactBlockStorageTests: XCTestCase { XCTAssertEqual(latestHeight, finalHeight) } + func testStoreThousandBlocksAsync() async throws { + let initialHeight = try! compactBlockDao.latestHeight() + let startHeight = self.network.constants.saplingActivationHeight + let blockCount = Int(1_000) + let finalHeight = startHeight + blockCount + + try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertNotEqual(initialHeight, latestHeight) + XCTAssertEqual(latestHeight, finalHeight) + } + func testStoreOneBlockFromEmpty() { let initialHeight = try! compactBlockDao.latestHeight() guard initialHeight == BlockHeight.empty() else { @@ -61,6 +79,24 @@ class CompactBlockStorageTests: XCTestCase { } } + func testStoreOneBlockFromEmptyAsync() async throws { + let initialHeight = try await compactBlockDao.latestHeightAsync() + guard initialHeight == BlockHeight.empty() else { + XCTFail("database not empty, latest height: \(initialHeight)") + return + } + + let expectedHeight = BlockHeight(123_456) + guard let block = StubBlockCreator.createRandomDataBlock(with: expectedHeight) else { + XCTFail("could not create randem block with height: \(expectedHeight)") + return + } + try await compactBlockDao.writeAsync(blocks: [block]) + + let result = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(result, expectedHeight) + } + func testRewindTo() { let startHeight = self.network.constants.saplingActivationHeight let blockCount = Int(1_000) @@ -82,4 +118,17 @@ class CompactBlockStorageTests: XCTestCase { XCTFail("Rewind latest block failed with error: \(error)") } } + + func testRewindToAsync() async throws { + let startHeight = self.network.constants.saplingActivationHeight + let blockCount = Int(1_000) + let finalHeight = startHeight + blockCount + + try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight) + let rewindHeight = BlockHeight(finalHeight - 233) + + try await compactBlockDao.rewindAsync(to: rewindHeight) + let latestHeight = try await compactBlockDao.latestHeightAsync() + XCTAssertEqual(latestHeight, rewindHeight - 1) + } } diff --git a/Tests/TestUtils/FakeStorage.swift b/Tests/TestUtils/FakeStorage.swift index 8ba79084..1f7613fb 100644 --- a/Tests/TestUtils/FakeStorage.swift +++ b/Tests/TestUtils/FakeStorage.swift @@ -10,6 +10,18 @@ import Foundation @testable import ZcashLightClientKit class ZcashConsoleFakeStorage: CompactBlockRepository { + func latestHeightAsync() async throws -> BlockHeight { + latestBlockHeight + } + + func writeAsync(blocks: [ZcashCompactBlock]) async throws { + fakeSave(blocks: blocks) + } + + func rewindAsync(to height: BlockHeight) async throws { + fakeRewind(to: height) + } + func latestHeight() throws -> Int { return self.latestBlockHeight } @@ -29,12 +41,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { self.latestBlockHeight = latestBlockHeight } - func latestHeight(result: @escaping (Result) -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - result(.success(self.latestBlockHeight)) - } - } - private func fakeSave(blocks: [ZcashCompactBlock]) { blocks.forEach { LoggerProxy.debug("saving block \($0)") @@ -42,20 +48,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { } } - func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - self.fakeSave(blocks: blocks) - completion?(nil) - } - } - - func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - self.fakeRewind(to: height) - completion?(nil) - } - } - private func fakeRewind(to height: BlockHeight) { LoggerProxy.debug("rewind to \(height)") self.latestBlockHeight = min(self.latestBlockHeight, height) diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 70ea9a36..4dc044c2 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -33,6 +33,10 @@ class AwfulLightWalletService: MockLightWalletService { } } + override func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in continuation.finish(throwing: LightWalletServiceError.invalidBlock) } + } + override func submit(spendTransaction: Data, result: @escaping(Result) -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { result(.failure(LightWalletServiceError.invalidBlock))