[#465] CompactBlockDownloading to Async/Await (#507)

Closes #465
- CompactBlockDownloading closures removed
- CompactBlockDownloading new async API provided
This commit is contained in:
Lukas Korba 2022-08-29 21:31:01 +02:00 committed by GitHub
parent 30bfa6c633
commit 16d1948b5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 128 deletions

View File

@ -27,49 +27,44 @@ public protocol CompactBlockDownloading {
Downloads and stores the given block range. Downloads and stores the given block range.
Non-Blocking Non-Blocking
*/ */
func downloadBlockRange( func downloadBlockRangeAsync(_ heightRange: CompactBlockRange) async throws
_ heightRange: CompactBlockRange,
completion: @escaping (Error?) -> Void
)
/**
Remove newer blocks and go back to the given height
- Parameters:
- height: the given height to rewind to
- completion: block to be executed after completing rewind
*/
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void)
/**
returns the height of the latest compact block stored locally
BlockHeight.empty() if no blocks are stored yet
non-blocking
*/
func lastDownloadedBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/**
Returns the last height on the blockchain
Non-blocking
*/
func latestBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/** /**
Restore the download progress up to the given height. Restore the download progress up to the given height.
*/ */
func rewind(to height: BlockHeight) throws func rewind(to height: BlockHeight) throws
/**
Remove newer blocks and go back to the given height
- Parameter height: the given height to rewind to
*/
func rewindAsync(to height: BlockHeight) async throws
/** /**
Returns the height of the latest compact block stored locally. Returns the height of the latest compact block stored locally.
BlockHeight.empty() if no blocks are stored yet BlockHeight.empty() if no blocks are stored yet
Blocking Blocking
*/ */
func lastDownloadedBlockHeight() throws -> BlockHeight func lastDownloadedBlockHeight() throws -> BlockHeight
/**
returns the height of the latest compact block stored locally
BlockHeight.empty() if no blocks are stored yet
non-blocking
*/
func lastDownloadedBlockHeightAsync() async throws -> BlockHeight
/** /**
Returns the latest block height Returns the latest block height
Blocking Blocking
*/ */
func latestBlockHeight() throws -> BlockHeight func latestBlockHeight() throws -> BlockHeight
/**
Returns the last height on the blockchain
Non-blocking
*/
func latestBlockHeightAsync() async throws -> BlockHeight
/** /**
Gets the transaction for the Id given Gets the transaction for the Id given
@ -82,17 +77,30 @@ public protocol CompactBlockDownloading {
/** /**
Gets the transaction for the Id given Gets the transaction for the Id given
- Parameter txId: Data representing the transaction Id - Parameter txId: Data representing the transaction Id
- Parameter result: a handler for the result of the operation
*/ */
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, Error>) -> Void) func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] 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<UnspentTransactionOutputEntity, Error>
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void)
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity]
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) // TODO: will be removed with the issue 474
// https://github.com/zcash/ZcashLightClientKit/issues/474
// Use the new API fetchUnspentTransactionOutputs(...) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
func fetchUnspentTransactionOutputs(
tAddresses: [String],
startHeight: BlockHeight,
result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void
)
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
func closeConnection() func closeConnection()
} }
@ -138,6 +146,10 @@ extension CompactBlockDownloader: CompactBlockDownloading {
} }
} }
} }
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight ) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight)
}
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] { func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
try lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) try lightwalletService.fetchUTXOs(for: tAddress, height: startHeight)
@ -154,72 +166,54 @@ extension CompactBlockDownloader: CompactBlockDownloading {
} }
} }
func latestBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) { func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
lightwalletService.latestBlockHeight { fetchResult in lightwalletService.fetchUTXOs(for: tAddress, height: startHeight)
switch fetchResult { }
case .failure(let error):
result(.failure(error)) func latestBlockHeightAsync() async throws -> BlockHeight {
case .success(let height): try await lightwalletService.latestBlockHeightAsync()
result(.success(height))
}
}
} }
func latestBlockHeight() throws -> BlockHeight { func latestBlockHeight() throws -> BlockHeight {
try lightwalletService.latestBlockHeight() try lightwalletService.latestBlockHeight()
} }
/**
Downloads and stores the given block range.
Non-Blocking
*/
func downloadBlockRange(
_ heightRange: CompactBlockRange,
completion: @escaping (Error?) -> Void
) {
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)
}
}
}
func downloadBlockRange(_ range: CompactBlockRange) throws { func downloadBlockRange(_ range: CompactBlockRange) throws {
let blocks = try lightwalletService.blockRange(range) let blocks = try lightwalletService.blockRange(range)
try storage.write(blocks: blocks) try storage.write(blocks: blocks)
} }
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) { func downloadBlockRangeAsync( _ heightRange: CompactBlockRange) async throws {
Task { let stream: AsyncThrowingStream<ZcashCompactBlock, Error> = lightwalletService.blockRange(heightRange)
do { do {
try await storage.rewindAsync(to: height) var compactBlocks: [ZcashCompactBlock] = []
completion(nil) for try await compactBlock in stream {
} catch { compactBlocks.append(compactBlock)
completion(error)
} }
try await self.storage.writeAsync(blocks: compactBlocks)
} catch {
throw error
} }
} }
func lastDownloadedBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) { func rewindAsync(to height: BlockHeight) async throws {
Task { do {
do { try await storage.rewindAsync(to: height)
let latestHeight = try await storage.latestHeightAsync() } catch {
result(.success(latestHeight)) throw error
} catch {
result(.failure(CompactBlockDownloadError.generalError(error: error)))
}
} }
} }
func lastDownloadedBlockHeightAsync() async throws -> BlockHeight {
do {
let latestHeight = try await storage.latestHeightAsync()
return latestHeight
} catch {
throw CompactBlockDownloadError.generalError(error: error)
}
}
func rewind(to height: BlockHeight) throws { func rewind(to height: BlockHeight) throws {
try self.storage.rewind(to: height) try self.storage.rewind(to: height)
} }
@ -232,14 +226,7 @@ extension CompactBlockDownloader: CompactBlockDownloading {
try lightwalletService.fetchTransaction(txId: txId) try lightwalletService.fetchTransaction(txId: txId)
} }
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, Error>) -> Void) { func fetchTransactionAsync(txId: Data) async throws -> TransactionEntity {
lightwalletService.fetchTransaction(txId: txId) { txResult in try await lightwalletService.fetchTransactionAsync(txId: txId)
switch txResult {
case .failure(let error):
result(.failure(error))
case .success(let transaction):
result(.success(transaction))
}
}
} }
} }

View File

@ -14,7 +14,9 @@ class CompactBlockDownloadOperation: ZcashOperation {
private var downloader: CompactBlockDownloading private var downloader: CompactBlockDownloading
private var range: CompactBlockRange private var range: CompactBlockRange
private var cancelableTask: Task<Void, Error>?
private var done = false
required init(downloader: CompactBlockDownloading, range: CompactBlockRange) { required init(downloader: CompactBlockDownloading, range: CompactBlockRange) {
self.range = range self.range = range
self.downloader = downloader self.downloader = downloader
@ -28,12 +30,29 @@ class CompactBlockDownloadOperation: ZcashOperation {
return return
} }
self.startedHandler?() self.startedHandler?()
do {
try downloader.downloadBlockRange(range) cancelableTask = Task {
} catch { do {
self.error = error try await downloader.downloadBlockRangeAsync(range)
self.fail() self.done = true
} catch {
self.fail(error: error)
}
} }
while !done && !isCancelled {
sleep(1)
}
}
override func fail(error: Error? = nil) {
self.cancelableTask?.cancel()
super.fail(error: error)
}
override func cancel() {
self.cancelableTask?.cancel()
super.cancel()
} }
} }

View File

@ -66,7 +66,9 @@ class FetchUnspentTxOutputsOperation: ZcashOperation {
throw FetchUTXOError.clearingFailed(error) throw FetchUTXOError.clearingFailed(error)
} }
let utxos = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) // TODO: will be replaced by new async API, issue 474
// https://github.com/zcash/ZcashLightClientKit/issues/474
let utxos: [UnspentTransactionOutputEntity] = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight)
let result = storeUTXOs(utxos, in: dataDb) let result = storeUTXOs(utxos, in: dataDb)

View File

@ -631,7 +631,14 @@ public class SDKSynchronizer: Synchronizer {
} }
public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) { public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
blockProcessor.downloader.latestBlockHeight(result: result) Task {
do {
let latestBlockHeight = try await blockProcessor.downloader.latestBlockHeightAsync()
result(.success(latestBlockHeight))
} catch {
result(.failure(error))
}
}
} }
public func latestHeight() throws -> BlockHeight { public func latestHeight() throws -> BlockHeight {

View File

@ -40,36 +40,23 @@ class BlockDownloaderTests: XCTestCase {
try? FileManager.default.removeItem(at: cacheDB) try? FileManager.default.removeItem(at: cacheDB)
} }
func testSmallDownloadAsync() { func testSmallDownloadAsync() async {
let expect = XCTestExpectation(description: self.description)
expect.expectedFulfillmentCount = 3
let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight
let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99 let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99
let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange))
downloader.downloadBlockRange(range) { error in do {
expect.fulfill() try await downloader.downloadBlockRangeAsync(range)
XCTAssertNil(error)
Task { // check what was 'stored'
do { let latestHeight = try await self.storage.latestHeightAsync()
// check what was 'stored' XCTAssertEqual(latestHeight, upperRange)
let latestHeight = try await self.storage.latestHeightAsync()
expect.fulfill() let resultHeight = try await self.downloader.lastDownloadedBlockHeightAsync()
XCTAssertEqual(resultHeight, upperRange)
XCTAssertEqual(latestHeight, upperRange) } catch {
XCTFail("testSmallDownloadAsync() shouldn't fail")
self.downloader.lastDownloadedBlockHeight { resultHeight in
expect.fulfill()
XCTAssertTrue(self.validate(result: resultHeight, against: upperRange))
}
} catch {
XCTFail("testSmallDownloadAsync() shouldn't fail")
}
}
} }
wait(for: [expect], timeout: 2)
} }
func testSmallDownload() { func testSmallDownload() {
@ -99,7 +86,7 @@ class BlockDownloaderTests: XCTestCase {
XCTAssertEqual(currentLatest, upperRange ) XCTAssertEqual(currentLatest, upperRange )
} }
func testFailure() { func testFailure() async {
let awfulDownloader = CompactBlockDownloader( let awfulDownloader = CompactBlockDownloader(
service: AwfulLightWalletService( service: AwfulLightWalletService(
latestBlockHeight: self.network.constants.saplingActivationHeight + 1000, latestBlockHeight: self.network.constants.saplingActivationHeight + 1000,
@ -108,18 +95,16 @@ class BlockDownloaderTests: XCTestCase {
storage: ZcashConsoleFakeStorage() storage: ZcashConsoleFakeStorage()
) )
let expect = XCTestExpectation(description: self.description)
expect.expectedFulfillmentCount = 1
let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight
let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99 let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99
let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange)) let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange))
awfulDownloader.downloadBlockRange(range) { error in do {
expect.fulfill() try await awfulDownloader.downloadBlockRangeAsync(range)
} catch {
XCTAssertNotNil(error) XCTAssertNotNil(error)
} }
wait(for: [expect], timeout: 2)
} }
} }