ZcashLightClientKit/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift

199 lines
8.0 KiB
Swift

//
// 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()
state = .scanning
// TODO: remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576
let batchSize = scanBatchSize(for: range, network: self.config.network.networkType)
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.mainThreadPostNotification(
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.mainThreadPostNotification(
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")
}
await Task.yield()
} while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight
if Task.isCancelled {
state = .stopped
LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled")
}
}
} catch {
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
}
fileprivate func scanBatchSize(for range: CompactBlockRange, network: NetworkType) -> UInt32 {
guard network == .mainnet else {
return UInt32(config.scanningBatchSize)
}
if range.lowerBound > 1_600_000 {
return 5
}
return UInt32(config.scanningBatchSize)
}
}
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)
}
}