Merge branch 'master' into merge_master_to_zip_316
This commit is contained in:
commit
bee1c9db80
|
@ -31,7 +31,7 @@ class SyncBlocksViewController: UIViewController {
|
||||||
// swiftlint:disable:next force_try
|
// swiftlint:disable:next force_try
|
||||||
_ = try! wallet.initialize(with: DemoAppConfig.seed)
|
_ = try! wallet.initialize(with: DemoAppConfig.seed)
|
||||||
processor = CompactBlockProcessor(initializer: wallet)
|
processor = CompactBlockProcessor(initializer: wallet)
|
||||||
statusLabel.text = textFor(state: processor?.state ?? .stopped)
|
statusLabel.text = textFor(state: processor?.state.getState() ?? .stopped)
|
||||||
progressBar.progress = 0
|
progressBar.progress = 0
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
|
@ -70,7 +70,7 @@ class SyncBlocksViewController: UIViewController {
|
||||||
@IBAction func startStop() {
|
@IBAction func startStop() {
|
||||||
guard let processor = processor else { return }
|
guard let processor = processor else { return }
|
||||||
|
|
||||||
switch processor.state {
|
switch processor.state.getState() {
|
||||||
case .stopped:
|
case .stopped:
|
||||||
startProcessor()
|
startProcessor()
|
||||||
default:
|
default:
|
||||||
|
@ -92,7 +92,7 @@ class SyncBlocksViewController: UIViewController {
|
||||||
func stopProcessor() {
|
func stopProcessor() {
|
||||||
guard let processor = processor else { return }
|
guard let processor = processor else { return }
|
||||||
|
|
||||||
processor.stop(cancelTasks: true)
|
processor.stop()
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class SyncBlocksViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI() {
|
func updateUI() {
|
||||||
guard let state = processor?.state else { return }
|
guard let state = processor?.state.getState() else { return }
|
||||||
|
|
||||||
statusLabel.text = textFor(state: state)
|
statusLabel.text = textFor(state: state)
|
||||||
startPause.setTitle(buttonText(for: state), for: .normal)
|
startPause.setTitle(buttonText(for: state), for: .normal)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,6 +30,7 @@ public enum CompactBlockProcessorError: Error {
|
||||||
case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID)
|
case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID)
|
||||||
case networkMismatch(expected: NetworkType, found: NetworkType)
|
case networkMismatch(expected: NetworkType, found: NetworkType)
|
||||||
case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight)
|
case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight)
|
||||||
|
case unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -311,11 +312,30 @@ public class CompactBlockProcessor {
|
||||||
case synced
|
case synced
|
||||||
}
|
}
|
||||||
|
|
||||||
public private(set) var state: State = .stopped {
|
// TODO: this isn't an Actor even though it looks like a good candidate, the reason:
|
||||||
didSet {
|
// `state` lives in both sync and async environments. An Actor is demanding async context only
|
||||||
transitionState(from: oldValue, to: self.state)
|
// 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 {
|
var config: Configuration {
|
||||||
willSet {
|
willSet {
|
||||||
|
@ -328,7 +348,7 @@ public class CompactBlockProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldStart: Bool {
|
var shouldStart: Bool {
|
||||||
switch self.state {
|
switch self.state.getState() {
|
||||||
case .stopped, .synced, .error:
|
case .stopped, .synced, .error:
|
||||||
return !maxAttemptsReached
|
return !maxAttemptsReached
|
||||||
default:
|
default:
|
||||||
|
@ -336,19 +356,19 @@ public class CompactBlockProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var service: LightWalletService
|
var service: LightWalletService
|
||||||
private(set) var downloader: CompactBlockDownloading
|
private(set) var downloader: CompactBlockDownloading
|
||||||
private var storage: CompactBlockStorage
|
var storage: CompactBlockStorage
|
||||||
private var transactionRepository: TransactionRepository
|
var transactionRepository: TransactionRepository
|
||||||
private var accountRepository: AccountRepository
|
var accountRepository: AccountRepository
|
||||||
private var rustBackend: ZcashRustBackendWelding.Type
|
var rustBackend: ZcashRustBackendWelding.Type
|
||||||
private var retryAttempts: Int = 0
|
private var retryAttempts: Int = 0
|
||||||
private var backoffTimer: Timer?
|
private var backoffTimer: Timer?
|
||||||
private var lowerBoundHeight: BlockHeight?
|
private var lowerBoundHeight: BlockHeight?
|
||||||
private var latestBlockHeight: BlockHeight
|
private var latestBlockHeight: BlockHeight
|
||||||
private var lastChainValidationFailure: BlockHeight?
|
private var lastChainValidationFailure: BlockHeight?
|
||||||
private var consecutiveChainValidationErrors: Int = 0
|
private var consecutiveChainValidationErrors: Int = 0
|
||||||
private var processingError: Error?
|
var processingError: Error?
|
||||||
private var foundBlocks = false
|
private var foundBlocks = false
|
||||||
private var maxAttempts: Int {
|
private var maxAttempts: Int {
|
||||||
config.retries
|
config.retries
|
||||||
|
@ -358,13 +378,7 @@ public class CompactBlockProcessor {
|
||||||
BlockHeight(self.config.downloadBatchSize)
|
BlockHeight(self.config.downloadBatchSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var operationQueue: OperationQueue = {
|
private var cancelableTask: Task<Void, Error>?
|
||||||
let queue = OperationQueue()
|
|
||||||
queue.name = "CompactBlockProcessorQueue"
|
|
||||||
queue.maxConcurrentOperationCount = 1
|
|
||||||
return queue
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
|
||||||
/// Initializes a CompactBlockProcessor instance
|
/// Initializes a CompactBlockProcessor instance
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
@ -431,7 +445,13 @@ public class CompactBlockProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
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(
|
static func validateServerInfo(
|
||||||
|
@ -487,19 +507,14 @@ public class CompactBlockProcessor {
|
||||||
self.backoffTimer?.invalidate()
|
self.backoffTimer?.invalidate()
|
||||||
self.backoffTimer = nil
|
self.backoffTimer = nil
|
||||||
}
|
}
|
||||||
guard !operationQueue.isSuspended else {
|
|
||||||
LoggerProxy.debug("restarting suspended queue")
|
|
||||||
operationQueue.isSuspended = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard shouldStart else {
|
guard shouldStart else {
|
||||||
switch self.state {
|
switch self.state.getState() {
|
||||||
case .error(let e):
|
case .error(let e):
|
||||||
// max attempts have been reached
|
// max attempts have been reached
|
||||||
LoggerProxy.info("max retry attempts reached with error: \(e)")
|
LoggerProxy.info("max retry attempts reached with error: \(e)")
|
||||||
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
|
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
|
||||||
self.state = .stopped
|
setState(.stopped)
|
||||||
case .stopped:
|
case .stopped:
|
||||||
// max attempts have been reached
|
// max attempts have been reached
|
||||||
LoggerProxy.info("max retry attempts reached")
|
LoggerProxy.info("max retry attempts reached")
|
||||||
|
@ -514,7 +529,7 @@ public class CompactBlockProcessor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.nextBatchTask()
|
self.nextBatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -523,18 +538,14 @@ public class CompactBlockProcessor {
|
||||||
Note: retry count is reset
|
Note: retry count is reset
|
||||||
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
|
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
|
||||||
*/
|
*/
|
||||||
public func stop(cancelTasks: Bool = true) {
|
public func stop() {
|
||||||
self.backoffTimer?.invalidate()
|
self.backoffTimer?.invalidate()
|
||||||
self.backoffTimer = nil
|
self.backoffTimer = nil
|
||||||
|
|
||||||
if cancelTasks {
|
cancelableTask?.cancel()
|
||||||
operationQueue.cancelAllOperations()
|
|
||||||
} else {
|
|
||||||
self.operationQueue.isSuspended = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.retryAttempts = 0
|
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
|
- Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started
|
||||||
*/
|
*/
|
||||||
func setStartHeight(_ startHeight: BlockHeight) throws {
|
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
|
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
|
/// 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
|
|
||||||
func processNewBlocks(range: CompactBlockRange) {
|
func processNewBlocks(range: CompactBlockRange) {
|
||||||
self.foundBlocks = true
|
self.foundBlocks = true
|
||||||
self.backoffTimer?.invalidate()
|
self.backoffTimer?.invalidate()
|
||||||
self.backoffTimer = nil
|
self.backoffTimer = nil
|
||||||
|
|
||||||
let cfg = self.config
|
cancelableTask = Task(priority: .userInitiated) {
|
||||||
let downloadBlockOperation = CompactBlockStreamDownloadOperation(
|
do {
|
||||||
service: self.service,
|
try await compactBlockStreamDownload(
|
||||||
storage: self.storage,
|
blockBufferSize: config.downloadBufferSize,
|
||||||
blockBufferSize: self.config.downloadBufferSize,
|
startHeight: range.lowerBound,
|
||||||
startHeight: range.lowerBound,
|
targetHeight: range.upperBound
|
||||||
targetHeight: range.upperBound,
|
)
|
||||||
progressDelegate: self
|
try await compactBlockValidation()
|
||||||
)
|
try await compactBlockBatchScanning(range: range)
|
||||||
|
try await compactBlockEnhancement(range: range)
|
||||||
downloadBlockOperation.startedHandler = { [weak self] in
|
try await fetchUnspentTxOutputs(range: range)
|
||||||
DispatchQueue.main.async {
|
} catch {
|
||||||
self?.state = .downloading
|
if error is CancellationError {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func calculateProgress(start: BlockHeight, current: BlockHeight, latest: BlockHeight) -> Float {
|
||||||
|
@ -892,35 +694,35 @@ public class CompactBlockProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
func severeFailure(_ error: Error) {
|
func severeFailure(_ error: Error) {
|
||||||
operationQueue.cancelAllOperations()
|
cancelableTask?.cancel()
|
||||||
LoggerProxy.error("show stoppper failure: \(error)")
|
LoggerProxy.error("show stoppper failure: \(error)")
|
||||||
self.backoffTimer?.invalidate()
|
self.backoffTimer?.invalidate()
|
||||||
self.retryAttempts = config.retries
|
self.retryAttempts = config.retries
|
||||||
self.processingError = error
|
self.processingError = error
|
||||||
self.state = .error(error)
|
setState(.error(error))
|
||||||
self.notifyError(error)
|
self.notifyError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fail(_ error: Error) {
|
func fail(_ error: Error) {
|
||||||
// todo specify: failure
|
// todo specify: failure
|
||||||
LoggerProxy.error("\(error)")
|
LoggerProxy.error("\(error)")
|
||||||
operationQueue.cancelAllOperations()
|
cancelableTask?.cancel()
|
||||||
self.retryAttempts += 1
|
self.retryAttempts += 1
|
||||||
self.processingError = error
|
self.processingError = error
|
||||||
switch self.state {
|
switch self.state.getState() {
|
||||||
case .error:
|
case .error:
|
||||||
notifyError(error)
|
notifyError(error)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
self.state = .error(error)
|
setState(.error(error))
|
||||||
guard self.maxAttemptsReached else { return }
|
guard self.maxAttemptsReached else { return }
|
||||||
// don't set a new timer if there are no more attempts.
|
// don't set a new timer if there are no more attempts.
|
||||||
self.setTimer()
|
self.setTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func retryProcessing(range: CompactBlockRange) {
|
func retryProcessing(range: CompactBlockRange) {
|
||||||
operationQueue.cancelAllOperations()
|
cancelableTask?.cancel()
|
||||||
// update retries
|
// update retries
|
||||||
self.retryAttempts += 1
|
self.retryAttempts += 1
|
||||||
self.processingError = nil
|
self.processingError = nil
|
||||||
|
@ -935,7 +737,7 @@ public class CompactBlockProcessor {
|
||||||
|
|
||||||
// process next batch
|
// process next batch
|
||||||
// processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday))
|
// processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday))
|
||||||
nextBatchTask()
|
nextBatch()
|
||||||
} catch {
|
} catch {
|
||||||
self.fail(error)
|
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() {
|
private func nextBatch() {
|
||||||
self.state = .downloading
|
setState(.downloading)
|
||||||
NextStateHelper.nextState(
|
Task { @MainActor [self] in
|
||||||
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
|
|
||||||
do {
|
do {
|
||||||
let nextState = try await NextStateHelper.nextStateAsync(
|
let nextState = try await NextStateHelper.nextStateAsync(
|
||||||
service: self.service,
|
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
|
// cancel all Tasks
|
||||||
operationQueue.cancelAllOperations()
|
cancelableTask?.cancel()
|
||||||
|
|
||||||
// register latest failure
|
// register latest failure
|
||||||
self.lastChainValidationFailure = height
|
self.lastChainValidationFailure = height
|
||||||
|
@ -1078,13 +841,13 @@ public class CompactBlockProcessor {
|
||||||
)
|
)
|
||||||
|
|
||||||
// process next batch
|
// process next batch
|
||||||
self.nextBatchTask()
|
self.nextBatch()
|
||||||
} catch {
|
} catch {
|
||||||
self.fail(error)
|
self.fail(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processBatchFinished(range: CompactBlockRange) {
|
internal func processBatchFinished(range: CompactBlockRange) {
|
||||||
guard processingError == nil else {
|
guard processingError == nil else {
|
||||||
retryProcessing(range: range)
|
retryProcessing(range: range)
|
||||||
return
|
return
|
||||||
|
@ -1098,7 +861,7 @@ public class CompactBlockProcessor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nextBatchTask()
|
nextBatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processingFinished(height: BlockHeight) {
|
private func processingFinished(height: BlockHeight) {
|
||||||
|
@ -1110,7 +873,7 @@ public class CompactBlockProcessor {
|
||||||
CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks
|
CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
self.state = .synced
|
setState(.synced)
|
||||||
setTimer()
|
setTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1292,33 +1055,27 @@ extension CompactBlockProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
let dataDb = self.config.dataDb
|
||||||
self.downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) { [weak self] fetchResult in
|
|
||||||
switch fetchResult {
|
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight)
|
||||||
case .success(let utxos):
|
var utxos: [UnspentTransactionOutputEntity] = []
|
||||||
DispatchQueue.main.async {
|
|
||||||
self?.operationQueue.addOperation { [self] in
|
do {
|
||||||
guard let self = self else { return }
|
for try await utxo in stream {
|
||||||
do {
|
utxos.append(utxo)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
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):
|
case let .wrongConsensusBranchId(expectedLocally, found):
|
||||||
// swiftlint:disable:next line_length
|
// 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."
|
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 {
|
extension CompactBlockProcessor: EnhancementStreamDelegate {
|
||||||
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) {
|
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) {
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
|
@ -1417,38 +1169,12 @@ extension CompactBlockProcessor: EnhancementStreamDelegate {
|
||||||
|
|
||||||
extension CompactBlockProcessor {
|
extension CompactBlockProcessor {
|
||||||
enum NextStateHelper {
|
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(
|
static func nextStateAsync(
|
||||||
service: LightWalletService,
|
service: LightWalletService,
|
||||||
downloader: CompactBlockDownloading,
|
downloader: CompactBlockDownloading,
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
rustBackend: ZcashRustBackendWelding.Type
|
rustBackend: ZcashRustBackendWelding.Type
|
||||||
) async throws -> FigureNextBatchOperation.NextState {
|
) async throws -> NextState {
|
||||||
let task = Task(priority: .userInitiated) {
|
let task = Task(priority: .userInitiated) {
|
||||||
// TODO: refactor to async call, issue 463, PR 493
|
// TODO: refactor to async call, issue 463, PR 493
|
||||||
// https://github.com/zcash/ZcashLightClientKit/issues/463
|
// https://github.com/zcash/ZcashLightClientKit/issues/463
|
||||||
|
@ -1467,7 +1193,7 @@ extension CompactBlockProcessor {
|
||||||
downloader: CompactBlockDownloading,
|
downloader: CompactBlockDownloading,
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
rustBackend: ZcashRustBackendWelding.Type
|
rustBackend: ZcashRustBackendWelding.Type
|
||||||
) throws -> FigureNextBatchOperation.NextState {
|
) throws -> NextState {
|
||||||
let info = try service.getInfo()
|
let info = try service.getInfo()
|
||||||
|
|
||||||
try CompactBlockProcessor.validateServerInfo(
|
try CompactBlockProcessor.validateServerInfo(
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,78 +8,58 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum CompactBlockValidationError: Error {
|
extension CompactBlockProcessor {
|
||||||
case validationFailed(height: BlockHeight)
|
enum CompactBlockValidationError: Error {
|
||||||
case failedWithError(_ error: 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
|
||||||
guard !shouldCancel() else {
|
|
||||||
cancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.startedHandler?()
|
func compactBlockValidation() async throws {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
setState(.validating)
|
||||||
|
|
||||||
cancelableTask = Task {
|
let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType)
|
||||||
let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network)
|
|
||||||
|
do {
|
||||||
switch result {
|
switch result {
|
||||||
case 0:
|
case 0:
|
||||||
let error = CompactBlockValidationError.failedWithError(rustBackend.lastError())
|
let error = CompactBlockValidationError.failedWithError(rustBackend.lastError())
|
||||||
self.error = error
|
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
throw error
|
||||||
self.fail(error: error)
|
|
||||||
|
|
||||||
case ZcashRustBackendWeldingConstants.validChain:
|
case ZcashRustBackendWeldingConstants.validChain:
|
||||||
self.done = true
|
if Task.isCancelled {
|
||||||
|
setState(.stopped)
|
||||||
|
LoggerProxy.debug("Warning: compactBlockValidation cancelled")
|
||||||
|
}
|
||||||
|
LoggerProxy.debug("validateChainFinished")
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result))
|
let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result))
|
||||||
self.error = error
|
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
throw error
|
||||||
self.fail(error: 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1787500",
|
||||||
|
"hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa",
|
||||||
|
"time": 1661588407,
|
||||||
|
"saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1790000",
|
||||||
|
"hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4",
|
||||||
|
"time": 1661777577,
|
||||||
|
"saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1792500",
|
||||||
|
"hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48",
|
||||||
|
"time": 1661965969,
|
||||||
|
"saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1795000",
|
||||||
|
"hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1",
|
||||||
|
"time": 1662152700,
|
||||||
|
"saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1797500",
|
||||||
|
"hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0",
|
||||||
|
"time": 1662341718,
|
||||||
|
"saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1800000",
|
||||||
|
"hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961",
|
||||||
|
"time": 1662529994,
|
||||||
|
"saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1802500",
|
||||||
|
"hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91",
|
||||||
|
"time": 1662718546,
|
||||||
|
"saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1805000",
|
||||||
|
"hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb",
|
||||||
|
"time": 1662907506,
|
||||||
|
"saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||||
|
"orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||||
|
}
|
|
@ -369,7 +369,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
|
||||||
}
|
}
|
||||||
continuation.finish(throwing: nil)
|
continuation.finish(throwing: nil)
|
||||||
} catch {
|
} catch {
|
||||||
continuation.finish(throwing: error)
|
continuation.finish(throwing: error.mapToServiceError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -551,7 +551,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
|
||||||
}
|
}
|
||||||
continuation.finish(throwing: nil)
|
continuation.finish(throwing: nil)
|
||||||
} catch {
|
} catch {
|
||||||
continuation.finish(throwing: error)
|
continuation.finish(throwing: error.mapToServiceError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,7 +619,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI {
|
||||||
}
|
}
|
||||||
continuation.finish(throwing: nil)
|
continuation.finish(throwing: nil)
|
||||||
} catch {
|
} catch {
|
||||||
continuation.finish(throwing: error)
|
continuation.finish(throwing: error.mapToServiceError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ public protocol Synchronizer {
|
||||||
func latestHeight() throws -> BlockHeight
|
func latestHeight() throws -> BlockHeight
|
||||||
|
|
||||||
/// Returns the latests UTXOs for the given address from the specified height on
|
/// 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
|
/// Returns the last stored transparent balance
|
||||||
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance
|
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance
|
||||||
|
|
|
@ -194,7 +194,7 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
blockProcessor.stop(cancelTasks: true)
|
blockProcessor.stop()
|
||||||
self.status = .stopped
|
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) {
|
public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs {
|
||||||
self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result)
|
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")
|
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
|
||||||
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
|
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
|
||||||
|
@ -856,6 +856,7 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
||||||
case .saplingActivationMismatch:
|
case .saplingActivationMismatch:
|
||||||
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
||||||
|
case .unknown: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,12 +11,6 @@ import XCTest
|
||||||
|
|
||||||
// swiftlint:disable print_function_usage
|
// swiftlint:disable print_function_usage
|
||||||
class BlockStreamingTest: XCTestCase {
|
class BlockStreamingTest: XCTestCase {
|
||||||
var queue: OperationQueue = {
|
|
||||||
let queue = OperationQueue()
|
|
||||||
queue.maxConcurrentOperationCount = 1
|
|
||||||
return queue
|
|
||||||
}()
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
try super.setUpWithError()
|
try super.setUpWithError()
|
||||||
logger = SampleLogger(logLevel: .debug)
|
logger = SampleLogger(logLevel: .debug)
|
||||||
|
@ -27,7 +21,7 @@ class BlockStreamingTest: XCTestCase {
|
||||||
try? FileManager.default.removeItem(at: __dataDbURL())
|
try? FileManager.default.removeItem(at: __dataDbURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStreamOperation() throws {
|
func testStream() throws {
|
||||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
let expectation = XCTestExpectation(description: "blockstream expectation")
|
||||||
|
|
||||||
let service = LightWalletGRPCService(
|
let service = LightWalletGRPCService(
|
||||||
|
@ -61,9 +55,7 @@ class BlockStreamingTest: XCTestCase {
|
||||||
wait(for: [expectation], timeout: 1000)
|
wait(for: [expectation], timeout: 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStreamOperationCancellation() throws {
|
func testStreamCancellation() async throws {
|
||||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
|
||||||
|
|
||||||
let service = LightWalletGRPCService(
|
let service = LightWalletGRPCService(
|
||||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||||
port: 9067,
|
port: 9067,
|
||||||
|
@ -71,37 +63,38 @@ class BlockStreamingTest: XCTestCase {
|
||||||
singleCallTimeout: 10000,
|
singleCallTimeout: 10000,
|
||||||
streamingCallTimeout: 10000
|
streamingCallTimeout: 10000
|
||||||
)
|
)
|
||||||
|
|
||||||
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
|
|
||||||
let startHeight = try service.latestBlockHeight() - 100_000
|
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,
|
service: service,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
blockBufferSize: 10,
|
backend: ZcashRustBackend.self,
|
||||||
startHeight: startHeight,
|
config: processorConfig
|
||||||
progressDelegate: self
|
|
||||||
)
|
)
|
||||||
|
|
||||||
operation.completionHandler = { _, cancelled in
|
let cancelableTask = Task {
|
||||||
XCTAssert(cancelled)
|
do {
|
||||||
expectation.fulfill()
|
try await compactBlockProcessor.compactBlockStreamDownload(
|
||||||
|
blockBufferSize: 10,
|
||||||
|
startHeight: startHeight
|
||||||
|
)
|
||||||
|
XCTAssertTrue(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
|
XCTFail("failed with error: \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.errorHandler = { error in
|
try await Task.sleep(nanoseconds: 3_000_000_000)
|
||||||
XCTFail("failed with error: \(error)")
|
cancelableTask.cancel()
|
||||||
expectation.fulfill()
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.addOperation(operation)
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
|
|
||||||
self.queue.cancelAllOperations()
|
|
||||||
})
|
|
||||||
wait(for: [expectation], timeout: 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStreamOperationTimeout() throws {
|
func testStreamTimeout() async throws {
|
||||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
|
||||||
let errorExpectation = XCTestExpectation(description: "blockstream error expectation")
|
|
||||||
let service = LightWalletGRPCService(
|
let service = LightWalletGRPCService(
|
||||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||||
port: 9067,
|
port: 9067,
|
||||||
|
@ -109,49 +102,49 @@ class BlockStreamingTest: XCTestCase {
|
||||||
singleCallTimeout: 1000,
|
singleCallTimeout: 1000,
|
||||||
streamingCallTimeout: 3000
|
streamingCallTimeout: 3000
|
||||||
)
|
)
|
||||||
|
|
||||||
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
|
|
||||||
let startHeight = try service.latestBlockHeight() - 100_000
|
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,
|
service: service,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
blockBufferSize: 10,
|
backend: ZcashRustBackend.self,
|
||||||
startHeight: startHeight,
|
config: processorConfig
|
||||||
progressDelegate: self
|
|
||||||
)
|
)
|
||||||
|
|
||||||
operation.completionHandler = { finished, _ in
|
let date = Date()
|
||||||
XCTAssert(finished)
|
|
||||||
|
|
||||||
expectation.fulfill()
|
|
||||||
}
|
|
||||||
|
|
||||||
operation.errorHandler = { error in
|
do {
|
||||||
|
try await compactBlockProcessor.compactBlockStreamDownload(
|
||||||
|
blockBufferSize: 10,
|
||||||
|
startHeight: startHeight
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
if let lwdError = error as? LightWalletServiceError {
|
if let lwdError = error as? LightWalletServiceError {
|
||||||
switch lwdError {
|
switch lwdError {
|
||||||
case .timeOut:
|
case .timeOut:
|
||||||
XCTAssert(true)
|
XCTAssert(true)
|
||||||
default:
|
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 {
|
} else {
|
||||||
XCTFail("Error should have been a timeLimit reached Error")
|
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 now = Date()
|
||||||
|
|
||||||
let elapsed = now.timeIntervalSince(date)
|
let elapsed = now.timeIntervalSince(date)
|
||||||
print("took \(elapsed) seconds")
|
print("took \(elapsed) seconds")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBatchOperation() throws {
|
func testBatch() async throws {
|
||||||
let expectation = XCTestExpectation(description: "blockbatch expectation")
|
|
||||||
|
|
||||||
let service = LightWalletGRPCService(
|
let service = LightWalletGRPCService(
|
||||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||||
port: 9067,
|
port: 9067,
|
||||||
|
@ -162,34 +155,29 @@ class BlockStreamingTest: XCTestCase {
|
||||||
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
||||||
let targetHeight = try service.latestBlockHeight()
|
let targetHeight = try service.latestBlockHeight()
|
||||||
let startHeight = targetHeight - 10_000
|
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,
|
service: service,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
startHeight: startHeight,
|
backend: ZcashRustBackend.self,
|
||||||
targetHeight: targetHeight,
|
config: processorConfig
|
||||||
progressDelegate: self
|
|
||||||
)
|
)
|
||||||
|
|
||||||
operation.completionHandler = { _, cancelled in
|
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
|
||||||
if cancelled {
|
do {
|
||||||
XCTFail("operation cancelled")
|
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
|
||||||
}
|
XCTAssertFalse(Task.isCancelled)
|
||||||
expectation.fulfill()
|
} catch {
|
||||||
}
|
|
||||||
|
|
||||||
operation.errorHandler = { error in
|
|
||||||
XCTFail("failed with error: \(error)")
|
XCTFail("failed with error: \(error)")
|
||||||
expectation.fulfill()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.addOperation(operation)
|
|
||||||
|
|
||||||
wait(for: [expectation], timeout: 120)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBatchOperationCancellation() throws {
|
func testBatchCancellation() async throws {
|
||||||
let expectation = XCTestExpectation(description: "blockbatch expectation")
|
|
||||||
|
|
||||||
let service = LightWalletGRPCService(
|
let service = LightWalletGRPCService(
|
||||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||||
port: 9067,
|
port: 9067,
|
||||||
|
@ -200,36 +188,30 @@ class BlockStreamingTest: XCTestCase {
|
||||||
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
||||||
let targetHeight = try service.latestBlockHeight()
|
let targetHeight = try service.latestBlockHeight()
|
||||||
let startHeight = targetHeight - 100_000
|
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,
|
service: service,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
startHeight: startHeight,
|
backend: ZcashRustBackend.self,
|
||||||
targetHeight: targetHeight,
|
config: processorConfig
|
||||||
progressDelegate: self
|
|
||||||
)
|
)
|
||||||
|
|
||||||
operation.completionHandler = { _, cancelled in
|
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
|
||||||
XCTAssert(cancelled)
|
let cancelableTask = Task {
|
||||||
expectation.fulfill()
|
do {
|
||||||
|
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
|
||||||
|
XCTAssertTrue(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
|
XCTFail("failed with error: \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.errorHandler = { error in
|
try await Task.sleep(nanoseconds: 3_000_000_000)
|
||||||
XCTFail("failed with error: \(error)")
|
cancelableTask.cancel()
|
||||||
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))")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// DownloadOperationTests.swift
|
// DownloadTests.swift
|
||||||
// ZcashLightClientKitTests
|
// ZcashLightClientKitTests
|
||||||
//
|
//
|
||||||
// Created by Francisco Gindre on 10/16/19.
|
// Created by Francisco Gindre on 10/16/19.
|
||||||
|
@ -12,40 +12,38 @@ import SQLite
|
||||||
@testable import ZcashLightClientKit
|
@testable import ZcashLightClientKit
|
||||||
|
|
||||||
// swiftlint:disable force_try
|
// swiftlint:disable force_try
|
||||||
class DownloadOperationTests: XCTestCase {
|
class DownloadTests: XCTestCase {
|
||||||
var operationQueue = OperationQueue()
|
|
||||||
var network = ZcashNetworkBuilder.network(for: .testnet)
|
var network = ZcashNetworkBuilder.network(for: .testnet)
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
operationQueue.cancelAllOperations()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSingleOperation() {
|
func testSingleDownload() async throws {
|
||||||
let expect = XCTestExpectation(description: self.description)
|
|
||||||
|
|
||||||
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
|
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
|
||||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: storage)
|
let downloader = CompactBlockDownloader(service: service, storage: storage)
|
||||||
let blockCount = 100
|
let blockCount = 100
|
||||||
let activationHeight = network.constants.saplingActivationHeight
|
let activationHeight = network.constants.saplingActivationHeight
|
||||||
let range = activationHeight ... activationHeight + blockCount
|
let range = activationHeight ... activationHeight + blockCount
|
||||||
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
|
|
||||||
|
|
||||||
downloadOperation.completionHandler = { finished, cancelled in
|
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||||
expect.fulfill()
|
for: network,
|
||||||
XCTAssertTrue(finished)
|
walletBirthday: network.constants.saplingActivationHeight
|
||||||
XCTAssertFalse(cancelled)
|
)
|
||||||
|
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)
|
XCTAssertEqual(try! storage.latestHeight(), range.upperBound)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,29 +11,13 @@ import XCTest
|
||||||
|
|
||||||
// swiftlint:disable force_try type_body_length
|
// swiftlint:disable force_try type_body_length
|
||||||
class BlockBatchValidationTests: XCTestCase {
|
class BlockBatchValidationTests: XCTestCase {
|
||||||
var queue: OperationQueue = {
|
func testBranchIdFailure() async throws {
|
||||||
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 {
|
|
||||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
latestBlockHeight: 1210000,
|
latestBlockHeight: 1210000,
|
||||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -59,17 +43,18 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = Int32(0xd34d)
|
mockRust.consensusBranchID = Int32(0xd34d)
|
||||||
|
|
||||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
let expectation = XCTestExpectation(description: "failure expectation")
|
service: service,
|
||||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
operation.startedHandler = {
|
config: config
|
||||||
startedExpectation.fulfill()
|
)
|
||||||
}
|
|
||||||
|
do {
|
||||||
operation.errorHandler = { error in
|
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||||
expectation.fulfill()
|
XCTAssertFalse(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case CompactBlockProcessorError.wrongConsensusBranchId:
|
case CompactBlockProcessorError.wrongConsensusBranchId:
|
||||||
break
|
break
|
||||||
|
@ -77,19 +62,15 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)")
|
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 network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
latestBlockHeight: 1210000,
|
latestBlockHeight: 1210000,
|
||||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -115,17 +96,18 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
mockRust.consensusBranchID = 0xd34db4d
|
||||||
|
|
||||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
let expectation = XCTestExpectation(description: "failure expectation")
|
service: service,
|
||||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
operation.startedHandler = {
|
config: config
|
||||||
startedExpectation.fulfill()
|
)
|
||||||
}
|
|
||||||
|
do {
|
||||||
operation.errorHandler = { error in
|
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||||
expectation.fulfill()
|
XCTAssertFalse(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet):
|
case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet):
|
||||||
break
|
break
|
||||||
|
@ -133,20 +115,15 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)")
|
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 network = ZcashNetworkBuilder.network(for: .testnet)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
latestBlockHeight: 1210000,
|
latestBlockHeight: 1210000,
|
||||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -172,17 +149,18 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
mockRust.consensusBranchID = 0xd34db4d
|
||||||
|
|
||||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
let expectation = XCTestExpectation(description: "failure expectation")
|
service: service,
|
||||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
operation.startedHandler = {
|
config: config
|
||||||
startedExpectation.fulfill()
|
)
|
||||||
}
|
|
||||||
|
do {
|
||||||
operation.errorHandler = { error in
|
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||||
expectation.fulfill()
|
XCTAssertFalse(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case CompactBlockProcessorError.generalError:
|
case CompactBlockProcessorError.generalError:
|
||||||
break
|
break
|
||||||
|
@ -190,20 +168,15 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)")
|
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 network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
latestBlockHeight: 1210000,
|
latestBlockHeight: 1210000,
|
||||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -230,17 +203,18 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
mockRust.consensusBranchID = 0xd34db4d
|
||||||
|
|
||||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
let expectation = XCTestExpectation(description: "failure expectation")
|
service: service,
|
||||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
operation.startedHandler = {
|
config: config
|
||||||
startedExpectation.fulfill()
|
)
|
||||||
}
|
|
||||||
|
do {
|
||||||
operation.errorHandler = { error in
|
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||||
expectation.fulfill()
|
XCTAssertFalse(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case CompactBlockProcessorError.saplingActivationMismatch(
|
case CompactBlockProcessorError.saplingActivationMismatch(
|
||||||
expected: network.constants.saplingActivationHeight,
|
expected: network.constants.saplingActivationHeight,
|
||||||
|
@ -251,15 +225,9 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)")
|
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 network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
|
|
||||||
let expectedLatestHeight = BlockHeight(1210000)
|
let expectedLatestHeight = BlockHeight(1210000)
|
||||||
|
@ -268,10 +236,11 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||||
)
|
)
|
||||||
let expectedStoreLatestHeight = BlockHeight(1220000)
|
let expectedStoreLatestHeight = BlockHeight(1220000)
|
||||||
let expectedResult = FigureNextBatchOperation.NextState.wait(
|
let expectedResult = CompactBlockProcessor.NextState.wait(
|
||||||
latestHeight: expectedLatestHeight,
|
latestHeight: expectedLatestHeight,
|
||||||
latestDownloadHeight: expectedLatestHeight
|
latestDownloadHeight: expectedLatestHeight
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -298,50 +267,41 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
mockRust.consensusBranchID = 0xd34db4d
|
||||||
|
|
||||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
let completedExpectation = XCTestExpectation(description: "completed expectation")
|
service: service,
|
||||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
operation.startedHandler = {
|
config: config
|
||||||
startedExpectation.fulfill()
|
)
|
||||||
}
|
|
||||||
|
var nextBatch: CompactBlockProcessor.NextState?
|
||||||
operation.errorHandler = { error in
|
do {
|
||||||
|
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||||
|
XCTAssertFalse(Task.isCancelled)
|
||||||
|
} catch {
|
||||||
XCTFail("this shouldn't happen: \(error)")
|
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)
|
guard let _ = nextBatch else {
|
||||||
XCTAssertNil(operation.error)
|
|
||||||
XCTAssertFalse(operation.isCancelled)
|
|
||||||
|
|
||||||
guard let result = operation.result else {
|
|
||||||
XCTFail("result should not be nil")
|
XCTFail("result should not be nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
{
|
{
|
||||||
switch result {
|
switch nextBatch {
|
||||||
case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight):
|
case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
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 network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
let expectedLatestHeight = BlockHeight(1230000)
|
let expectedLatestHeight = BlockHeight(1230000)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
|
@ -350,13 +310,14 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
)
|
)
|
||||||
let expectedStoreLatestHeight = BlockHeight(1220000)
|
let expectedStoreLatestHeight = BlockHeight(1220000)
|
||||||
let walletBirthday = BlockHeight(1210000)
|
let walletBirthday = BlockHeight(1210000)
|
||||||
let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks(
|
let expectedResult = CompactBlockProcessor.NextState.processNewBlocks(
|
||||||
range: CompactBlockProcessor.nextBatchBlockRange(
|
range: CompactBlockProcessor.nextBatchBlockRange(
|
||||||
latestHeight: expectedLatestHeight,
|
latestHeight: expectedLatestHeight,
|
||||||
latestDownloadedHeight: expectedStoreLatestHeight,
|
latestDownloadedHeight: expectedStoreLatestHeight,
|
||||||
walletBirthday: walletBirthday
|
walletBirthday: walletBirthday
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -383,50 +344,41 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
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)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
XCTAssertNil(operation.error)
|
service: service,
|
||||||
XCTAssertFalse(operation.isCancelled)
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
guard let result = operation.result else {
|
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")
|
XCTFail("result should not be nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
{
|
{
|
||||||
switch result {
|
switch nextBatch {
|
||||||
case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))):
|
case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
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 network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||||
let expectedLatestHeight = BlockHeight(1230000)
|
let expectedLatestHeight = BlockHeight(1230000)
|
||||||
let service = MockLightWalletService(
|
let service = MockLightWalletService(
|
||||||
|
@ -435,7 +387,8 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
)
|
)
|
||||||
let expectedStoreLatestHeight = BlockHeight(1230000)
|
let expectedStoreLatestHeight = BlockHeight(1230000)
|
||||||
let walletBirthday = BlockHeight(1210000)
|
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 repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||||
let config = CompactBlockProcessor.Configuration(
|
let config = CompactBlockProcessor.Configuration(
|
||||||
|
@ -462,46 +415,38 @@ class BlockBatchValidationTests: XCTestCase {
|
||||||
|
|
||||||
let mockRust = MockRustBackend.self
|
let mockRust = MockRustBackend.self
|
||||||
mockRust.consensusBranchID = 0xd34db4d
|
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)
|
let compactBlockProcessor = CompactBlockProcessor(
|
||||||
XCTAssertNil(operation.error)
|
service: service,
|
||||||
XCTAssertFalse(operation.isCancelled)
|
storage: storage,
|
||||||
|
backend: mockRust,
|
||||||
guard let result = operation.result else {
|
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")
|
XCTFail("result should not be nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
{
|
{
|
||||||
switch result {
|
|
||||||
|
switch nextBatch {
|
||||||
case .finishProcessing(height: expectedLatestHeight):
|
case .finishProcessing(height: expectedLatestHeight):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
"Expected \(expectedResult) got: \(result)"
|
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'ZcashLightClientKit'
|
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.summary = 'Zcash Light Client wallet SDK for iOS'
|
||||||
|
|
||||||
s.description = <<-DESC
|
s.description = <<-DESC
|
||||||
|
|
13
changelog.md
13
changelog.md
|
@ -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
|
# 0.16.8-beta
|
||||||
Checkpoints added:
|
Checkpoints added:
|
||||||
Mainnet
|
Mainnet
|
||||||
|
|
Loading…
Reference in New Issue