Merge pull request #1192 from LukasKorba/1188-Working-prototype-of-SbS
1188 working prototype of SbS
This commit is contained in:
commit
bcecc91190
|
@ -53,6 +53,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
spendParamsURL: try! spendParamsURLHelper(),
|
||||
outputParamsURL: try! outputParamsURLHelper(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.default,
|
||||
syncAlgorithm: .spendBeforeSync,
|
||||
enableBackendTracing: true
|
||||
)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ enum DemoAppConfig {
|
|||
|
||||
static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 1935000 : 2170000
|
||||
static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
|
||||
kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner
|
||||
kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner
|
||||
""")
|
||||
|
||||
static let otherSynchronizers: [SynchronizerInitData] = [
|
||||
|
|
|
@ -112,6 +112,7 @@ class SyncBlocksListViewController: UIViewController {
|
|||
outputParamsURL: try! outputParamsURLHelper(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.default,
|
||||
alias: data.alias,
|
||||
syncAlgorithm: .spendBeforeSync,
|
||||
loggingPolicy: .default(.debug),
|
||||
enableBackendTracing: true
|
||||
)
|
||||
|
|
|
@ -11,12 +11,16 @@ actor ActionContext {
|
|||
var state: CBPState
|
||||
var prevState: CBPState?
|
||||
var syncControlData: SyncControlData
|
||||
let preferredSyncAlgorithm: SyncAlgorithm
|
||||
var supportedSyncAlgorithm: SyncAlgorithm?
|
||||
var totalProgressRange: CompactBlockRange = 0...0
|
||||
var lastScannedHeight: BlockHeight?
|
||||
var lastDownloadedHeight: BlockHeight?
|
||||
var lastEnhancedHeight: BlockHeight?
|
||||
|
||||
init(state: CBPState) {
|
||||
init(state: CBPState, preferredSyncAlgorithm: SyncAlgorithm = .linear) {
|
||||
self.state = state
|
||||
self.preferredSyncAlgorithm = preferredSyncAlgorithm
|
||||
syncControlData = SyncControlData.empty
|
||||
}
|
||||
|
||||
|
@ -26,8 +30,10 @@ actor ActionContext {
|
|||
}
|
||||
func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
|
||||
func update(totalProgressRange: CompactBlockRange) async { self.totalProgressRange = totalProgressRange }
|
||||
func update(lastScannedHeight: BlockHeight) async { self.lastScannedHeight = lastScannedHeight }
|
||||
func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
|
||||
func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
|
||||
func update(supportedSyncAlgorithm: SyncAlgorithm) async { self.supportedSyncAlgorithm = supportedSyncAlgorithm }
|
||||
}
|
||||
|
||||
enum CBPState: CaseIterable {
|
||||
|
@ -36,6 +42,7 @@ enum CBPState: CaseIterable {
|
|||
case validateServer
|
||||
case updateSubtreeRoots
|
||||
case updateChainTip
|
||||
case processSuggestedScanRanges
|
||||
case computeSyncControlData
|
||||
case download
|
||||
case scan
|
||||
|
|
|
@ -21,7 +21,10 @@ extension ClearAlreadyScannedBlocksAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
guard let lastScannedHeight = await context.lastScannedHeight else {
|
||||
throw ZcashError.compactBlockProcessorLastScannedHeight
|
||||
}
|
||||
|
||||
try await storage.clear(upTo: lastScannedHeight)
|
||||
|
||||
await context.update(state: .enhance)
|
||||
|
|
|
@ -23,7 +23,19 @@ extension ClearCacheAction: Action {
|
|||
if await context.prevState == .idle {
|
||||
await context.update(state: .migrateLegacyCacheDB)
|
||||
} else {
|
||||
await context.update(state: .finished)
|
||||
if context.preferredSyncAlgorithm == .linear {
|
||||
await context.update(state: .finished)
|
||||
} else {
|
||||
if let supportedSyncAlgorithm = await context.supportedSyncAlgorithm {
|
||||
if supportedSyncAlgorithm == .linear {
|
||||
await context.update(state: .finished)
|
||||
} else {
|
||||
await context.update(state: .processSuggestedScanRanges)
|
||||
}
|
||||
} else {
|
||||
throw ZcashError.compactBlockProcessorSupportedSyncAlgorithm
|
||||
}
|
||||
}
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ extension ComputeSyncControlDataAction: Action {
|
|||
firstUnenhancedHeight: enhanceStart
|
||||
)
|
||||
|
||||
await context.update(lastScannedHeight: latestScannedHeight)
|
||||
await context.update(lastDownloadedHeight: latestScannedHeight)
|
||||
await context.update(syncControlData: syncControlData)
|
||||
await context.update(totalProgressRange: latestScannedHeight...latestBlockHeight)
|
||||
|
@ -72,7 +73,7 @@ extension ComputeSyncControlDataAction: Action {
|
|||
if latestBlockHeight < latestScannedHeight || latestBlockHeight == latestScannedHeight {
|
||||
await context.update(state: .finished)
|
||||
} else {
|
||||
await context.update(state: .fetchUTXO)
|
||||
await context.update(state: .download)
|
||||
}
|
||||
|
||||
return context
|
||||
|
|
|
@ -30,16 +30,15 @@ extension DownloadAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { true }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
|
||||
guard let lastScannedHeight = await context.lastScannedHeight else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
|
||||
// So the right range for this batch must be computed.
|
||||
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
|
||||
let batchRangeStart = lastScannedHeight
|
||||
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
|
@ -47,10 +46,11 @@ extension DownloadAction: Action {
|
|||
}
|
||||
|
||||
let batchRange = batchRangeStart...batchRangeEnd
|
||||
let downloadLimit = batchRange.upperBound + (2 * config.batchSize)
|
||||
let potentialDownloadLimit = batchRange.upperBound + (2 * config.batchSize)
|
||||
let downloadLimit = await context.syncControlData.latestBlockHeight >= potentialDownloadLimit ? potentialDownloadLimit : batchRangeEnd
|
||||
|
||||
logger.debug("Starting download with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
|
||||
await downloader.update(latestDownloadedBlockHeight: batchRange.lowerBound)
|
||||
await downloader.update(latestDownloadedBlockHeight: batchRange.lowerBound, force: true) // SbS
|
||||
try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
|
||||
await downloader.setDownloadLimit(downloadLimit)
|
||||
await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
|
||||
|
|
|
@ -49,7 +49,9 @@ extension EnhanceAction: Action {
|
|||
// download and scan.
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
guard let lastScannedHeight = await context.lastScannedHeight else {
|
||||
throw ZcashError.compactBlockProcessorLastScannedHeight
|
||||
}
|
||||
|
||||
guard let firstUnenhancedHeight = await context.syncControlData.firstUnenhancedHeight else {
|
||||
return await decideWhatToDoNext(context: context, lastScannedHeight: lastScannedHeight)
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// ProcessSuggestedScanRangesAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 02.08.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ProcessSuggestedScanRangesAction {
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let service: LightWalletService
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer) {
|
||||
service = container.resolve(LightWalletService.self)
|
||||
rustBackend = container.resolve(ZcashRustBackendWelding.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ProcessSuggestedScanRangesAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
logger.info("Getting the suggested scan ranges from the wallet database.")
|
||||
let scanRanges = try await rustBackend.suggestScanRanges()
|
||||
|
||||
if let firstRange = scanRanges.first {
|
||||
// If there is a range of blocks that needs to be verified, it will always
|
||||
// be returned as the first element of the vector of suggested ranges.
|
||||
if firstRange.priority == .verify {
|
||||
// TODO: [#1189] handle rewind, https://github.com/zcash/ZcashLightClientKit/issues/1189
|
||||
// REWIND to download.start height HERE
|
||||
}
|
||||
|
||||
let lowerBound = firstRange.range.lowerBound - 1
|
||||
let upperBound = firstRange.range.upperBound - 1
|
||||
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: upperBound,
|
||||
latestScannedHeight: lowerBound,
|
||||
firstUnenhancedHeight: lowerBound + 1
|
||||
)
|
||||
|
||||
logger.debug("""
|
||||
Init numbers:
|
||||
latestBlockHeight [BC]: \(upperBound)
|
||||
latestScannedHeight [DB]: \(lowerBound)
|
||||
firstUnenhancedHeight [DB]: \(lowerBound + 1)
|
||||
""")
|
||||
|
||||
await context.update(lastScannedHeight: lowerBound)
|
||||
await context.update(lastDownloadedHeight: lowerBound)
|
||||
await context.update(syncControlData: syncControlData)
|
||||
await context.update(totalProgressRange: lowerBound...upperBound)
|
||||
|
||||
await context.update(state: .download)
|
||||
} else {
|
||||
await context.update(state: .finished)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -23,7 +23,12 @@ extension SaplingParamsAction: Action {
|
|||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
logger.debug("Fetching sapling parameters")
|
||||
try await saplingParametersHandler.handleIfNeeded()
|
||||
await context.update(state: .download)
|
||||
|
||||
if context.preferredSyncAlgorithm == .spendBeforeSync {
|
||||
await context.update(state: .updateSubtreeRoots)
|
||||
} else {
|
||||
await context.update(state: .computeSyncControlData)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
@ -30,16 +30,15 @@ extension ScanAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { true }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
|
||||
guard let lastScannedHeight = await context.lastScannedHeight else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `scanRange` are scanned.
|
||||
// So the right range for this batch must be computed.
|
||||
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
|
||||
let batchRangeStart = lastScannedHeight
|
||||
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
|
@ -50,14 +49,24 @@ extension ScanAction: Action {
|
|||
|
||||
logger.debug("Starting scan blocks with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
|
||||
let totalProgressRange = await context.totalProgressRange
|
||||
try await blockScanner.scanBlocks(at: batchRange, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
|
||||
let progress = BlockProgress(
|
||||
startHeight: totalProgressRange.lowerBound,
|
||||
targetHeight: totalProgressRange.upperBound,
|
||||
progressHeight: lastScannedHeight
|
||||
)
|
||||
self?.logger.debug("progress: \(progress)")
|
||||
await didUpdate(.progressPartialUpdate(.syncing(progress)))
|
||||
|
||||
do {
|
||||
try await blockScanner.scanBlocks(at: batchRange, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
|
||||
let progress = BlockProgress(
|
||||
startHeight: totalProgressRange.lowerBound,
|
||||
targetHeight: totalProgressRange.upperBound,
|
||||
progressHeight: lastScannedHeight
|
||||
)
|
||||
self?.logger.debug("progress: \(progress)")
|
||||
await didUpdate(.progressPartialUpdate(.syncing(progress)))
|
||||
|
||||
// ScanAction is controlled locally so it must report back the updated scanned height
|
||||
await context.update(lastScannedHeight: lastScannedHeight)
|
||||
}
|
||||
} catch {
|
||||
// TODO: [#1189] check isContinuityError, https://github.com/zcash/ZcashLightClientKit/issues/1189
|
||||
// if YES, REWIND to height at what error occured - at least 1 block
|
||||
throw error
|
||||
}
|
||||
|
||||
return await update(context: context)
|
||||
|
|
|
@ -28,9 +28,7 @@ extension UpdateChainTipAction: Action {
|
|||
logger.info("Latest block height is \(latestBlockHeight)")
|
||||
try await rustBackend.updateChainTip(height: Int32(latestBlockHeight))
|
||||
|
||||
// TODO: [#1169] Switching back to linear sync for now before step 5 & 6 are implemented
|
||||
// https://github.com/zcash/ZcashLightClientKit/issues/1169
|
||||
await context.update(state: .computeSyncControlData)
|
||||
await context.update(state: .processSuggestedScanRanges)
|
||||
|
||||
return context
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ extension UpdateSubtreeRootsAction: Action {
|
|||
request.shieldedProtocol = .sapling
|
||||
request.maxEntries = 65536
|
||||
|
||||
logger.info("Attempt to get subtree roots, this may fail because lightwalletd may not support DAG sync.")
|
||||
logger.info("Attempt to get subtree roots, this may fail because lightwalletd may not support Spend before Sync.")
|
||||
let stream = service.getSubtreeRoots(request)
|
||||
|
||||
var roots: [SubtreeRoot] = []
|
||||
|
@ -42,12 +42,14 @@ extension UpdateSubtreeRootsAction: Action {
|
|||
err = error
|
||||
}
|
||||
|
||||
// In case of error, the lightwalletd doesn't support DAG sync -> switching to linear sync.
|
||||
// In case of error, the lightwalletd doesn't support Spend before Sync -> switching to linear sync.
|
||||
// Likewise, no subtree roots results in switching to linear sync.
|
||||
if err != nil || roots.isEmpty {
|
||||
logger.info("DAG sync is not possible, switching to linear sync.")
|
||||
logger.info("Spend before Sync is not possible, switching to linear sync.")
|
||||
await context.update(supportedSyncAlgorithm: .linear)
|
||||
await context.update(state: .computeSyncControlData)
|
||||
} else {
|
||||
await context.update(supportedSyncAlgorithm: .spendBeforeSync)
|
||||
logger.info("Sapling tree has \(roots.count) subtrees")
|
||||
do {
|
||||
try await rustBackend.putSaplingSubtreeRoots(startIndex: UInt64(request.startIndex), roots: roots)
|
||||
|
|
|
@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
|
|||
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
|
||||
}
|
||||
|
||||
await context.update(state: .updateSubtreeRoots)
|
||||
await context.update(state: .fetchUTXO)
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ actor CompactBlockProcessor {
|
|||
let network: ZcashNetwork
|
||||
let saplingActivation: BlockHeight
|
||||
let cacheDbURL: URL?
|
||||
let syncAlgorithm: SyncAlgorithm
|
||||
var blockPollInterval: TimeInterval {
|
||||
TimeInterval.random(in: ZcashSDK.defaultPollInterval / 2 ... ZcashSDK.defaultPollInterval * 1.5)
|
||||
}
|
||||
|
@ -89,6 +90,7 @@ actor CompactBlockProcessor {
|
|||
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
||||
walletBirthdayProvider: @escaping () -> BlockHeight,
|
||||
saplingActivation: BlockHeight,
|
||||
syncAlgorithm: SyncAlgorithm = .linear,
|
||||
network: ZcashNetwork
|
||||
) {
|
||||
self.alias = alias
|
||||
|
@ -106,6 +108,7 @@ actor CompactBlockProcessor {
|
|||
self.walletBirthdayProvider = walletBirthdayProvider
|
||||
self.saplingActivation = saplingActivation
|
||||
self.cacheDbURL = cacheDbURL
|
||||
self.syncAlgorithm = syncAlgorithm
|
||||
}
|
||||
|
||||
init(
|
||||
|
@ -121,6 +124,7 @@ actor CompactBlockProcessor {
|
|||
maxBackoffInterval: TimeInterval = ZcashSDK.defaultMaxBackOffInterval,
|
||||
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
||||
walletBirthdayProvider: @escaping () -> BlockHeight,
|
||||
syncAlgorithm: SyncAlgorithm = .linear,
|
||||
network: ZcashNetwork
|
||||
) {
|
||||
self.alias = alias
|
||||
|
@ -138,6 +142,7 @@ actor CompactBlockProcessor {
|
|||
self.retries = retries
|
||||
self.maxBackoffInterval = maxBackoffInterval
|
||||
self.rewindDistance = rewindDistance
|
||||
self.syncAlgorithm = syncAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,13 +174,18 @@ actor CompactBlockProcessor {
|
|||
outputParamsURL: initializer.outputParamsURL,
|
||||
saplingParamsSourceURL: initializer.saplingParamsSourceURL,
|
||||
walletBirthdayProvider: walletBirthdayProvider,
|
||||
syncAlgorithm: initializer.syncAlgorithm,
|
||||
network: initializer.network
|
||||
),
|
||||
accountRepository: initializer.accountRepository
|
||||
)
|
||||
}
|
||||
|
||||
init(container: DIContainer, config: Configuration, accountRepository: AccountRepository) {
|
||||
init(
|
||||
container: DIContainer,
|
||||
config: Configuration,
|
||||
accountRepository: AccountRepository
|
||||
) {
|
||||
Dependencies.setupCompactBlockProcessor(
|
||||
in: container,
|
||||
config: config,
|
||||
|
@ -183,7 +193,7 @@ actor CompactBlockProcessor {
|
|||
)
|
||||
|
||||
let configProvider = ConfigProvider(config: config)
|
||||
context = ActionContext(state: .idle)
|
||||
context = ActionContext(state: .idle, preferredSyncAlgorithm: config.syncAlgorithm)
|
||||
actions = Self.makeActions(container: container, configProvider: configProvider)
|
||||
|
||||
self.metrics = container.resolve(SDKMetrics.self)
|
||||
|
@ -218,6 +228,8 @@ actor CompactBlockProcessor {
|
|||
action = UpdateSubtreeRootsAction(container: container)
|
||||
case .updateChainTip:
|
||||
action = UpdateChainTipAction(container: container)
|
||||
case .processSuggestedScanRanges:
|
||||
action = ProcessSuggestedScanRangesAction(container: container)
|
||||
case .computeSyncControlData:
|
||||
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
|
||||
case .download:
|
||||
|
@ -593,6 +605,8 @@ extension CompactBlockProcessor {
|
|||
break
|
||||
case .updateChainTip:
|
||||
break
|
||||
case .processSuggestedScanRanges:
|
||||
break
|
||||
case .computeSyncControlData:
|
||||
break
|
||||
case .download:
|
||||
|
@ -620,7 +634,7 @@ extension CompactBlockProcessor {
|
|||
|
||||
private func resetContext() async {
|
||||
let lastEnhancedheight = await context.lastEnhancedHeight
|
||||
context = ActionContext(state: .idle)
|
||||
context = ActionContext(state: .idle, preferredSyncAlgorithm: config.syncAlgorithm)
|
||||
await context.update(lastEnhancedHeight: lastEnhancedheight)
|
||||
await compactBlockProgress.reset()
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ protocol BlockDownloader {
|
|||
|
||||
/// Updates the internal in memory value of latest downloaded block height. This way the `BlockDownloader` works with the current latest height and can
|
||||
/// continue on parallel downloading of next batch.
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async
|
||||
func update(latestDownloadedBlockHeight: BlockHeight, force: Bool) async
|
||||
/// Provides the value of latest downloaded height.
|
||||
func latestDownloadedBlockHeight() async -> BlockHeight
|
||||
/// In case rewind is needed, the latestDownloadedBlockHeight is rewritten forcefully.
|
||||
|
@ -253,8 +253,8 @@ extension BlockDownloaderImpl: BlockDownloader {
|
|||
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight ?? -1
|
||||
}
|
||||
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async {
|
||||
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight {
|
||||
func update(latestDownloadedBlockHeight: BlockHeight, force: Bool = false) async {
|
||||
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight || force {
|
||||
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ extension BlockScannerImpl: BlockScanner {
|
|||
logger.debug("Going to scan blocks in range: \(range)")
|
||||
try Task.checkCancellation()
|
||||
|
||||
let scanStartHeight = try await transactionRepository.lastScannedHeight()
|
||||
let scanStartHeight = range.lowerBound
|
||||
let targetScanHeight = range.upperBound
|
||||
|
||||
var scannedNewBlocks = false
|
||||
|
@ -65,11 +65,7 @@ extension BlockScannerImpl: BlockScanner {
|
|||
|
||||
let scanFinishTime = Date()
|
||||
|
||||
if let lastScannedBlock = try await transactionRepository.lastScannedBlock() {
|
||||
lastScannedHeight = lastScannedBlock.height
|
||||
await latestBlocksDataProvider.updateLatestScannedHeight(lastScannedHeight)
|
||||
await latestBlocksDataProvider.updateLatestScannedTime(TimeInterval(lastScannedBlock.time))
|
||||
}
|
||||
lastScannedHeight = startHeight + Int(batchSize) - 1
|
||||
|
||||
scannedNewBlocks = previousScannedHeight != lastScannedHeight
|
||||
if scannedNewBlocks {
|
||||
|
|
|
@ -540,6 +540,12 @@ public enum ZcashError: Equatable, Error {
|
|||
/// Put sapling subtree roots to the DB failed.
|
||||
/// ZCBPEO0019
|
||||
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
|
||||
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
|
||||
/// ZCBPEO0020
|
||||
case compactBlockProcessorLastScannedHeight
|
||||
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
|
||||
/// ZCBPEO0021
|
||||
case compactBlockProcessorSupportedSyncAlgorithm
|
||||
/// The synchronizer is unprepared.
|
||||
/// ZSYNCO0001
|
||||
case synchronizerNotPrepared
|
||||
|
@ -715,6 +721,8 @@ public enum ZcashError: Equatable, Error {
|
|||
case .compactBlockProcessorConsensusBranchID: return "Consensus BranchIDs don't match this is probably an API or programming error."
|
||||
case .compactBlockProcessorDownloadBlockActionRewind: return "Rewind of DownloadBlockAction failed as no action is possible to unwrapp."
|
||||
case .compactBlockProcessorPutSaplingSubtreeRoots: return "Put sapling subtree roots to the DB failed."
|
||||
case .compactBlockProcessorLastScannedHeight: return "Getting the `lastScannedHeight` failed but it's supposed to always provide some value."
|
||||
case .compactBlockProcessorSupportedSyncAlgorithm: return "Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value."
|
||||
case .synchronizerNotPrepared: return "The synchronizer is unprepared."
|
||||
case .synchronizerSendMemoToTransparentAddress: return "Memos can't be sent to transparent addresses."
|
||||
case .synchronizerShieldFundsInsuficientTransparentFunds: return "There is not enough transparent funds to cover fee for the shielding."
|
||||
|
@ -880,6 +888,8 @@ public enum ZcashError: Equatable, Error {
|
|||
case .compactBlockProcessorConsensusBranchID: return .compactBlockProcessorConsensusBranchID
|
||||
case .compactBlockProcessorDownloadBlockActionRewind: return .compactBlockProcessorDownloadBlockActionRewind
|
||||
case .compactBlockProcessorPutSaplingSubtreeRoots: return .compactBlockProcessorPutSaplingSubtreeRoots
|
||||
case .compactBlockProcessorLastScannedHeight: return .compactBlockProcessorLastScannedHeight
|
||||
case .compactBlockProcessorSupportedSyncAlgorithm: return .compactBlockProcessorSupportedSyncAlgorithm
|
||||
case .synchronizerNotPrepared: return .synchronizerNotPrepared
|
||||
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
|
||||
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds
|
||||
|
|
|
@ -317,6 +317,10 @@ public enum ZcashErrorCode: String {
|
|||
case compactBlockProcessorDownloadBlockActionRewind = "ZCBPEO0018"
|
||||
/// Put sapling subtree roots to the DB failed.
|
||||
case compactBlockProcessorPutSaplingSubtreeRoots = "ZCBPEO0019"
|
||||
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
|
||||
case compactBlockProcessorLastScannedHeight = "ZCBPEO0020"
|
||||
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
|
||||
case compactBlockProcessorSupportedSyncAlgorithm = "ZCBPEO0021"
|
||||
/// The synchronizer is unprepared.
|
||||
case synchronizerNotPrepared = "ZSYNCO0001"
|
||||
/// Memos can't be sent to transparent addresses.
|
||||
|
|
|
@ -613,6 +613,12 @@ enum ZcashErrorDefinition {
|
|||
/// Put sapling subtree roots to the DB failed.
|
||||
// sourcery: code="ZCBPEO0019"
|
||||
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
|
||||
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
|
||||
// sourcery: code="ZCBPEO0020"
|
||||
case compactBlockProcessorLastScannedHeight
|
||||
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
|
||||
// sourcery: code="ZCBPEO0021"
|
||||
case compactBlockProcessorSupportedSyncAlgorithm
|
||||
|
||||
// MARK: - SDKSynchronizer
|
||||
|
||||
|
|
|
@ -126,6 +126,7 @@ public class Initializer {
|
|||
let network: ZcashNetwork
|
||||
let logger: Logger
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let syncAlgorithm: SyncAlgorithm
|
||||
|
||||
/// The effective birthday of the wallet based on the height provided when initializing and the checkpoints available on this SDK.
|
||||
///
|
||||
|
@ -165,6 +166,7 @@ public class Initializer {
|
|||
outputParamsURL: URL,
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL,
|
||||
alias: ZcashSynchronizerAlias = .default,
|
||||
syncAlgorithm: SyncAlgorithm = .linear,
|
||||
loggingPolicy: LoggingPolicy = .default(.debug),
|
||||
enableBackendTracing: Bool = false
|
||||
) {
|
||||
|
@ -197,6 +199,7 @@ public class Initializer {
|
|||
saplingParamsSourceURL: saplingParamsSourceURL,
|
||||
alias: alias,
|
||||
urlsParsingError: parsingError,
|
||||
syncAlgorithm: syncAlgorithm,
|
||||
loggingPolicy: loggingPolicy
|
||||
)
|
||||
}
|
||||
|
@ -257,6 +260,7 @@ public class Initializer {
|
|||
saplingParamsSourceURL: SaplingParamsSourceURL,
|
||||
alias: ZcashSynchronizerAlias,
|
||||
urlsParsingError: ZcashError?,
|
||||
syncAlgorithm: SyncAlgorithm = .linear,
|
||||
loggingPolicy: LoggingPolicy = .default(.debug)
|
||||
) {
|
||||
self.container = container
|
||||
|
@ -284,6 +288,7 @@ public class Initializer {
|
|||
self.walletBirthday = Checkpoint.birthday(with: 0, network: network).height
|
||||
self.urlsParsingError = urlsParsingError
|
||||
self.logger = container.resolve(Logger.self)
|
||||
self.syncAlgorithm = syncAlgorithm
|
||||
}
|
||||
|
||||
private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory {
|
||||
|
|
|
@ -8,6 +8,20 @@
|
|||
import Foundation
|
||||
|
||||
struct ScanRange {
|
||||
enum Priority: UInt8 {
|
||||
case unknown = 0
|
||||
case scanned = 10
|
||||
case historic = 20
|
||||
case openAdjacent = 30
|
||||
case foundNote = 40
|
||||
case chainTip = 50
|
||||
case verify = 60
|
||||
|
||||
init(_ value: UInt8) {
|
||||
self = Priority(rawValue: value) ?? .unknown
|
||||
}
|
||||
}
|
||||
|
||||
let range: Range<BlockHeight>
|
||||
let priority: UInt8
|
||||
let priority: Priority
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
|
|||
latestScannedTime = TimeInterval(time)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateBlockData() async {
|
||||
if let newLatestBlockHeight = try? await service.latestBlockHeight(),
|
||||
latestBlockHeight < newLatestBlockHeight {
|
||||
|
|
|
@ -585,7 +585,7 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
BlockHeight(scanRange.start),
|
||||
BlockHeight(scanRange.end)
|
||||
)),
|
||||
priority: scanRange.priority
|
||||
priority: ScanRange.Priority(scanRange.priority)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -120,6 +120,9 @@ public protocol Synchronizer: AnyObject {
|
|||
/// An object that when enabled collects mertrics from the synchronizer
|
||||
var metrics: SDKMetrics { get }
|
||||
|
||||
/// Default algorithm used to sync the stored wallet with the blockchain.
|
||||
var syncAlgorithm: SyncAlgorithm { get }
|
||||
|
||||
/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
|
||||
/// database migrations. most of the times the seed won't be needed. If they do and are
|
||||
/// not provided this will fail with `InitializationResult.seedRequired`. It could
|
||||
|
@ -427,6 +430,15 @@ enum InternalSyncStatus: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
/// Algorithm used to sync the sdk with the blockchain
|
||||
public enum SyncAlgorithm: Equatable {
|
||||
/// Linear sync processes the unsynced blocks in a linear way up to the chain tip
|
||||
case linear
|
||||
/// Spend before Sync processes the unsynced blocks non-lineary, in prioritised ranges relevant to the stored wallet.
|
||||
/// Note: This feature is in development (alpha version) so use carefully.
|
||||
case spendBeforeSync
|
||||
}
|
||||
|
||||
/// Kind of transactions handled by a Synchronizer
|
||||
public enum TransactionKind {
|
||||
case sent
|
||||
|
|
|
@ -24,7 +24,9 @@ public class SDKSynchronizer: Synchronizer {
|
|||
|
||||
public let metrics: SDKMetrics
|
||||
public let logger: Logger
|
||||
|
||||
public var syncAlgorithm: SyncAlgorithm = .linear
|
||||
private var requestedSyncAlgorithm: SyncAlgorithm?
|
||||
|
||||
// Don't read this variable directly. Use `status` instead. And don't update this variable directly use `updateStatus()` methods instead.
|
||||
private var underlyingStatus: GenericActor<InternalSyncStatus>
|
||||
var status: InternalSyncStatus {
|
||||
|
@ -87,6 +89,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
self.syncSession = SyncSession(.nullID)
|
||||
self.syncSessionTicker = syncSessionTicker
|
||||
self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self)
|
||||
self.syncAlgorithm = initializer.syncAlgorithm
|
||||
|
||||
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
|
||||
self?.connectivityStateChanged(oldState: oldState, newState: newState)
|
||||
|
@ -542,7 +545,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
|
||||
return subject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
// MARK: notify state
|
||||
|
||||
private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState {
|
||||
|
|
|
@ -25,9 +25,11 @@ final class ClearAlreadyScannedBlocksActionTests: ZcashTestCase {
|
|||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await clearAlreadyScannedBlocksAction.run(with: .init(state: .clearAlreadyScannedBlocks)) { _ in }
|
||||
let context = ActionContext(state: .clearAlreadyScannedBlocks)
|
||||
await context.update(lastScannedHeight: -1)
|
||||
|
||||
let nextContext = try await clearAlreadyScannedBlocksAction.run(with: context) { _ in }
|
||||
XCTAssertTrue(compactBlockRepositoryMock.clearUpToCalled, "storage.clear(upTo:) is expected to be called.")
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .enhance,
|
||||
|
|
|
@ -81,8 +81,8 @@ final class ComputeSyncControlDataActionTests: ZcashTestCase {
|
|||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .fetchUTXO,
|
||||
"nextContext after .computeSyncControlData is expected to be .fetchUTXO but received \(nextState)"
|
||||
nextState == .download,
|
||||
"nextContext after .computeSyncControlData is expected to be .download but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncControlDataAction_checksBeforeSyncCase is not expected to fail. \(error)")
|
||||
|
|
|
@ -22,7 +22,7 @@ final class DownloadActionTests: ZcashTestCase {
|
|||
blockDownloaderMock.setDownloadLimitClosure = { _ in }
|
||||
blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in }
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in }
|
||||
blockDownloaderMock.updateLatestDownloadedBlockHeightClosure = { _ in }
|
||||
blockDownloaderMock.updateLatestDownloadedBlockHeightForceClosure = { _, _ in }
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock,
|
||||
|
@ -33,11 +33,11 @@ final class DownloadActionTests: ZcashTestCase {
|
|||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
await syncContext.update(lastScannedHeight: 1000)
|
||||
|
||||
do {
|
||||
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is expected to be called.")
|
||||
|
@ -111,7 +111,6 @@ final class DownloadActionTests: ZcashTestCase {
|
|||
do {
|
||||
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.")
|
||||
|
|
|
@ -80,7 +80,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_NoEnhanceRange is not expected to fail. \(error)")
|
||||
|
@ -104,7 +103,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_1000BlocksConditionNotFulfilled is not expected to fail. \(error)")
|
||||
|
@ -163,8 +161,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
XCTAssertEqual(receivedTransaction.expiryHeight, transaction.expiryHeight, "ReceivedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_FoundTransactions is not expected to fail. \(error)")
|
||||
}
|
||||
|
@ -226,8 +222,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
}
|
||||
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
|
||||
}
|
||||
|
@ -289,8 +283,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
}
|
||||
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
|
||||
}
|
||||
|
@ -307,6 +299,7 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
await syncContext.update(syncControlData: syncControlData)
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
await syncContext.update(lastScannedHeight: underlyingScanRange?.lowerBound ?? -1)
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ final class SaplingParamsActionTests: ZcashTestCase {
|
|||
XCTAssertTrue(saplingParametersHandlerMock.handleIfNeededCalled, "saplingParametersHandler.handleIfNeeded() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .download,
|
||||
"nextContext after .handleSaplingParams is expected to be .download but received \(nextState)"
|
||||
nextState == .computeSyncControlData,
|
||||
"nextContext after .handleSaplingParams is expected to be .computeSyncControlData but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testSaplingParamsAction_NextAction is not expected to fail. \(error)")
|
||||
|
|
|
@ -22,6 +22,8 @@ final class ScanActionTests: ZcashTestCase {
|
|||
let scanAction = setupAction(blockScannerMock, transactionRepositoryMock, loggerMock)
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
await syncContext.update(lastScannedHeight: 1500)
|
||||
|
||||
do {
|
||||
let nextContext = try await scanAction.run(with: syncContext) { event in
|
||||
guard case .progressPartialUpdate(.syncing(let progress)) = event else {
|
||||
|
@ -32,7 +34,6 @@ final class ScanActionTests: ZcashTestCase {
|
|||
XCTAssertEqual(progress.targetHeight, BlockHeight(2000))
|
||||
XCTAssertEqual(progress.progressHeight, BlockHeight(1500))
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is expected to be called.")
|
||||
XCTAssertTrue(blockScannerMock.scanBlocksAtTotalProgressRangeDidScanCalled, "blockScanner.scanBlocks(...) is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
|
@ -78,7 +79,6 @@ final class ScanActionTests: ZcashTestCase {
|
|||
|
||||
do {
|
||||
_ = try await scanAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is not expected to be called.")
|
||||
XCTAssertFalse(blockScannerMock.scanBlocksAtTotalProgressRangeDidScanCalled, "blockScanner.scanBlocks(...) is not expected to be called.")
|
||||
} catch {
|
||||
|
|
|
@ -31,8 +31,8 @@ final class ValidateServerActionTests: ZcashTestCase {
|
|||
let nextContext = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .updateSubtreeRoots,
|
||||
"nextContext after .validateServer is expected to be .updateSubtreeRoots but received \(nextState)"
|
||||
nextState == .fetchUTXO,
|
||||
"nextContext after .validateServer is expected to be .fetchUTXO but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testValidateServerAction_NextAction is not expected to fail. \(error)")
|
||||
|
|
|
@ -97,17 +97,17 @@ class BlockDownloaderMock: BlockDownloader {
|
|||
|
||||
// MARK: - update
|
||||
|
||||
var updateLatestDownloadedBlockHeightCallsCount = 0
|
||||
var updateLatestDownloadedBlockHeightCalled: Bool {
|
||||
return updateLatestDownloadedBlockHeightCallsCount > 0
|
||||
var updateLatestDownloadedBlockHeightForceCallsCount = 0
|
||||
var updateLatestDownloadedBlockHeightForceCalled: Bool {
|
||||
return updateLatestDownloadedBlockHeightForceCallsCount > 0
|
||||
}
|
||||
var updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight: BlockHeight?
|
||||
var updateLatestDownloadedBlockHeightClosure: ((BlockHeight) async -> Void)?
|
||||
var updateLatestDownloadedBlockHeightForceReceivedArguments: (latestDownloadedBlockHeight: BlockHeight, force: Bool)?
|
||||
var updateLatestDownloadedBlockHeightForceClosure: ((BlockHeight, Bool) async -> Void)?
|
||||
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async {
|
||||
updateLatestDownloadedBlockHeightCallsCount += 1
|
||||
updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight = latestDownloadedBlockHeight
|
||||
await updateLatestDownloadedBlockHeightClosure!(latestDownloadedBlockHeight)
|
||||
func update(latestDownloadedBlockHeight: BlockHeight, force: Bool) async {
|
||||
updateLatestDownloadedBlockHeightForceCallsCount += 1
|
||||
updateLatestDownloadedBlockHeightForceReceivedArguments = (latestDownloadedBlockHeight: latestDownloadedBlockHeight, force: force)
|
||||
await updateLatestDownloadedBlockHeightForceClosure!(latestDownloadedBlockHeight, force)
|
||||
}
|
||||
|
||||
// MARK: - latestDownloadedBlockHeight
|
||||
|
@ -1043,6 +1043,10 @@ class SynchronizerMock: Synchronizer {
|
|||
get { return underlyingMetrics }
|
||||
}
|
||||
var underlyingMetrics: SDKMetrics!
|
||||
var syncAlgorithm: SyncAlgorithm {
|
||||
get { return underlyingSyncAlgorithm }
|
||||
}
|
||||
var underlyingSyncAlgorithm: SyncAlgorithm!
|
||||
var pendingTransactions: [ZcashTransaction.Overview] {
|
||||
get async { return underlyingPendingTransactions }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue