[#464] CompactBlockStorage To async/await (#494)

- await/async APIs provided
- async throws unit tests using new API implemented

[464] CompactBlockStorage To async/await (494)

- removed deprecated closure APIs
- upgraded use of the async APIs
- tests updated
This commit is contained in:
Lukas Korba 2022-08-26 19:52:12 +02:00 committed by GitHub
parent f1a570bbc2
commit 742e6bd8ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 91 deletions

View File

@ -85,39 +85,28 @@ extension CompactBlockStorage: CompactBlockRepository {
try latestBlockHeight()
}
func latestHeight(result: @escaping (Swift.Result<BlockHeight, Error>) -> 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
}
}

View File

@ -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<BlockHeight, Error>) -> 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<ZcashCompactBlock, Error> = 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<BlockHeight, Error>) -> 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)))
}
}
}

View File

@ -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<BlockHeight, Error>) -> 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
}

View File

@ -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")
}
}
}

View File

@ -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)
}
}

View File

@ -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<BlockHeight, Error>) -> 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)

View File

@ -33,6 +33,10 @@ class AwfulLightWalletService: MockLightWalletService {
}
}
override func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
AsyncThrowingStream { continuation in continuation.finish(throwing: LightWalletServiceError.invalidBlock) }
}
override func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
result(.failure(LightWalletServiceError.invalidBlock))