Merge branch 'master' into merge_master_to_zip_316

This commit is contained in:
Francisco Gindre 2022-09-15 11:04:49 -03:00
commit bee1c9db80
32 changed files with 1211 additions and 2000 deletions

View File

@ -31,7 +31,7 @@ class SyncBlocksViewController: UIViewController {
// swiftlint:disable:next force_try
_ = try! wallet.initialize(with: DemoAppConfig.seed)
processor = CompactBlockProcessor(initializer: wallet)
statusLabel.text = textFor(state: processor?.state ?? .stopped)
statusLabel.text = textFor(state: processor?.state.getState() ?? .stopped)
progressBar.progress = 0
NotificationCenter.default.addObserver(
@ -70,7 +70,7 @@ class SyncBlocksViewController: UIViewController {
@IBAction func startStop() {
guard let processor = processor else { return }
switch processor.state {
switch processor.state.getState() {
case .stopped:
startProcessor()
default:
@ -92,7 +92,7 @@ class SyncBlocksViewController: UIViewController {
func stopProcessor() {
guard let processor = processor else { return }
processor.stop(cancelTasks: true)
processor.stop()
updateUI()
}
@ -114,7 +114,7 @@ class SyncBlocksViewController: UIViewController {
}
func updateUI() {
guard let state = processor?.state else { return }
guard let state = processor?.state.getState() else { return }
statusLabel.text = textFor(state: state)
startPause.setTitle(buttonText(for: state), for: .normal)

View File

@ -0,0 +1,160 @@
//
// CompactBlockDownload.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
extension CompactBlockProcessor {
func compactBlockStreamDownload(
blockBufferSize: Int,
startHeight: BlockHeight? = nil,
targetHeight: BlockHeight? = nil
) async throws {
try Task.checkCancellation()
setState(.downloading)
var buffer: [ZcashCompactBlock] = []
var targetHeightInternal: BlockHeight?
do {
targetHeightInternal = targetHeight
if targetHeight == nil {
targetHeightInternal = try await service.latestBlockHeightAsync()
}
guard let latestHeight = targetHeightInternal else {
throw LightWalletServiceError.generalError(message: "missing target height on compactBlockStreamDownload")
}
let latestDownloaded = try await storage.latestHeightAsync()
let startHeight = max(startHeight ?? BlockHeight.empty(), latestDownloaded)
let stream = service.blockStream(
startHeight: startHeight,
endHeight: latestHeight
)
for try await zcashCompactBlock in stream {
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)
buffer.removeAll(keepingCapacity: true)
}
let progress = BlockProgress(
startHeight: startHeight,
targetHeight: latestHeight,
progressHeight: zcashCompactBlock.height
)
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)
buffer.removeAll(keepingCapacity: true)
} catch {
guard let err = error as? LightWalletServiceError, case .userCancelled = err else {
throw error
}
}
}
}
extension CompactBlockProcessor {
func compactBlockDownload(
downloader: CompactBlockDownloading,
range: CompactBlockRange
) async throws {
try Task.checkCancellation()
do {
try await downloader.downloadBlockRangeAsync(range)
} catch {
throw error
}
}
}
extension CompactBlockProcessor {
enum CompactBlockBatchDownloadError: Error {
case startHeightMissing
case batchDownloadFailed(range: CompactBlockRange, error: Error?)
}
func compactBlockBatchDownload(
range: CompactBlockRange,
batchSize: Int = 100,
maxRetries: Int = 5
) async throws {
try Task.checkCancellation()
var startHeight = range.lowerBound
let targetHeight = range.upperBound
do {
let localDownloadedHeight = try await self.storage.latestHeightAsync()
if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight {
LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))")
startHeight = localDownloadedHeight + 1
}
var currentHeight = startHeight
notifyProgress(
.download(
BlockProgress(
startHeight: currentHeight,
targetHeight: targetHeight,
progressHeight: currentHeight
)
)
)
while !Task.isCancelled && currentHeight <= targetHeight {
var retries = 0
var success = true
var localError: Error?
let range = CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batchSize, targetHeight)))
repeat {
do {
let stream: AsyncThrowingStream<ZcashCompactBlock, Error> = service.blockRange(range)
var blocks: [ZcashCompactBlock] = []
for try await compactBlock in stream {
blocks.append(compactBlock)
}
try storage.insert(blocks)
success = true
} catch {
success = false
localError = error
retries += 1
}
} while !Task.isCancelled && !success && retries < maxRetries
if retries >= maxRetries {
throw CompactBlockBatchDownloadError.batchDownloadFailed(range: range, error: localError)
}
notifyProgress(
.download(
BlockProgress(
startHeight: startHeight,
targetHeight: targetHeight,
progressHeight: range.upperBound
)
)
)
currentHeight = range.upperBound + 1
}
} catch {
throw error
}
}
}

View File

@ -1,318 +0,0 @@
//
// CompactBlockDownloadOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
class CompactBlockDownloadOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
private var downloader: CompactBlockDownloading
private var range: CompactBlockRange
private var cancelableTask: Task<Void, Error>?
private var done = false
required init(downloader: CompactBlockDownloading, range: CompactBlockRange) {
self.range = range
self.downloader = downloader
super.init()
self.name = "Download Operation: \(range)"
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
do {
try await downloader.downloadBlockRangeAsync(range)
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()
}
}
protocol CompactBlockProgressDelegate: AnyObject {
func progressUpdated(_ progress: CompactBlockProgress)
}
class CompactBlockStreamDownloadOperation: ZcashOperation {
enum CompactBlockStreamDownloadOperationError: Error {
case startHeightMissing
}
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
private var storage: CompactBlockStorage
private var service: LightWalletService
private var done = false
private var cancelableTask: Task<Void, Error>?
private var startHeight: BlockHeight?
private var targetHeight: BlockHeight?
private var blockBufferSize: Int
private var buffer: [ZcashCompactBlock] = []
private weak var progressDelegate: CompactBlockProgressDelegate?
/// Creates an Compact Block Stream Download Operation Operation
/// - Parameters:
/// - service: instance that conforms to `LightWalletService`
/// - storage: instance that conforms to `CompactBlockStorage`
/// - blockBufferSize: the number of blocks that the stream downloader will store in memory
/// before writing them to disk. Making this number smaller makes the downloader easier on RAM
/// memory while being less efficient on disk writes. Making it bigger takes up more RAM memory
/// but is less straining on Disk Writes. Too little or too big buffer will make this less efficient.
/// - startHeight: the height this downloader will start downloading from. If `nil`,
/// it will start from the latest height found on the local cacheDb
/// - targetHeight: the upper bound for this stream download. If `nil`, the
/// streamer will call `service.latestBlockHeight()`
/// - progressDelegate: Optional delegate to report ongoing progress conforming to
/// `CompactBlockProgressDelegate`
///
required init(
service: LightWalletService,
storage: CompactBlockStorage,
blockBufferSize: Int,
startHeight: BlockHeight? = nil,
targetHeight: BlockHeight? = nil,
progressDelegate: CompactBlockProgressDelegate? = nil
) {
self.storage = storage
self.service = service
self.startHeight = startHeight
self.targetHeight = targetHeight
self.progressDelegate = progressDelegate
self.blockBufferSize = blockBufferSize
super.init()
self.name = "Download Stream Operation"
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
do {
if self.targetHeight == nil {
self.targetHeight = try await service.latestBlockHeightAsync()
}
guard let latestHeight = self.targetHeight else {
throw LightWalletServiceError.generalError(message: "missing target height on block stream operation")
}
let latestDownloaded = try await storage.latestHeightAsync()
let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded)
let stream = service.blockStream(
startHeight: startHeight,
endHeight: latestHeight
)
for try await zcashCompactBlock in stream {
try self.cache(zcashCompactBlock, flushCache: false)
let progress = BlockProgress(
startHeight: startHeight,
targetHeight: latestHeight,
progressHeight: zcashCompactBlock.height
)
self.progressDelegate?.progressUpdated(.download(progress))
}
try self.flush()
self.done = true
} catch {
if let err = error as? LightWalletServiceError, case .userCancelled = err {
self.done = true
} else {
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()
}
func cache(_ block: ZcashCompactBlock, flushCache: Bool) throws {
self.buffer.append(block)
if flushCache || buffer.count >= blockBufferSize {
try flush()
}
}
func flush() throws {
try self.storage.write(blocks: self.buffer)
self.buffer.removeAll(keepingCapacity: true)
}
}
class CompactBlockBatchDownloadOperation: ZcashOperation {
enum CompactBlockBatchDownloadOperationError: Error {
case startHeightMissing
case batchDownloadFailed(range: CompactBlockRange, error: Error?)
}
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
private var batch: Int
private var done = false
private var maxRetries: Int
private var storage: CompactBlockStorage
private var service: LightWalletService
private var cancelableTask: Task<Void, Error>?
private var startHeight: BlockHeight
private var targetHeight: BlockHeight
private weak var progressDelegate: CompactBlockProgressDelegate?
required init(
service: LightWalletService,
storage: CompactBlockStorage,
startHeight: BlockHeight,
targetHeight: BlockHeight,
batchSize: Int = 100,
maxRetries: Int = 5,
progressDelegate: CompactBlockProgressDelegate? = nil
) {
self.storage = storage
self.service = service
self.startHeight = startHeight
self.targetHeight = targetHeight
self.progressDelegate = progressDelegate
self.batch = batchSize
self.maxRetries = maxRetries
super.init()
self.name = "Download Batch Operation"
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
do {
let localDownloadedHeight = try await self.storage.latestHeightAsync()
if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight {
LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))")
startHeight = localDownloadedHeight + 1
}
var currentHeight = startHeight
self.progressDelegate?.progressUpdated(
.download(
BlockProgress(
startHeight: currentHeight,
targetHeight: targetHeight,
progressHeight: currentHeight
)
)
)
while !isCancelled && currentHeight <= targetHeight {
var retries = 0
var success = true
var localError: Error?
let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight)
repeat {
do {
let stream: AsyncThrowingStream<ZcashCompactBlock, Error> = service.blockRange(range)
var blocks: [ZcashCompactBlock] = []
for try await compactBlock in stream {
blocks.append(compactBlock)
}
try storage.insert(blocks)
success = true
} catch {
success = false
localError = error
retries += 1
}
} while !isCancelled && !success && retries < maxRetries
if retries >= maxRetries {
throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError)
}
self.progressDelegate?.progressUpdated(
.download(
BlockProgress(
startHeight: startHeight,
targetHeight: targetHeight,
progressHeight: range.upperBound
)
)
)
currentHeight = range.upperBound + 1
}
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()
}
func nextRange(currentHeight: BlockHeight, targetHeight: BlockHeight) -> CompactBlockRange {
CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batch, targetHeight)))
}
}

View File

@ -0,0 +1,119 @@
//
// CompactBlockEnhancement.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 4/10/20.
//
import Foundation
extension CompactBlockProcessor {
enum EnhancementError: Error {
case noRawData(message: String)
case unknownError
case decryptError(error: Error)
case txIdNotFound(txId: Data)
}
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 transactionID = transaction.transactionId.toHexStringTxId()
let block = String(describing: transaction.minedHeight)
LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
guard let rawBytes = transaction.raw?.bytes else {
let error = EnhancementError.noRawData(
message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data"
)
LoggerProxy.error("\(error)")
throw error
}
guard let minedHeight = transaction.minedHeight else {
let error = EnhancementError.noRawData(
message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())"
)
LoggerProxy.error("\(error)")
throw error
}
guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else {
if let rustError = rustBackend.lastError() {
throw EnhancementError.decryptError(error: rustError)
}
throw EnhancementError.unknownError
}
guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else {
throw EnhancementError.txIdNotFound(txId: transaction.transactionId)
}
return confirmedTx
}
func compactBlockEnhancement(range: CompactBlockRange) async throws {
try Task.checkCancellation()
LoggerProxy.debug("Started Enhancing range: \(range)")
setState(.enhancing)
let blockRange = range.blockRange()
var retries = 0
let maxRetries = 5
// fetch transactions
do {
guard let transactions = try transactionRepository.findTransactions(in: blockRange, limit: Int.max), !transactions.isEmpty else {
LoggerProxy.debug("no transactions detected on range: \(blockRange.printRange)")
return
}
for index in 0 ..< transactions.count {
let transaction = transactions[index]
var retry = true
while retry && retries < maxRetries {
try Task.checkCancellation()
do {
let confirmedTx = try await enhance(transaction: transaction)
retry = false
notifyProgress(
.enhance(
EnhancementStreamProgress(
totalTransactions: transactions.count,
enhancedTransactions: index + 1,
lastFoundTransaction: confirmedTx,
range: range
)
)
)
} catch {
retries += 1
LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)")
if retries > maxRetries {
throw error
}
}
}
}
} catch {
LoggerProxy.error("error enhancing transactions! \(error)")
throw error
}
if let foundTxs = try? transactionRepository.findConfirmedTransactions(in: blockRange, offset: 0, limit: Int.max) {
notifyTransactions(foundTxs, in: blockRange)
}
if Task.isCancelled {
LoggerProxy.debug("Warning: compactBlockEnhancement on range \(range) cancelled")
}
}
}
private extension BlockRange {
var printRange: String {
"\(self.start.height) ... \(self.end.height)"
}
}

View File

@ -1,175 +0,0 @@
//
// CompactBlockEnhancementOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 4/10/20.
//
import Foundation
class CompactBlockEnhancementOperation: ZcashOperation {
enum EnhancementError: Error {
case noRawData(message: String)
case unknownError
case decryptError(error: Error)
case txIdNotFound(txId: Data)
}
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var rustBackend: ZcashRustBackendWelding.Type
var txFoundHandler: (([ConfirmedTransactionEntity], BlockRange) -> Void)?
var downloader: CompactBlockDownloading
var repository: TransactionRepository
var range: BlockRange
var maxRetries: Int = 5
var retries: Int = 0
private(set) var network: NetworkType
private weak var progressDelegate: CompactBlockProgressDelegate?
private var dataDb: URL
private var cancelableTask: Task<Void, Error>?
private var done = false
init(
rustWelding: ZcashRustBackendWelding.Type,
dataDb: URL,
downloader: CompactBlockDownloading,
repository: TransactionRepository,
range: BlockRange,
networkType: NetworkType,
progressDelegate: CompactBlockProgressDelegate? = nil
) {
rustBackend = rustWelding
self.dataDb = dataDb
self.downloader = downloader
self.repository = repository
self.range = range
self.progressDelegate = progressDelegate
self.network = networkType
super.init()
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
// fetch transactions
do {
guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else {
LoggerProxy.debug("no transactions detected on range: \(range.printRange)")
return
}
for index in 0 ..< transactions.count {
let transaction = transactions[index]
var retry = true
while retry && self.retries < maxRetries {
do {
let confirmedTx = try await enhance(transaction: transaction)
retry = false
self.reportProgress(
totalTransactions: transactions.count,
enhanced: index + 1,
txEnhanced: confirmedTx
)
} catch {
self.retries += 1
LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)")
if retries > maxRetries {
throw error
}
}
}
}
} catch {
LoggerProxy.error("error enhancing transactions! \(error)")
self.fail(error: error)
return
}
if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) {
handler(foundTxs, self.range)
}
self.done = true
}
while !done && !isCancelled {
sleep(1)
}
}
func reportProgress(totalTransactions: Int, enhanced: Int, txEnhanced: ConfirmedTransactionEntity) {
self.progressDelegate?.progressUpdated(
.enhance(
EnhancementStreamProgress(
totalTransactions: totalTransactions,
enhancedTransactions: enhanced,
lastFoundTransaction: txEnhanced,
range: self.range.compactBlockRange
)
)
)
}
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 transactionID = transaction.transactionId.toHexStringTxId()
let block = String(describing: transaction.minedHeight)
LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
guard let rawBytes = transaction.raw?.bytes else {
let error = EnhancementError.noRawData(
message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data"
)
LoggerProxy.error("\(error)")
throw error
}
guard let minedHeight = transaction.minedHeight else {
let error = EnhancementError.noRawData(
message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())"
)
LoggerProxy.error("\(error)")
throw error
}
guard rustBackend.decryptAndStoreTransaction(dbData: dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: network) else {
if let rustError = rustBackend.lastError() {
throw EnhancementError.decryptError(error: rustError)
}
throw EnhancementError.unknownError
}
guard let confirmedTx = try self.repository.findConfirmedTransactionBy(rawId: transaction.transactionId) else {
throw EnhancementError.txIdNotFound(txId: transaction.transactionId)
}
return confirmedTx
}
override func fail(error: Error? = nil) {
self.cancelableTask?.cancel()
super.fail(error: error)
}
override func cancel() {
self.cancelableTask?.cancel()
super.cancel()
}
}
private extension BlockRange {
var printRange: String {
"\(self.start.height) ... \(self.end.height)"
}
}

View File

@ -30,6 +30,7 @@ public enum CompactBlockProcessorError: Error {
case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID)
case networkMismatch(expected: NetworkType, found: NetworkType)
case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight)
case unknown
}
/**
@ -311,11 +312,30 @@ public class CompactBlockProcessor {
case synced
}
public private(set) var state: State = .stopped {
didSet {
transitionState(from: oldValue, to: self.state)
// TODO: this isn't an Actor even though it looks like a good candidate, the reason:
// `state` lives in both sync and async environments. An Actor is demanding async context only
// so we can't take the advantage unless we encapsulate all `state` reads/writes to async context.
// Therefore solution with class + lock works for us butr eventually will be replaced.
// The future of CompactBlockProcessor is an actor (we won't need to encapsulate the state separately), issue 523,
// https://github.com/zcash/ZcashLightClientKit/issues/523
public class ThreadSafeState {
private var state: State = .stopped
let lock = NSLock()
func setState(_ newState: State) {
lock.lock()
defer { lock.unlock() }
state = newState
}
public func getState() -> State {
lock.lock()
defer { lock.unlock() }
return state
}
}
public internal(set) var state = ThreadSafeState()
var config: Configuration {
willSet {
@ -328,7 +348,7 @@ public class CompactBlockProcessor {
}
var shouldStart: Bool {
switch self.state {
switch self.state.getState() {
case .stopped, .synced, .error:
return !maxAttemptsReached
default:
@ -336,19 +356,19 @@ public class CompactBlockProcessor {
}
}
private var service: LightWalletService
var service: LightWalletService
private(set) var downloader: CompactBlockDownloading
private var storage: CompactBlockStorage
private var transactionRepository: TransactionRepository
private var accountRepository: AccountRepository
private var rustBackend: ZcashRustBackendWelding.Type
var storage: CompactBlockStorage
var transactionRepository: TransactionRepository
var accountRepository: AccountRepository
var rustBackend: ZcashRustBackendWelding.Type
private var retryAttempts: Int = 0
private var backoffTimer: Timer?
private var lowerBoundHeight: BlockHeight?
private var latestBlockHeight: BlockHeight
private var lastChainValidationFailure: BlockHeight?
private var consecutiveChainValidationErrors: Int = 0
private var processingError: Error?
var processingError: Error?
private var foundBlocks = false
private var maxAttempts: Int {
config.retries
@ -358,13 +378,7 @@ public class CompactBlockProcessor {
BlockHeight(self.config.downloadBatchSize)
}
private var operationQueue: OperationQueue = {
let queue = OperationQueue()
queue.name = "CompactBlockProcessorQueue"
queue.maxConcurrentOperationCount = 1
return queue
}()
private var cancelableTask: Task<Void, Error>?
/// Initializes a CompactBlockProcessor instance
/// - Parameters:
@ -431,7 +445,13 @@ public class CompactBlockProcessor {
}
deinit {
self.operationQueue.cancelAllOperations()
cancelableTask?.cancel()
}
func setState(_ newState: State) {
let oldValue = state.getState()
state.setState(newState)
transitionState(from: oldValue, to: newState)
}
static func validateServerInfo(
@ -487,19 +507,14 @@ public class CompactBlockProcessor {
self.backoffTimer?.invalidate()
self.backoffTimer = nil
}
guard !operationQueue.isSuspended else {
LoggerProxy.debug("restarting suspended queue")
operationQueue.isSuspended = false
return
}
guard shouldStart else {
switch self.state {
switch self.state.getState() {
case .error(let e):
// max attempts have been reached
LoggerProxy.info("max retry attempts reached with error: \(e)")
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
self.state = .stopped
setState(.stopped)
case .stopped:
// max attempts have been reached
LoggerProxy.info("max retry attempts reached")
@ -514,7 +529,7 @@ public class CompactBlockProcessor {
return
}
self.nextBatchTask()
self.nextBatch()
}
/**
@ -523,18 +538,14 @@ public class CompactBlockProcessor {
Note: retry count is reset
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
*/
public func stop(cancelTasks: Bool = true) {
public func stop() {
self.backoffTimer?.invalidate()
self.backoffTimer = nil
if cancelTasks {
operationQueue.cancelAllOperations()
} else {
self.operationQueue.isSuspended = true
}
cancelableTask?.cancel()
self.retryAttempts = 0
self.state = .stopped
setState(.stopped)
}
/**
@ -577,7 +588,7 @@ public class CompactBlockProcessor {
- Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started
*/
func setStartHeight(_ startHeight: BlockHeight) throws {
guard self.state == .stopped, startHeight >= config.network.constants.saplingActivationHeight else {
guard self.state.getState() == .stopped, startHeight >= config.network.constants.saplingActivationHeight else {
throw CompactBlockProcessorError.invalidConfiguration
}
@ -612,243 +623,34 @@ public class CompactBlockProcessor {
})
}
/**
processes new blocks on the given range based on the configuration set for this instance
the way operations are queued is implemented based on the following good practice https://forums.developer.apple.com/thread/25761
*/
// swiftlint:disable cyclomatic_complexity
/// Processes new blocks on the given range based on the configuration set for this instance
func processNewBlocks(range: CompactBlockRange) {
self.foundBlocks = true
self.backoffTimer?.invalidate()
self.backoffTimer = nil
let cfg = self.config
let downloadBlockOperation = CompactBlockStreamDownloadOperation(
service: self.service,
storage: self.storage,
blockBufferSize: self.config.downloadBufferSize,
startHeight: range.lowerBound,
targetHeight: range.upperBound,
progressDelegate: self
)
downloadBlockOperation.startedHandler = { [weak self] in
DispatchQueue.main.async {
self?.state = .downloading
}
}
downloadBlockOperation.errorHandler = { [weak self] error in
DispatchQueue.main.async {
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
}
let validateChainOperation = CompactBlockValidationOperation(
rustWelding: self.rustBackend,
cacheDb: cfg.cacheDb,
dataDb: cfg.dataDb,
networkType: self.config.network.networkType
)
let downloadValidateAdapterOperation = BlockOperation { [weak validateChainOperation, weak downloadBlockOperation] in
validateChainOperation?.error = downloadBlockOperation?.error
}
validateChainOperation.completionHandler = { [weak self] _, cancelled in
guard !cancelled else {
DispatchQueue.main.async {
self?.state = .stopped
LoggerProxy.debug("Warning: validateChainOperation operation cancelled")
}
return
}
LoggerProxy.debug("validateChainFinished")
}
validateChainOperation.errorHandler = { [weak self] error in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let validationError = error as? CompactBlockValidationError else {
LoggerProxy.error("Warning: validateChain operation returning generic error: \(error)")
return
}
switch validationError {
case .validationFailed(let height):
LoggerProxy.debug("chain validation at height: \(height)")
self.validationFailed(at: height)
case .failedWithError(let e):
guard let validationFailure = e else {
LoggerProxy.error("validation failed without a specific error")
self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error"))
return
}
cancelableTask = Task(priority: .userInitiated) {
do {
try await compactBlockStreamDownload(
blockBufferSize: config.downloadBufferSize,
startHeight: range.lowerBound,
targetHeight: range.upperBound
)
try await compactBlockValidation()
try await compactBlockBatchScanning(range: range)
try await compactBlockEnhancement(range: range)
try await fetchUnspentTxOutputs(range: range)
} catch {
if error is CancellationError {
self.fail(validationFailure)
}
if !(Task.isCancelled) {
fail(error)
}
}
}
validateChainOperation.startedHandler = { [weak self] in
DispatchQueue.main.async { [weak self] in
self?.state = .validating
}
}
let scanBlocksOperation = CompactBlockBatchScanningOperation(
rustWelding: rustBackend,
cacheDb: config.cacheDb,
dataDb: config.dataDb,
transactionRepository: transactionRepository,
range: range,
batchSize: UInt32(self.config.scanningBatchSize),
networkType: self.config.network.networkType,
progressDelegate: self
)
let validateScanningAdapterOperation = BlockOperation { [weak scanBlocksOperation, weak validateChainOperation] in
scanBlocksOperation?.error = validateChainOperation?.error
}
scanBlocksOperation.startedHandler = { [weak self] in
DispatchQueue.main.async { [weak self] in
self?.state = .scanning
}
}
scanBlocksOperation.completionHandler = { [weak self] _, cancelled in
guard !cancelled else {
DispatchQueue.main.async { [weak self] in
self?.state = .stopped
LoggerProxy.debug("Warning: scanBlocksOperation operation cancelled")
}
return
}
}
scanBlocksOperation.errorHandler = { [weak self] error in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
}
let enhanceOperation = CompactBlockEnhancementOperation(
rustWelding: rustBackend,
dataDb: config.dataDb,
downloader: downloader,
repository: transactionRepository,
range: range.blockRange(),
networkType: self.config.network.networkType
)
enhanceOperation.startedHandler = {
LoggerProxy.debug("Started Enhancing range: \(range)")
DispatchQueue.main.async { [weak self] in
self?.state = .enhancing
}
}
enhanceOperation.txFoundHandler = { [weak self] txs, range in
self?.notifyTransactions(txs, in: range)
}
enhanceOperation.completionHandler = { _, cancelled in
guard !cancelled else {
LoggerProxy.debug("Warning: enhance operation on range \(range) cancelled")
return
}
}
enhanceOperation.errorHandler = { [weak self] error in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
}
let scanEnhanceAdapterOperation = BlockOperation { [weak enhanceOperation, weak scanBlocksOperation] in
enhanceOperation?.error = scanBlocksOperation?.error
}
let fetchOperation = FetchUnspentTxOutputsOperation(
accountRepository: accountRepository,
downloader: self.downloader,
rustbackend: rustBackend,
dataDb: config.dataDb,
startHeight: config.walletBirthday,
networkType: self.config.network.networkType
)
fetchOperation.startedHandler = { [weak self] in
DispatchQueue.main.async { [weak self] in
self?.state = .fetching
}
}
fetchOperation.completionHandler = { [weak self] _, cancelled in
guard !cancelled else {
LoggerProxy.debug("Warning: fetch operation on range \(range) cancelled")
return
}
DispatchQueue.main.async { [weak self] in
self?.processBatchFinished(range: range)
}
}
fetchOperation.errorHandler = { [weak self] error in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
}
fetchOperation.fetchedUTXOsHandler = { result in
NotificationCenter.default.post(
name: .blockProcessorStoredUTXOs,
object: self,
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
)
}
let enhanceFetchAdapterOperation = BlockOperation { [weak fetchOperation, weak enhanceOperation] in
fetchOperation?.error = enhanceOperation?.error
}
downloadValidateAdapterOperation.addDependency(downloadBlockOperation)
validateChainOperation.addDependency(downloadValidateAdapterOperation)
validateScanningAdapterOperation.addDependency(validateChainOperation)
scanBlocksOperation.addDependency(validateScanningAdapterOperation)
scanEnhanceAdapterOperation.addDependency(scanBlocksOperation)
enhanceOperation.addDependency(scanEnhanceAdapterOperation)
enhanceFetchAdapterOperation.addDependency(enhanceOperation)
fetchOperation.addDependency(enhanceFetchAdapterOperation)
operationQueue.addOperations(
[
downloadBlockOperation,
downloadValidateAdapterOperation,
validateChainOperation,
validateScanningAdapterOperation,
scanBlocksOperation,
scanEnhanceAdapterOperation,
enhanceOperation,
enhanceFetchAdapterOperation,
fetchOperation
],
waitUntilFinished: false
)
}
func calculateProgress(start: BlockHeight, current: BlockHeight, latest: BlockHeight) -> Float {
@ -892,35 +694,35 @@ public class CompactBlockProcessor {
}
func severeFailure(_ error: Error) {
operationQueue.cancelAllOperations()
cancelableTask?.cancel()
LoggerProxy.error("show stoppper failure: \(error)")
self.backoffTimer?.invalidate()
self.retryAttempts = config.retries
self.processingError = error
self.state = .error(error)
setState(.error(error))
self.notifyError(error)
}
func fail(_ error: Error) {
// todo specify: failure
LoggerProxy.error("\(error)")
operationQueue.cancelAllOperations()
cancelableTask?.cancel()
self.retryAttempts += 1
self.processingError = error
switch self.state {
switch self.state.getState() {
case .error:
notifyError(error)
default:
break
}
self.state = .error(error)
setState(.error(error))
guard self.maxAttemptsReached else { return }
// don't set a new timer if there are no more attempts.
self.setTimer()
}
func retryProcessing(range: CompactBlockRange) {
operationQueue.cancelAllOperations()
cancelableTask?.cancel()
// update retries
self.retryAttempts += 1
self.processingError = nil
@ -935,7 +737,7 @@ public class CompactBlockProcessor {
// process next batch
// processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday))
nextBatchTask()
nextBatch()
} catch {
self.fail(error)
}
@ -972,48 +774,9 @@ public class CompactBlockProcessor {
}
}
@available(*, deprecated, message: "This static method will be removed soon, use `nextBatchTask()` instead.")
private func nextBatch() {
self.state = .downloading
NextStateHelper.nextState(
service: self.service,
downloader: self.downloader,
config: self.config,
rustBackend: self.rustBackend,
queue: nil
) { result in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
switch result {
case .success(let nextState):
switch nextState {
case .finishProcessing(let height):
self.latestBlockHeight = height
self.processingFinished(height: height)
case .processNewBlocks(let range):
self.latestBlockHeight = range.upperBound
self.lowerBoundHeight = range.lowerBound
self.processNewBlocks(range: range)
case let .wait(latestHeight, latestDownloadHeight):
// Lightwalletd might be syncing
self.lowerBoundHeight = latestDownloadHeight
self.latestBlockHeight = latestHeight
LoggerProxy.info(
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight)" +
"while latest blockheight is reported at: \(latestHeight)"
)
self.processingFinished(height: latestDownloadHeight)
}
case .failure(let error):
self.severeFailure(error)
}
}
}
}
private func nextBatchTask() {
self.state = .downloading
Task { [self] in
setState(.downloading)
Task { @MainActor [self] in
do {
let nextState = try await NextStateHelper.nextStateAsync(
service: self.service,
@ -1045,9 +808,9 @@ public class CompactBlockProcessor {
}
}
private func validationFailed(at height: BlockHeight) {
internal func validationFailed(at height: BlockHeight) {
// cancel all Tasks
operationQueue.cancelAllOperations()
cancelableTask?.cancel()
// register latest failure
self.lastChainValidationFailure = height
@ -1078,13 +841,13 @@ public class CompactBlockProcessor {
)
// process next batch
self.nextBatchTask()
self.nextBatch()
} catch {
self.fail(error)
}
}
private func processBatchFinished(range: CompactBlockRange) {
internal func processBatchFinished(range: CompactBlockRange) {
guard processingError == nil else {
retryProcessing(range: range)
return
@ -1098,7 +861,7 @@ public class CompactBlockProcessor {
return
}
nextBatchTask()
nextBatch()
}
private func processingFinished(height: BlockHeight) {
@ -1110,7 +873,7 @@ public class CompactBlockProcessor {
CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks
]
)
self.state = .synced
setState(.synced)
setTimer()
}
@ -1292,33 +1055,27 @@ extension CompactBlockProcessor {
}
extension CompactBlockProcessor {
func refreshUTXOs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
func refreshUTXOs(tAddress: String, startHeight: BlockHeight) async throws -> RefreshedUTXOs {
let dataDb = self.config.dataDb
self.downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) { [weak self] fetchResult in
switch fetchResult {
case .success(let utxos):
DispatchQueue.main.async {
self?.operationQueue.addOperation { [self] in
guard let self = self else { return }
do {
guard try self.rustBackend.clearUtxos(
dbData: dataDb,
address: tAddress,
sinceHeight: startHeight - 1,
networkType: self.config.network.networkType
) >= 0 else {
result(.failure(CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned")))
return
}
} catch {
result(.failure(self.mapError(error)))
}
result(.success(self.storeUTXOs(utxos, in: dataDb)))
}
}
case .failure(let error):
result(.failure(self?.mapError(error) ?? error))
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight)
var utxos: [UnspentTransactionOutputEntity] = []
do {
for try await utxo in stream {
utxos.append(utxo)
}
guard try rustBackend.clearUtxos(
dbData: dataDb,
address: tAddress,
sinceHeight: startHeight - 1,
networkType: self.config.network.networkType
) >= 0 else {
throw CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned")
}
return storeUTXOs(utxos, in: dataDb)
} catch {
throw mapError(error)
}
}
@ -1380,6 +1137,7 @@ extension CompactBlockProcessorError: LocalizedError {
case let .wrongConsensusBranchId(expectedLocally, found):
// swiftlint:disable:next line_length
return "The remote server you are connecting to is publishing a different branch ID \(found) than the one your App is expecting to be (\(expectedLocally)). This could be caused by your App being out of date or the server you are connecting you being either on a different network or out of date after a network upgrade."
case .unknown: return "Unknown error occured."
}
}
@ -1399,12 +1157,6 @@ extension CompactBlockProcessorError: LocalizedError {
}
}
extension CompactBlockProcessor: CompactBlockProgressDelegate {
func progressUpdated(_ progress: CompactBlockProgress) {
notifyProgress(progress)
}
}
extension CompactBlockProcessor: EnhancementStreamDelegate {
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) {
NotificationCenter.default.post(
@ -1417,38 +1169,12 @@ extension CompactBlockProcessor: EnhancementStreamDelegate {
extension CompactBlockProcessor {
enum NextStateHelper {
// swiftlint:disable:next function_parameter_count
static func nextState(
service: LightWalletService,
downloader: CompactBlockDownloading,
config: Configuration,
rustBackend: ZcashRustBackendWelding.Type,
queue: DispatchQueue?,
result: @escaping (Result<FigureNextBatchOperation.NextState, Error>) -> Void
) {
let dispatchQueue = queue ?? DispatchQueue.global(qos: .userInitiated)
dispatchQueue.async {
do {
let nextResult = try self.nextState(
service: service,
downloader: downloader,
config: config,
rustBackend: rustBackend
)
result(.success(nextResult))
} catch {
result(.failure(error))
}
}
}
static func nextStateAsync(
service: LightWalletService,
downloader: CompactBlockDownloading,
config: Configuration,
rustBackend: ZcashRustBackendWelding.Type
) async throws -> FigureNextBatchOperation.NextState {
) async throws -> NextState {
let task = Task(priority: .userInitiated) {
// TODO: refactor to async call, issue 463, PR 493
// https://github.com/zcash/ZcashLightClientKit/issues/463
@ -1467,7 +1193,7 @@ extension CompactBlockProcessor {
downloader: CompactBlockDownloading,
config: Configuration,
rustBackend: ZcashRustBackendWelding.Type
) throws -> FigureNextBatchOperation.NextState {
) throws -> NextState {
let info = try service.getInfo()
try CompactBlockProcessor.validateServerInfo(

View File

@ -0,0 +1,183 @@
//
// CompactBlockProcessing.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/15/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
extension CompactBlockProcessor {
func compactBlockBatchScanning(range: CompactBlockRange) async throws {
try Task.checkCancellation()
setState(.scanning)
let batchSize = UInt32(config.scanningBatchSize)
do {
if batchSize == 0 {
let scanStartTime = Date()
guard self.rustBackend.scanBlocks(dbCache: config.cacheDb, dbData: config.dataDb, limit: batchSize, networkType: config.network.networkType) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
let scanFinishTime = Date()
NotificationCenter.default.post(
SDKMetrics.progressReportNotification(
progress: BlockProgress(
startHeight: range.lowerBound,
targetHeight: range.upperBound,
progressHeight: range.upperBound
),
start: scanStartTime,
end: scanFinishTime,
task: .scanBlocks
)
)
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(range.count) blocks in \(seconds) seconds")
} else {
let scanStartHeight = try transactionRepository.lastScannedHeight()
let targetScanHeight = range.upperBound
var scannedNewBlocks = false
var lastScannedHeight = scanStartHeight
repeat {
try Task.checkCancellation()
let previousScannedHeight = lastScannedHeight
let scanStartTime = Date()
guard self.rustBackend.scanBlocks(
dbCache: config.cacheDb,
dbData: config.dataDb,
limit: batchSize,
networkType: config.network.networkType
) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
let scanFinishTime = Date()
lastScannedHeight = try transactionRepository.lastScannedHeight()
scannedNewBlocks = previousScannedHeight != lastScannedHeight
if scannedNewBlocks {
let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight)
notifyProgress(.scan(progress))
NotificationCenter.default.post(
SDKMetrics.progressReportNotification(
progress: progress,
start: scanStartTime,
end: scanFinishTime,
task: .scanBlocks
)
)
let heightCount = lastScannedHeight - previousScannedHeight
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
}
} while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight
if Task.isCancelled {
setState(.stopped)
LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled")
}
}
} catch {
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
}
}
extension CompactBlockProcessor {
func compactBlockScanning(
rustWelding: ZcashRustBackendWelding.Type,
cacheDb: URL,
dataDb: URL,
limit: UInt32 = 0,
networkType: NetworkType
) throws {
try Task.checkCancellation()
guard rustBackend.scanBlocks(dbCache: cacheDb, dbData: dataDb, limit: limit, networkType: networkType) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
}
}
public enum SDKMetrics {
struct BlockMetricReport {
var startHeight: BlockHeight
var targetHeight: BlockHeight
var duration: TimeInterval
var task: TaskReported
}
enum TaskReported: String {
case scanBlocks
}
static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey"
static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey"
static let progressHeightKey = "SDKMetrics.progressHeight"
static let startDateKey = "SDKMetrics.startDateKey"
static let endDateKey = "SDKMetrics.endDateKey"
static let taskReportedKey = "SDKMetrics.taskReported"
static let notificationName = Notification.Name("SDKMetrics.Notification")
static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? {
guard
notification.name == notificationName,
let info = notification.userInfo,
let startHeight = info[startBlockHeightKey] as? BlockHeight,
let targetHeight = info[targetBlockHeightKey] as? BlockHeight,
let task = info[taskReportedKey] as? TaskReported,
let startDate = info[startDateKey] as? Date,
let endDate = info[endDateKey] as? Date
else {
return nil
}
return BlockMetricReport(
startHeight: startHeight,
targetHeight: targetHeight,
duration: abs(
startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate
),
task: task
)
}
static func progressReportNotification(
progress: BlockProgress,
start: Date,
end: Date,
task: SDKMetrics.TaskReported
) -> Notification {
var notification = Notification(name: notificationName)
notification.userInfo = [
startBlockHeightKey: progress.startHeight,
targetBlockHeightKey: progress.targetHeight,
progressHeightKey: progress.progressHeight,
startDateKey: start,
endDateKey: end,
taskReportedKey: task
]
return notification
}
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) {
let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds"
appendLiteral(literal)
}
}

View File

@ -1,252 +0,0 @@
//
// CompactBlockProcessingOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/15/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
class CompactBlockScanningOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var rustBackend: ZcashRustBackendWelding.Type
private var cacheDb: URL
private var dataDb: URL
private var limit: UInt32
private var network: NetworkType
init(rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, dataDb: URL, limit: UInt32 = 0, networkType: NetworkType) {
rustBackend = rustWelding
self.cacheDb = cacheDb
self.dataDb = dataDb
self.limit = limit
self.network = networkType
super.init()
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: limit, networkType: network) else {
self.error = self.rustBackend.lastError() ?? ZcashOperationError.unknown
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
self.fail()
return
}
}
}
public enum SDKMetrics {
struct BlockMetricReport {
var startHeight: BlockHeight
var targetHeight: BlockHeight
var duration: TimeInterval
var task: TaskReported
}
enum TaskReported: String {
case scanBlocks
}
static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey"
static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey"
static let progressHeightKey = "SDKMetrics.progressHeight"
static let startDateKey = "SDKMetrics.startDateKey"
static let endDateKey = "SDKMetrics.endDateKey"
static let taskReportedKey = "SDKMetrics.taskReported"
static let notificationName = Notification.Name("SDKMetrics.Notification")
static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? {
guard
notification.name == notificationName,
let info = notification.userInfo,
let startHeight = info[startBlockHeightKey] as? BlockHeight,
let targetHeight = info[targetBlockHeightKey] as? BlockHeight,
let task = info[taskReportedKey] as? TaskReported,
let startDate = info[startDateKey] as? Date,
let endDate = info[endDateKey] as? Date
else {
return nil
}
return BlockMetricReport(
startHeight: startHeight,
targetHeight: targetHeight,
duration: abs(
startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate
),
task: task
)
}
static func progressReportNotification(
progress: BlockProgress,
start: Date,
end: Date,
task: SDKMetrics.TaskReported
) -> Notification {
var notification = Notification(name: notificationName)
notification.userInfo = [
startBlockHeightKey: progress.startHeight,
targetBlockHeightKey: progress.targetHeight,
progressHeightKey: progress.progressHeight,
startDateKey: start,
endDateKey: end,
taskReportedKey: task
]
return notification
}
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) {
let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds"
appendLiteral(literal)
}
}
class CompactBlockBatchScanningOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var rustBackend: ZcashRustBackendWelding.Type
private var cacheDb: URL
private var dataDb: URL
private var batchSize: UInt32
private var blockRange: CompactBlockRange
private var transactionRepository: TransactionRepository
private var network: NetworkType
private var cancelableTask: Task<Void, Error>?
private var done = false
private weak var progressDelegate: CompactBlockProgressDelegate?
init(
rustWelding: ZcashRustBackendWelding.Type,
cacheDb: URL,
dataDb: URL,
transactionRepository: TransactionRepository,
range: CompactBlockRange,
batchSize: UInt32,
networkType: NetworkType,
progressDelegate: CompactBlockProgressDelegate? = nil
) {
rustBackend = rustWelding
self.cacheDb = cacheDb
self.dataDb = dataDb
self.transactionRepository = transactionRepository
self.blockRange = range
self.batchSize = batchSize
self.progressDelegate = progressDelegate
self.network = networkType
super.init()
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
do {
if batchSize == 0 {
let scanStartTime = Date()
guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else {
self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown)
return
}
let scanFinishTime = Date()
NotificationCenter.default.post(
SDKMetrics.progressReportNotification(
progress: BlockProgress(
startHeight: self.blockRange.lowerBound,
targetHeight: self.blockRange.upperBound,
progressHeight: self.blockRange.upperBound
),
start: scanStartTime,
end: scanFinishTime,
task: .scanBlocks
)
)
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds")
} else {
let scanStartHeight = try transactionRepository.lastScannedHeight()
let targetScanHeight = blockRange.upperBound
var scannedNewBlocks = false
var lastScannedHeight = scanStartHeight
repeat {
guard !shouldCancel() else {
cancel()
return
}
let previousScannedHeight = lastScannedHeight
let scanStartTime = Date()
guard self.rustBackend.scanBlocks(
dbCache: self.cacheDb,
dbData: self.dataDb,
limit: batchSize,
networkType: network
) else {
self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown)
return
}
let scanFinishTime = Date()
lastScannedHeight = try transactionRepository.lastScannedHeight()
scannedNewBlocks = previousScannedHeight != lastScannedHeight
if scannedNewBlocks {
let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight)
progressDelegate?.progressUpdated(.scan(progress))
NotificationCenter.default.post(
SDKMetrics.progressReportNotification(
progress: progress,
start: scanStartTime,
end: scanFinishTime,
task: .scanBlocks
)
)
let heightCount = lastScannedHeight - previousScannedHeight
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
}
} while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight
self.done = true
}
} catch {
scanFailed(error)
}
}
while !done && !isCancelled {
sleep(1)
}
}
func scanFailed(_ error: Error) {
self.cancelableTask?.cancel()
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
super.fail(error: error)
}
override func cancel() {
self.cancelableTask?.cancel()
super.cancel()
}
}

View File

@ -8,78 +8,58 @@
import Foundation
enum CompactBlockValidationError: Error {
case validationFailed(height: BlockHeight)
case failedWithError(_ error: Error?)
}
class CompactBlockValidationOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var rustBackend: ZcashRustBackendWelding.Type
private var cacheDb: URL
private var dataDb: URL
private var network: NetworkType
private var cancelableTask: Task<Void, Error>?
private var done = false
init(
rustWelding: ZcashRustBackendWelding.Type,
cacheDb: URL,
dataDb: URL,
networkType: NetworkType
) {
rustBackend = rustWelding
self.cacheDb = cacheDb
self.dataDb = dataDb
self.network = networkType
super.init()
extension CompactBlockProcessor {
enum CompactBlockValidationError: Error {
case validationFailed(height: BlockHeight)
case failedWithError(_ error: Error?)
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
func compactBlockValidation() async throws {
try Task.checkCancellation()
setState(.validating)
cancelableTask = Task {
let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network)
let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType)
do {
switch result {
case 0:
let error = CompactBlockValidationError.failedWithError(rustBackend.lastError())
self.error = error
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
self.fail(error: error)
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
case ZcashRustBackendWeldingConstants.validChain:
self.done = true
if Task.isCancelled {
setState(.stopped)
LoggerProxy.debug("Warning: compactBlockValidation cancelled")
}
LoggerProxy.debug("validateChainFinished")
break
default:
let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result))
self.error = error
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
self.fail(error: error)
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
} catch {
guard let validationError = error as? CompactBlockValidationError else {
LoggerProxy.error("Warning: compactBlockValidation returning generic error: \(error)")
return
}
switch validationError {
case .validationFailed(let height):
LoggerProxy.debug("chain validation at height: \(height)")
validationFailed(at: height)
case .failedWithError(let err):
guard let validationFailure = err else {
LoggerProxy.error("validation failed without a specific error")
self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error"))
return
}
throw validationFailure
}
}
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

@ -0,0 +1,81 @@
//
// FetchUnspentTxOutputs.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 6/2/21.
//
import Foundation
extension CompactBlockProcessor {
enum FetchUTXOError: Error {
case clearingFailed(_ error: Error?)
case fetchFailed(error: Error)
}
func fetchUnspentTxOutputs(range: CompactBlockRange) async throws {
try Task.checkCancellation()
setState(.fetching)
do {
let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress })
do {
for tAddress in tAddresses {
guard try rustBackend.clearUtxos(
dbData: config.dataDb,
address: tAddress,
sinceHeight: config.walletBirthday - 1,
networkType: config.network.networkType
) >= 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned")
}
}
} catch {
throw FetchUTXOError.clearingFailed(error)
}
var utxos: [UnspentTransactionOutputEntity] = []
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: config.walletBirthday)
for try await transaction in stream {
utxos.append(transaction)
}
var refreshed: [UnspentTransactionOutputEntity] = []
var skipped: [UnspentTransactionOutputEntity] = []
for utxo in utxos {
do {
try rustBackend.putUnspentTransparentOutput(
dbData: config.dataDb,
txid: utxo.txid.bytes,
index: utxo.index,
script: utxo.script.bytes,
value: Int64(utxo.valueZat),
height: utxo.height,
networkType: config.network.networkType
) ? refreshed.append(utxo) : skipped.append(utxo)
} catch {
LoggerProxy.error("failed to put utxo - error: \(error)")
skipped.append(utxo)
}
}
let result = (inserted: refreshed, skipped: skipped)
NotificationCenter.default.post(
name: .blockProcessorStoredUTXOs,
object: self,
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
)
if Task.isCancelled {
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
} else {
processBatchFinished(range: range)
}
} catch {
throw error
}
}
}

View File

@ -1,125 +0,0 @@
//
// FetchUnspentTxOutputsOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 6/2/21.
//
import Foundation
class FetchUnspentTxOutputsOperation: ZcashOperation {
enum FetchUTXOError: Error {
case clearingFailed(_ error: Error?)
case fetchFailed(error: Error)
}
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var fetchedUTXOsHandler: ((RefreshedUTXOs) -> Void)?
private var accountRepository: AccountRepository
private var downloader: CompactBlockDownloading
private var rustbackend: ZcashRustBackendWelding.Type
private var startHeight: BlockHeight
private var network: NetworkType
private var dataDb: URL
private var cancelableTask: Task<Void, Error>?
private var done = false
init(
accountRepository: AccountRepository,
downloader: CompactBlockDownloading,
rustbackend: ZcashRustBackendWelding.Type,
dataDb: URL,
startHeight: BlockHeight,
networkType: NetworkType
) {
self.dataDb = dataDb
self.accountRepository = accountRepository
self.downloader = downloader
self.rustbackend = rustbackend
self.startHeight = startHeight
self.network = networkType
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
cancelableTask = Task {
do {
let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress })
do {
for tAddress in tAddresses {
guard try self.rustbackend.clearUtxos(
dbData: dataDb,
address: tAddress,
sinceHeight: startHeight - 1,
networkType: network
) >= 0 else {
throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned")
}
}
} catch {
throw FetchUTXOError.clearingFailed(error)
}
var utxos: [UnspentTransactionOutputEntity] = []
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight)
for try await transaction in stream {
utxos.append(transaction)
}
let result = storeUTXOs(utxos, in: dataDb)
self.fetchedUTXOsHandler?(result)
self.done = true
} catch {
self.fail(error: error)
}
}
while !done && !isCancelled {
sleep(1)
}
}
private func storeUTXOs(_ utxos: [UnspentTransactionOutputEntity], in dataDb: URL) -> RefreshedUTXOs {
var refreshed: [UnspentTransactionOutputEntity] = []
var skipped: [UnspentTransactionOutputEntity] = []
for utxo in utxos {
do {
try self.rustbackend.putUnspentTransparentOutput(
dbData: dataDb,
txid: utxo.txid.bytes,
index: utxo.index,
script: utxo.script.bytes,
value: Int64(utxo.valueZat),
height: utxo.height,
networkType: network
) ? refreshed.append(utxo) : skipped.append(utxo)
} catch {
LoggerProxy.error("failed to put utxo - error: \(error)")
skipped.append(utxo)
}
}
return (inserted: refreshed, skipped: skipped)
}
override func fail(error: Error? = nil) {
self.cancelableTask?.cancel()
super.fail(error: error)
}
override func cancel() {
self.cancelableTask?.cancel()
super.cancel()
}
}

View File

@ -0,0 +1,34 @@
//
// FigureNextBatch.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 6/17/21.
//
import Foundation
extension CompactBlockProcessor {
enum NextState {
case finishProcessing(height: BlockHeight)
case processNewBlocks(range: CompactBlockRange)
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
}
@discardableResult
func figureNextBatch(
downloader: CompactBlockDownloading
) async throws -> NextState {
try Task.checkCancellation()
do {
return try await CompactBlockProcessor.NextStateHelper.nextStateAsync(
service: service,
downloader: downloader,
config: config,
rustBackend: rustBackend
)
} catch {
throw error
}
}
}

View File

@ -1,58 +0,0 @@
//
// FigureNextBatchOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 6/17/21.
//
import Foundation
class FigureNextBatchOperation: ZcashOperation {
enum NextState {
case finishProcessing(height: BlockHeight)
case processNewBlocks(range: CompactBlockRange)
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
}
private var service: LightWalletService
private var downloader: CompactBlockDownloading
private var config: CompactBlockProcessor.Configuration
private var rustBackend: ZcashRustBackendWelding.Type
private(set) var result: NextState?
required init(
downloader: CompactBlockDownloading,
service: LightWalletService,
config: CompactBlockProcessor.Configuration,
rustBackend: ZcashRustBackendWelding.Type
) {
self.service = service
self.config = config
self.downloader = downloader
self.rustBackend = rustBackend
super.init()
self.name = "Next Batch Operation"
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
do {
result = try CompactBlockProcessor.NextStateHelper.nextState(
service: self.service,
downloader: self.downloader,
config: self.config,
rustBackend: self.rustBackend
)
} catch {
self.fail(error: error)
}
}
}

View File

@ -1,68 +0,0 @@
//
// ZcashOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/27/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
typealias ZcashOperationCompletionBlock = (_ finished: Bool, _ cancelled: Bool) -> Void
typealias ZcashOperationStartedBlock = () -> Void
typealias ZcashOperationErrorBlock = (_ error: Error) -> Void
enum ZcashOperationError: Error {
case unknown
}
class ZcashOperation: Operation {
var error: Error?
var startedHandler: ZcashOperationStartedBlock?
var errorHandler: ZcashOperationErrorBlock?
var completionHandler: ZcashOperationCompletionBlock?
var handlerDispatchQueue = DispatchQueue.main
override init() {
super.init()
completionBlock = { [weak self] in
guard let self = self, let handler = self.completionHandler else { return }
handler(self.isFinished, self.isCancelled)
}
}
convenience init(completionDispatchQueue: DispatchQueue = DispatchQueue.main) {
self.init()
self.handlerDispatchQueue = completionDispatchQueue
}
func shouldCancel() -> Bool {
self.error != nil || isCancelled || dependencyCancelled()
}
func dependencyCancelled() -> Bool {
self.dependencies.first { $0.isCancelled } != nil
}
func fail(error: Error? = nil) {
defer {
self.cancel()
}
if let error = error {
self.error = error
}
LoggerProxy.debug("\(self) failed")
guard let errorHandler = self.errorHandler else {
return
}
self.handlerDispatchQueue.async { [weak self] in
let error = error ?? (self?.error ?? ZcashOperationError.unknown)
errorHandler(error)
}
}
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1787500",
"hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa",
"time": 1661588407,
"saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1790000",
"hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4",
"time": 1661777577,
"saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1792500",
"hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48",
"time": 1661965969,
"saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1795000",
"hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1",
"time": 1662152700,
"saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1797500",
"hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0",
"time": 1662341718,
"saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1800000",
"hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961",
"time": 1662529994,
"saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1802500",
"hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91",
"time": 1662718546,
"saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1805000",
"hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb",
"time": 1662907506,
"saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -369,7 +369,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
}
continuation.finish(throwing: nil)
} catch {
continuation.finish(throwing: error)
continuation.finish(throwing: error.mapToServiceError())
}
}
}
@ -551,7 +551,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
}
continuation.finish(throwing: nil)
} catch {
continuation.finish(throwing: error)
continuation.finish(throwing: error.mapToServiceError())
}
}
}
@ -619,7 +619,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
}
continuation.finish(throwing: nil)
} catch {
continuation.finish(throwing: error)
continuation.finish(throwing: error.mapToServiceError())
}
}
}

View File

@ -175,7 +175,7 @@ public protocol Synchronizer {
func latestHeight() throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void)
func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs
/// Returns the last stored transparent balance
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance

View File

@ -194,7 +194,7 @@ public class SDKSynchronizer: Synchronizer {
return
}
blockProcessor.stop(cancelTasks: true)
blockProcessor.stop()
self.status = .stopped
}
@ -639,8 +639,8 @@ public class SDKSynchronizer: Synchronizer {
}
}
public func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result)
public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs {
try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
}
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
@ -856,6 +856,7 @@ public class SDKSynchronizer: Synchronizer {
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
case .saplingActivationMismatch:
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
case .unknown: break
}
}

View File

@ -1,270 +0,0 @@
//
// BlockScanOperationTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/17/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
import SQLite
@testable import TestUtils
@testable import ZcashLightClientKit
// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage
class BlockScanOperationTests: XCTestCase {
let rustWelding = ZcashRustBackend.self
var operationQueue = OperationQueue()
var cacheDbURL: URL!
var dataDbURL: URL!
var ufvk = UnifiedFullViewingKey(
validatedEncoding: "uviewtest1q48t999peecrfkq7ykcxckfkjt77w3lckk5mptlrtuy7xltjnzg8fm5434cxe9p9838ljs24yv83rluhk33ew098dkarapzyj4vk5kfxp5zn2jp3ww74jwd48r05aqjvgqxzx3nqn6zfqh3cmwdtmz0mc5624tvdza55q7mguxrehwcy4y0uktcpp4tkpex4qhazddux4yt6hr0sc9fkqmfr5tyz6ldd7yrq93tyj7446u4kst3vhmd40uga636p56hr0hjfdhgp07qyh90kmsl3qnmld6c8h7u06vekkjywmxv07mqzz9muwcl6weczrn5vf3p27uc9ufrumdp64zdzulzvc373wx3gl0yntntujhcsjhrwk9xwyjpvyuf0s8q3mgjs7uy3pg960w40dthpngcnauhgg9xq8cdcyfkq7ctnngqg4nkp5eh9knd4ckwjyd9czdd240lumul96r2fuerlvjeha6cyn9ftm7gr6xqjmq0zy6tv", // swiftlint:disable:this line_length
account: 0
)
var walletBirthDay = Checkpoint.birthday(
with: 1386000,
network: ZcashNetworkBuilder.network(for: .testnet)
)
var network = ZcashNetworkBuilder.network(for: .testnet)
var blockRepository: BlockRepository!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
self.cacheDbURL = try! __cacheDbURL()
self.dataDbURL = try! __dataDbURL()
deleteDBs()
operationQueue.maxConcurrentOperationCount = 1
}
private func deleteDBs() {
try? FileManager.default.removeItem(at: cacheDbURL)
try? FileManager.default.removeItem(at: dataDbURL)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
operationQueue.cancelAllOperations()
try? FileManager.default.removeItem(at: cacheDbURL)
try? FileManager.default.removeItem(at: dataDbURL)
}
func testSingleDownloadAndScanOperation() {
logger = SampleLogger(logLevel: .debug)
var dbInit: DbInitResult!
XCTAssertNoThrow(try { dbInit = try ZcashRustBackend.initDataDb(dbData: self.dataDbURL, seed: nil, networkType: .testnet) }())
guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))")
return
}
let downloadStartedExpect = XCTestExpectation(description: "\(self.description) download started")
let downloadExpect = XCTestExpectation(description: "\(self.description) download")
let scanStartedExpect = XCTestExpectation(description: "\(self.description) scan started")
let scanExpect = XCTestExpectation(description: "\(self.description) scan")
let latestScannedBlockExpect = XCTestExpectation(description: "\(self.description) latestScannedHeight")
let service = LightWalletGRPCService(
endpoint: LightWalletEndpoint(
address: "lightwalletd.testnet.electriccoin.co",
port: 9067
)
)
let blockCount = 100
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
let downloadOperation = CompactBlockDownloadOperation(
downloader: CompactBlockDownloader.sqlDownloader(
service: service,
at: cacheDbURL
)!,
range: range
)
let scanOperation = CompactBlockScanningOperation(
rustWelding: rustWelding,
cacheDb: cacheDbURL,
dataDb: dataDbURL,
networkType: network.networkType
)
downloadOperation.startedHandler = {
downloadStartedExpect.fulfill()
}
downloadOperation.completionHandler = { finished, cancelled in
downloadExpect.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
downloadOperation.errorHandler = { error in
XCTFail("Download Operation failed with Error: \(error)")
}
scanOperation.startedHandler = {
scanStartedExpect.fulfill()
}
scanOperation.completionHandler = { finished, cancelled in
scanExpect.fulfill()
XCTAssertFalse(cancelled)
XCTAssertTrue(finished)
}
scanOperation.errorHandler = { error in
XCTFail("Scan Operation failed with Error: \(error)")
}
scanOperation.addDependency(downloadOperation)
var latestScannedheight = BlockHeight.empty()
let latestScannedBlockOperation = BlockOperation {
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
latestScannedheight = repository.lastScannedBlockHeight()
}
latestScannedBlockOperation.completionBlock = {
latestScannedBlockExpect.fulfill()
XCTAssertEqual(latestScannedheight, range.upperBound)
}
latestScannedBlockOperation.addDependency(scanOperation)
operationQueue.addOperations(
[downloadOperation, scanOperation, latestScannedBlockOperation],
waitUntilFinished: false
)
wait(
for: [downloadStartedExpect, downloadExpect, scanStartedExpect, scanExpect, latestScannedBlockExpect],
timeout: 10,
enforceOrder: true
)
}
@objc func observeBenchmark(_ notification: Notification) {
guard let report = SDKMetrics.blockReportFromNotification(notification) else {
return
}
print("observed benchmark: \(report)")
}
func testScanValidateDownload() throws {
logger = SampleLogger(logLevel: .debug)
NotificationCenter.default.addObserver(
self,
selector: #selector(observeBenchmark(_:)),
name: SDKMetrics.notificationName,
object: nil
)
let dbInit = try self.rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType)
guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)")
return
}
guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, ufvks: [ufvk], networkType: network.networkType) else {
XCTFail("failed to init account table")
return
}
try self.rustWelding.initBlocksTable(
dbData: dataDbURL,
height: Int32(walletBirthDay.height),
hash: walletBirthDay.hash,
time: walletBirthDay.time,
saplingTree: walletBirthDay.saplingTree,
networkType: network.networkType
)
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
try storage.createTable()
let downloadExpectation = XCTestExpectation(description: "download expectation")
let validateExpectation = XCTestExpectation(description: "validate expectation")
let scanExpectation = XCTestExpectation(description: "scan expectation")
let downloadOperation = CompactBlockStreamDownloadOperation(
service: service,
storage: storage,
blockBufferSize: 10,
startHeight: walletBirthDay.height,
targetHeight: walletBirthDay.height + 10000,
progressDelegate: self
)
downloadOperation.completionHandler = { finished, cancelled in
XCTAssert(finished)
XCTAssertFalse(cancelled)
downloadExpectation.fulfill()
}
downloadOperation.errorHandler = { error in
if let lwdError = error as? LightWalletServiceError {
switch lwdError {
case .timeOut:
XCTAssert(true)
default:
XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)")
}
} else {
XCTFail("Error should have been a timeLimit reached Error - \(error)")
}
}
let validationOperation = CompactBlockValidationOperation(
rustWelding: rustWelding,
cacheDb: cacheDbURL,
dataDb: dataDbURL,
networkType: network.networkType
)
validationOperation.errorHandler = { error in
self.operationQueue.cancelAllOperations()
XCTFail("failed with error \(error)")
}
validationOperation.completionHandler = { finished, cancelled in
XCTAssert(finished)
XCTAssertFalse(cancelled)
validateExpectation.fulfill()
}
let transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL)
let scanningOperation = CompactBlockBatchScanningOperation(
rustWelding: rustWelding,
cacheDb: cacheDbURL,
dataDb: dataDbURL,
transactionRepository: transactionRepository,
range: CompactBlockRange(
uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)
),
batchSize: 1000,
networkType: network.networkType,
progressDelegate: self
)
scanningOperation.completionHandler = { finished, cancelled in
XCTAssert(finished)
XCTAssertFalse(cancelled)
scanExpectation.fulfill()
}
operationQueue.addOperations([downloadOperation, validationOperation, scanningOperation], waitUntilFinished: false)
wait(for: [downloadExpectation, validateExpectation, scanExpectation], timeout: 300, enforceOrder: true)
}
}
extension BlockScanOperationTests: CompactBlockProgressDelegate {
func progressUpdated(_ progress: CompactBlockProgress) {
}
}

View File

@ -0,0 +1,191 @@
//
// BlockScanTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/17/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
import SQLite
@testable import TestUtils
@testable import ZcashLightClientKit
// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage
class BlockScanTests: XCTestCase {
let rustWelding = ZcashRustBackend.self
var cacheDbURL: URL!
var dataDbURL: URL!
var saplingExtendedKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u")
var walletBirthDay = Checkpoint.birthday(
with: 1386000,
network: ZcashNetworkBuilder.network(for: .testnet)
)
var network = ZcashNetworkBuilder.network(for: .testnet)
var blockRepository: BlockRepository!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
self.cacheDbURL = try! __cacheDbURL()
self.dataDbURL = try! __dataDbURL()
deleteDBs()
}
private func deleteDBs() {
try? FileManager.default.removeItem(at: cacheDbURL)
try? FileManager.default.removeItem(at: dataDbURL)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
try? FileManager.default.removeItem(at: cacheDbURL)
try? FileManager.default.removeItem(at: dataDbURL)
}
func testSingleDownloadAndScan() async throws {
logger = SampleLogger(logLevel: .debug)
XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType))
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let service = LightWalletGRPCService(
endpoint: LightWalletEndpoint(
address: "lightwalletd.testnet.electriccoin.co",
port: 9067
)
)
let blockCount = 100
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
let downloader = CompactBlockDownloader.sqlDownloader(
service: service,
at: cacheDbURL
)!
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: network,
walletBirthday: network.constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: ZcashRustBackend.self,
config: processorConfig
)
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
var latestScannedheight = BlockHeight.empty()
do {
try await compactBlockProcessor.compactBlockDownload(
downloader: downloader,
range: range
)
XCTAssertFalse(Task.isCancelled)
try compactBlockProcessor.compactBlockScanning(
rustWelding: rustWelding,
cacheDb: cacheDbURL,
dataDb: dataDbURL,
networkType: network.networkType
)
latestScannedheight = repository.lastScannedBlockHeight()
XCTAssertEqual(latestScannedheight, range.upperBound)
} catch {
XCTFail("Download failed with error: \(error)")
}
}
@objc func observeBenchmark(_ notification: Notification) {
guard let report = SDKMetrics.blockReportFromNotification(notification) else {
return
}
print("observed benchmark: \(report)")
}
func testScanValidateDownload() async throws {
logger = SampleLogger(logLevel: .debug)
NotificationCenter.default.addObserver(
self,
selector: #selector(observeBenchmark(_:)),
name: SDKMetrics.notificationName,
object: nil
)
guard try self.rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType) == .success else {
XCTFail("Seed should not be required for this test")
return
}
let uvks = try DerivationTool(networkType: .testnet).deriveUnifiedFullViewingKeys(seed: TestSeed().seed(), numberOfAccounts: 1)
guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, ufvks: uvks, networkType: network.networkType) else {
XCTFail("failed to init account table")
return
}
try self.rustWelding.initBlocksTable(
dbData: dataDbURL,
height: Int32(walletBirthDay.height),
hash: walletBirthDay.hash,
time: walletBirthDay.time,
saplingTree: walletBirthDay.saplingTree,
networkType: network.networkType
)
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
try storage.createTable()
var processorConfig = CompactBlockProcessor.Configuration(
cacheDb: cacheDbURL,
dataDb: dataDbURL,
walletBirthday: network.constants.saplingActivationHeight,
network: network
)
processorConfig.scanningBatchSize = 1000
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: rustWelding,
config: processorConfig
)
let range = CompactBlockRange(
uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)
)
do {
try await compactBlockProcessor.compactBlockStreamDownload(
blockBufferSize: 10,
startHeight: range.lowerBound,
targetHeight: range.upperBound
)
XCTAssertFalse(Task.isCancelled)
try await compactBlockProcessor.compactBlockValidation()
XCTAssertFalse(Task.isCancelled)
try await compactBlockProcessor.compactBlockBatchScanning(range: range)
XCTAssertFalse(Task.isCancelled)
} catch {
if let lwdError = error as? LightWalletServiceError {
switch lwdError {
case .timeOut:
XCTAssert(true)
default:
XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)")
}
} else {
XCTFail("Error should have been a timeLimit reached Error - \(error)")
}
}
}
}

View File

@ -11,12 +11,6 @@ import XCTest
// swiftlint:disable print_function_usage
class BlockStreamingTest: XCTestCase {
var queue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
override func setUpWithError() throws {
try super.setUpWithError()
logger = SampleLogger(logLevel: .debug)
@ -27,7 +21,7 @@ class BlockStreamingTest: XCTestCase {
try? FileManager.default.removeItem(at: __dataDbURL())
}
func testStreamOperation() throws {
func testStream() throws {
let expectation = XCTestExpectation(description: "blockstream expectation")
let service = LightWalletGRPCService(
@ -61,9 +55,7 @@ class BlockStreamingTest: XCTestCase {
wait(for: [expectation], timeout: 1000)
}
func testStreamOperationCancellation() throws {
let expectation = XCTestExpectation(description: "blockstream expectation")
func testStreamCancellation() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
@ -71,37 +63,38 @@ class BlockStreamingTest: XCTestCase {
singleCallTimeout: 10000,
streamingCallTimeout: 10000
)
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
let startHeight = try service.latestBlockHeight() - 100_000
let operation = CompactBlockStreamDownloadOperation(
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
blockBufferSize: 10,
startHeight: startHeight,
progressDelegate: self
backend: ZcashRustBackend.self,
config: processorConfig
)
operation.completionHandler = { _, cancelled in
XCTAssert(cancelled)
expectation.fulfill()
let cancelableTask = Task {
do {
try await compactBlockProcessor.compactBlockStreamDownload(
blockBufferSize: 10,
startHeight: startHeight
)
XCTAssertTrue(Task.isCancelled)
} catch {
XCTFail("failed with error: \(error)")
}
}
operation.errorHandler = { error in
XCTFail("failed with error: \(error)")
expectation.fulfill()
}
queue.addOperation(operation)
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
self.queue.cancelAllOperations()
})
wait(for: [expectation], timeout: 1000)
try await Task.sleep(nanoseconds: 3_000_000_000)
cancelableTask.cancel()
}
func testStreamOperationTimeout() throws {
let expectation = XCTestExpectation(description: "blockstream expectation")
let errorExpectation = XCTestExpectation(description: "blockstream error expectation")
func testStreamTimeout() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
@ -109,49 +102,49 @@ class BlockStreamingTest: XCTestCase {
singleCallTimeout: 1000,
streamingCallTimeout: 3000
)
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
let startHeight = try service.latestBlockHeight() - 100_000
let operation = CompactBlockStreamDownloadOperation(
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
blockBufferSize: 10,
startHeight: startHeight,
progressDelegate: self
backend: ZcashRustBackend.self,
config: processorConfig
)
operation.completionHandler = { finished, _ in
XCTAssert(finished)
expectation.fulfill()
}
let date = Date()
operation.errorHandler = { error in
do {
try await compactBlockProcessor.compactBlockStreamDownload(
blockBufferSize: 10,
startHeight: startHeight
)
} catch {
if let lwdError = error as? LightWalletServiceError {
switch lwdError {
case .timeOut:
XCTAssert(true)
default:
XCTFail("LWD Service erro found, but should have been a timeLimit reached Error")
XCTFail("LWD Service error found, but should have been a timeLimit reached Error")
}
} else {
XCTFail("Error should have been a timeLimit reached Error")
}
errorExpectation.fulfill()
}
queue.addOperation(operation)
let date = Date()
wait(for: [errorExpectation], timeout: 4)
let now = Date()
let elapsed = now.timeIntervalSince(date)
print("took \(elapsed) seconds")
}
func testBatchOperation() throws {
let expectation = XCTestExpectation(description: "blockbatch expectation")
func testBatch() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
@ -162,34 +155,29 @@ class BlockStreamingTest: XCTestCase {
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
let targetHeight = try service.latestBlockHeight()
let startHeight = targetHeight - 10_000
let operation = CompactBlockBatchDownloadOperation(
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
startHeight: startHeight,
targetHeight: targetHeight,
progressDelegate: self
backend: ZcashRustBackend.self,
config: processorConfig
)
operation.completionHandler = { _, cancelled in
if cancelled {
XCTFail("operation cancelled")
}
expectation.fulfill()
}
operation.errorHandler = { error in
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
do {
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
XCTAssertFalse(Task.isCancelled)
} catch {
XCTFail("failed with error: \(error)")
expectation.fulfill()
}
queue.addOperation(operation)
wait(for: [expectation], timeout: 120)
}
func testBatchOperationCancellation() throws {
let expectation = XCTestExpectation(description: "blockbatch expectation")
func testBatchCancellation() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
@ -200,36 +188,30 @@ class BlockStreamingTest: XCTestCase {
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
let targetHeight = try service.latestBlockHeight()
let startHeight = targetHeight - 100_000
let operation = CompactBlockBatchDownloadOperation(
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
startHeight: startHeight,
targetHeight: targetHeight,
progressDelegate: self
backend: ZcashRustBackend.self,
config: processorConfig
)
operation.completionHandler = { _, cancelled in
XCTAssert(cancelled)
expectation.fulfill()
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
let cancelableTask = Task {
do {
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
XCTAssertTrue(Task.isCancelled)
} catch {
XCTFail("failed with error: \(error)")
}
}
operation.errorHandler = { error in
XCTFail("failed with error: \(error)")
expectation.fulfill()
}
queue.addOperation(operation)
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
self.queue.cancelAllOperations()
})
wait(for: [expectation], timeout: 1000)
}
}
extension BlockStreamingTest: CompactBlockProgressDelegate {
func progressUpdated(_ progress: CompactBlockProgress) {
print("progressHeight: \(String(describing: progress.progressHeight))")
print("startHeight: \(progress.progress)")
print("targetHeight: \(String(describing: progress.targetHeight))")
try await Task.sleep(nanoseconds: 3_000_000_000)
cancelableTask.cancel()
}
}

View File

@ -1,5 +1,5 @@
//
// DownloadOperationTests.swift
// DownloadTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/16/19.
@ -12,40 +12,38 @@ import SQLite
@testable import ZcashLightClientKit
// swiftlint:disable force_try
class DownloadOperationTests: XCTestCase {
var operationQueue = OperationQueue()
class DownloadTests: XCTestCase {
var network = ZcashNetworkBuilder.network(for: .testnet)
override func tearDown() {
super.tearDown()
operationQueue.cancelAllOperations()
}
func testSingleOperation() {
let expect = XCTestExpectation(description: self.description)
func testSingleDownload() async throws {
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let downloader = CompactBlockDownloader(service: service, storage: storage)
let blockCount = 100
let activationHeight = network.constants.saplingActivationHeight
let range = activationHeight ... activationHeight + blockCount
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
downloadOperation.completionHandler = { finished, cancelled in
expect.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: network,
walletBirthday: network.constants.saplingActivationHeight
)
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: ZcashRustBackend.self,
config: processorConfig
)
do {
try await compactBlockProcessor.compactBlockDownload(downloader: downloader, range: range)
} catch {
XCTFail("Download failed with error: \(error)")
}
downloadOperation.errorHandler = { error in
XCTFail("Donwload Operation failed with error: \(error)")
}
operationQueue.addOperation(downloadOperation)
wait(for: [expect], timeout: 10)
XCTAssertEqual(try! storage.latestHeight(), range.upperBound)
}
}

View File

@ -11,29 +11,13 @@ import XCTest
// swiftlint:disable force_try type_body_length
class BlockBatchValidationTests: XCTestCase {
var queue: OperationQueue = {
let queue = OperationQueue()
queue.name = "Test Queue"
queue.maxConcurrentOperationCount = 1
return queue
}()
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testBranchIdFailure() throws {
func testBranchIdFailure() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -59,17 +43,18 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = Int32(0xd34d)
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
do {
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
switch error {
case CompactBlockProcessorError.wrongConsensusBranchId:
break
@ -77,19 +62,15 @@ class BlockBatchValidationTests: XCTestCase {
XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testBranchNetworkMismatchFailure() throws {
func testBranchNetworkMismatchFailure() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -115,17 +96,18 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
do {
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
switch error {
case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet):
break
@ -133,20 +115,15 @@ class BlockBatchValidationTests: XCTestCase {
XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testBranchNetworkTypeWrongFailure() throws {
func testBranchNetworkTypeWrongFailure() async throws {
let network = ZcashNetworkBuilder.network(for: .testnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -172,17 +149,18 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
do {
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
switch error {
case CompactBlockProcessorError.generalError:
break
@ -190,20 +168,15 @@ class BlockBatchValidationTests: XCTestCase {
XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testSaplingActivationHeightMismatch() throws {
func testSaplingActivationHeightMismatch() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -230,17 +203,18 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
do {
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
switch error {
case CompactBlockProcessorError.saplingActivationMismatch(
expected: network.constants.saplingActivationHeight,
@ -251,15 +225,9 @@ class BlockBatchValidationTests: XCTestCase {
XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testResultIsWait() throws {
func testResultIsWait() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let expectedLatestHeight = BlockHeight(1210000)
@ -268,10 +236,11 @@ class BlockBatchValidationTests: XCTestCase {
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
)
let expectedStoreLatestHeight = BlockHeight(1220000)
let expectedResult = FigureNextBatchOperation.NextState.wait(
let expectedResult = CompactBlockProcessor.NextState.wait(
latestHeight: expectedLatestHeight,
latestDownloadHeight: expectedLatestHeight
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -298,50 +267,41 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
var nextBatch: CompactBlockProcessor.NextState?
do {
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
XCTFail("this shouldn't happen: \(error)")
}
operation.completionHandler = { finished, cancelled in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
guard let _ = nextBatch else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue(
{
switch result {
switch nextBatch {
case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight):
return true
default:
return false
}
}(),
"Expected \(expectedResult) got: \(result)"
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
)
}
func testResultProcessNew() throws {
func testResultProcessNew() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
@ -350,13 +310,14 @@ class BlockBatchValidationTests: XCTestCase {
)
let expectedStoreLatestHeight = BlockHeight(1220000)
let walletBirthday = BlockHeight(1210000)
let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks(
let expectedResult = CompactBlockProcessor.NextState.processNewBlocks(
range: CompactBlockProcessor.nextBatchBlockRange(
latestHeight: expectedLatestHeight,
latestDownloadedHeight: expectedStoreLatestHeight,
walletBirthday: walletBirthday
)
)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -383,50 +344,41 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { _ in
XCTFail("this shouldn't happen")
}
operation.completionHandler = { finished, cancelled in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
var nextBatch: CompactBlockProcessor.NextState?
do {
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
XCTFail("this shouldn't happen: \(error)")
}
guard let _ = nextBatch else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue(
{
switch result {
switch nextBatch {
case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))):
return true
default:
return false
}
}(),
"Expected \(expectedResult) got: \(result)"
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
)
}
func testResultProcessorFinished() throws {
func testResultProcessorFinished() async throws {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
@ -435,7 +387,8 @@ class BlockBatchValidationTests: XCTestCase {
)
let expectedStoreLatestHeight = BlockHeight(1230000)
let walletBirthday = BlockHeight(1210000)
let expectedResult = FigureNextBatchOperation.NextState.finishProcessing(height: expectedStoreLatestHeight)
let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoreLatestHeight)
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
@ -462,46 +415,38 @@ class BlockBatchValidationTests: XCTestCase {
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { _ in
XCTFail("this shouldn't happen")
}
operation.completionHandler = { finished, cancelled in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
let compactBlockProcessor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockRust,
config: config
)
var nextBatch: CompactBlockProcessor.NextState?
do {
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
XCTAssertFalse(Task.isCancelled)
} catch {
XCTFail("this shouldn't happen: \(error)")
}
guard let _ = nextBatch else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue(
{
switch result {
switch nextBatch {
case .finishProcessing(height: expectedLatestHeight):
return true
default:
return false
}
}(),
"Expected \(expectedResult) got: \(result)"
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
)
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'ZcashLightClientKit'
s.version = '0.16.8-beta'
s.version = '0.16.9-beta'
s.summary = 'Zcash Light Client wallet SDK for iOS'
s.description = <<-DESC

View File

@ -1,3 +1,16 @@
# 0.16.9-beta
Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json
````
# 0.16.8-beta
Checkpoints added:
Mainnet