2019-10-18 11:45:19 -07:00
|
|
|
//
|
|
|
|
// CompactBlockProcessor.swift
|
|
|
|
// ZcashLightClientKit
|
|
|
|
//
|
|
|
|
// Created by Francisco Gindre on 18/09/2019.
|
|
|
|
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2023-02-07 05:22:28 -08:00
|
|
|
import Combine
|
2021-03-08 10:47:36 -08:00
|
|
|
|
|
|
|
public typealias RefreshedUTXOs = (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
/// The compact block processor is in charge of orchestrating the download and caching of compact blocks from a LightWalletEndpoint
|
|
|
|
/// when started the processor downloads does a download - validate - scan cycle until it reaches latest height on the blockchain.
|
|
|
|
actor CompactBlockProcessor {
|
2023-05-05 08:04:13 -07:00
|
|
|
// It would be better to use Combine here but Combine doesn't work great with async. When this runs regularly only one closure is stored here
|
|
|
|
// and that is one provided by `SDKSynchronizer`. But while running tests more "subscribers" is required here. Therefore it's required to handle
|
|
|
|
// more closures here.
|
|
|
|
private var eventClosures: [String: EventClosure] = [:]
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private var syncTask: Task<Void, Error>?
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private let actions: [CBPState: Action]
|
2023-10-12 06:59:47 -07:00
|
|
|
var context: ActionContext
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private(set) var config: Configuration
|
|
|
|
private let configProvider: ConfigProvider
|
|
|
|
private var afterSyncHooksManager = AfterSyncHooksManager()
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private let accountRepository: AccountRepository
|
2024-02-07 02:48:59 -08:00
|
|
|
var blockDownloaderService: BlockDownloaderService
|
|
|
|
private var latestBlocksDataProvider: LatestBlocksDataProvider
|
2023-05-05 08:04:13 -07:00
|
|
|
private let logger: Logger
|
|
|
|
private let metrics: SDKMetrics
|
|
|
|
private let rustBackend: ZcashRustBackendWelding
|
2024-02-07 02:48:59 -08:00
|
|
|
var service: LightWalletService
|
2023-05-05 08:04:13 -07:00
|
|
|
let storage: CompactBlockRepository
|
|
|
|
private let transactionRepository: TransactionRepository
|
|
|
|
private let fileManager: ZcashFileManager
|
2019-10-18 11:45:19 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private var retryAttempts: Int = 0
|
2024-02-06 01:10:02 -08:00
|
|
|
private var serviceFailureRetryAttempts: Int = 0
|
2023-05-05 08:04:13 -07:00
|
|
|
private var backoffTimer: Timer?
|
|
|
|
private var consecutiveChainValidationErrors: Int = 0
|
|
|
|
|
|
|
|
private var compactBlockProgress: CompactBlockProgress = .zero
|
|
|
|
|
2022-07-29 06:07:08 -07:00
|
|
|
/// Compact Block Processor configuration
|
|
|
|
///
|
2023-02-02 08:58:12 -08:00
|
|
|
/// - parameter fsBlockCacheRoot: absolute root path where the filesystem block cache will be stored.
|
|
|
|
/// - parameter dataDb: absolute file path of the DB where all information derived from the cache DB is stored.
|
|
|
|
/// - parameter spendParamsURL: absolute file path of the sapling-spend.params file
|
|
|
|
/// - parameter outputParamsURL: absolute file path of the sapling-output.params file
|
2023-01-12 04:05:11 -08:00
|
|
|
struct Configuration {
|
2023-03-22 05:47:32 -07:00
|
|
|
let alias: ZcashSynchronizerAlias
|
2023-02-16 08:27:49 -08:00
|
|
|
let saplingParamsSourceURL: SaplingParamsSourceURL
|
2023-04-24 14:15:20 -07:00
|
|
|
let fsBlockCacheRoot: URL
|
|
|
|
let dataDb: URL
|
|
|
|
let spendParamsURL: URL
|
|
|
|
let outputParamsURL: URL
|
2023-05-05 08:04:13 -07:00
|
|
|
let enhanceBatchSize: Int
|
|
|
|
let batchSize: Int
|
2023-04-24 14:15:20 -07:00
|
|
|
let retries: Int
|
|
|
|
let maxBackoffInterval: TimeInterval
|
|
|
|
let maxReorgSize = ZcashSDK.maxReorgSize
|
|
|
|
let rewindDistance: Int
|
2023-03-10 03:58:28 -08:00
|
|
|
let walletBirthdayProvider: () -> BlockHeight
|
2023-04-24 14:15:20 -07:00
|
|
|
var walletBirthday: BlockHeight { walletBirthdayProvider() }
|
|
|
|
let downloadBufferSize: Int = 10
|
|
|
|
let network: ZcashNetwork
|
|
|
|
let saplingActivation: BlockHeight
|
|
|
|
let cacheDbURL: URL?
|
2023-01-12 04:05:11 -08:00
|
|
|
var blockPollInterval: TimeInterval {
|
2021-09-17 06:49:58 -07:00
|
|
|
TimeInterval.random(in: ZcashSDK.defaultPollInterval / 2 ... ZcashSDK.defaultPollInterval * 1.5)
|
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
|
2023-04-24 14:15:20 -07:00
|
|
|
init(
|
2023-03-22 05:47:32 -07:00
|
|
|
alias: ZcashSynchronizerAlias,
|
2023-02-02 08:58:12 -08:00
|
|
|
cacheDbURL: URL? = nil,
|
|
|
|
fsBlockCacheRoot: URL,
|
2021-07-26 16:22:30 -07:00
|
|
|
dataDb: URL,
|
2022-11-28 22:38:28 -08:00
|
|
|
spendParamsURL: URL,
|
|
|
|
outputParamsURL: URL,
|
2023-02-16 08:27:49 -08:00
|
|
|
saplingParamsSourceURL: SaplingParamsSourceURL,
|
2023-05-05 08:04:13 -07:00
|
|
|
enhanceBatchSize: Int = ZcashSDK.DefaultEnhanceBatch,
|
|
|
|
batchSize: Int = ZcashSDK.DefaultBatchSize,
|
2023-04-24 14:15:20 -07:00
|
|
|
retries: Int = ZcashSDK.defaultRetries,
|
|
|
|
maxBackoffInterval: TimeInterval = ZcashSDK.defaultMaxBackOffInterval,
|
|
|
|
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
2023-03-10 03:58:28 -08:00
|
|
|
walletBirthdayProvider: @escaping () -> BlockHeight,
|
2021-07-26 16:22:30 -07:00
|
|
|
saplingActivation: BlockHeight,
|
2021-07-28 09:59:10 -07:00
|
|
|
network: ZcashNetwork
|
2021-09-17 06:49:58 -07:00
|
|
|
) {
|
2023-03-22 05:47:32 -07:00
|
|
|
self.alias = alias
|
2023-02-02 08:58:12 -08:00
|
|
|
self.fsBlockCacheRoot = fsBlockCacheRoot
|
2020-06-03 16:18:57 -07:00
|
|
|
self.dataDb = dataDb
|
2022-11-28 22:38:28 -08:00
|
|
|
self.spendParamsURL = spendParamsURL
|
|
|
|
self.outputParamsURL = outputParamsURL
|
2023-02-16 08:27:49 -08:00
|
|
|
self.saplingParamsSourceURL = saplingParamsSourceURL
|
2021-07-26 16:22:30 -07:00
|
|
|
self.network = network
|
2023-05-05 08:04:13 -07:00
|
|
|
self.enhanceBatchSize = enhanceBatchSize
|
|
|
|
self.batchSize = batchSize
|
2020-06-03 16:18:57 -07:00
|
|
|
self.retries = retries
|
|
|
|
self.maxBackoffInterval = maxBackoffInterval
|
|
|
|
self.rewindDistance = rewindDistance
|
2023-03-10 03:58:28 -08:00
|
|
|
self.walletBirthdayProvider = walletBirthdayProvider
|
2020-06-03 16:18:57 -07:00
|
|
|
self.saplingActivation = saplingActivation
|
2023-02-02 08:58:12 -08:00
|
|
|
self.cacheDbURL = cacheDbURL
|
2020-06-03 16:18:57 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
|
2023-01-12 04:05:11 -08:00
|
|
|
init(
|
2023-03-22 05:47:32 -07:00
|
|
|
alias: ZcashSynchronizerAlias,
|
2023-02-02 08:58:12 -08:00
|
|
|
fsBlockCacheRoot: URL,
|
2022-11-28 22:38:28 -08:00
|
|
|
dataDb: URL,
|
|
|
|
spendParamsURL: URL,
|
|
|
|
outputParamsURL: URL,
|
2023-02-16 08:27:49 -08:00
|
|
|
saplingParamsSourceURL: SaplingParamsSourceURL,
|
2023-05-05 08:04:13 -07:00
|
|
|
enhanceBatchSize: Int = ZcashSDK.DefaultEnhanceBatch,
|
|
|
|
batchSize: Int = ZcashSDK.DefaultBatchSize,
|
2023-04-24 14:15:20 -07:00
|
|
|
retries: Int = ZcashSDK.defaultRetries,
|
|
|
|
maxBackoffInterval: TimeInterval = ZcashSDK.defaultMaxBackOffInterval,
|
|
|
|
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
2023-03-10 03:58:28 -08:00
|
|
|
walletBirthdayProvider: @escaping () -> BlockHeight,
|
2022-11-28 22:38:28 -08:00
|
|
|
network: ZcashNetwork
|
|
|
|
) {
|
2023-03-22 05:47:32 -07:00
|
|
|
self.alias = alias
|
2023-02-02 08:58:12 -08:00
|
|
|
self.fsBlockCacheRoot = fsBlockCacheRoot
|
2019-11-04 15:18:07 -08:00
|
|
|
self.dataDb = dataDb
|
2022-11-28 22:38:28 -08:00
|
|
|
self.spendParamsURL = spendParamsURL
|
|
|
|
self.outputParamsURL = outputParamsURL
|
2023-02-16 08:27:49 -08:00
|
|
|
self.saplingParamsSourceURL = saplingParamsSourceURL
|
2023-03-10 03:58:28 -08:00
|
|
|
self.walletBirthdayProvider = walletBirthdayProvider
|
2021-09-15 05:21:29 -07:00
|
|
|
self.saplingActivation = network.constants.saplingActivationHeight
|
2021-07-28 09:59:10 -07:00
|
|
|
self.network = network
|
2023-02-02 08:58:12 -08:00
|
|
|
self.cacheDbURL = nil
|
2023-05-05 08:04:13 -07:00
|
|
|
self.enhanceBatchSize = enhanceBatchSize
|
|
|
|
self.batchSize = batchSize
|
2023-04-24 14:15:20 -07:00
|
|
|
self.retries = retries
|
|
|
|
self.maxBackoffInterval = maxBackoffInterval
|
|
|
|
self.rewindDistance = rewindDistance
|
2021-09-17 06:49:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:07:08 -07:00
|
|
|
/// Initializes a CompactBlockProcessor instance
|
|
|
|
/// - Parameters:
|
|
|
|
/// - service: concrete implementation of `LightWalletService` protocol
|
2023-02-02 08:58:12 -08:00
|
|
|
/// - storage: concrete implementation of `CompactBlockRepository` protocol
|
2022-07-29 06:07:08 -07:00
|
|
|
/// - backend: a class that complies to `ZcashRustBackendWelding`
|
|
|
|
/// - config: `Configuration` struct for this processor
|
2023-05-05 08:04:13 -07:00
|
|
|
init(container: DIContainer, config: Configuration) {
|
2021-09-15 05:21:29 -07:00
|
|
|
self.init(
|
2023-05-01 07:28:59 -07:00
|
|
|
container: container,
|
2021-09-15 05:21:29 -07:00
|
|
|
config: config,
|
2023-05-01 07:28:59 -07:00
|
|
|
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: container.resolve(Logger.self))
|
2021-09-17 06:49:58 -07:00
|
|
|
)
|
2020-04-23 10:11:03 -07:00
|
|
|
}
|
2022-07-29 06:07:08 -07:00
|
|
|
|
|
|
|
/// Initializes a CompactBlockProcessor instance from an Initialized object
|
2023-02-02 08:58:12 -08:00
|
|
|
/// - Parameters:
|
|
|
|
/// - initializer: an instance that complies to CompactBlockDownloading protocol
|
2023-05-01 07:28:59 -07:00
|
|
|
init(initializer: Initializer, walletBirthdayProvider: @escaping () -> BlockHeight) {
|
2021-09-15 05:21:29 -07:00
|
|
|
self.init(
|
2023-05-01 07:28:59 -07:00
|
|
|
container: initializer.container,
|
2021-09-15 05:21:29 -07:00
|
|
|
config: Configuration(
|
2023-03-22 05:47:32 -07:00
|
|
|
alias: initializer.alias,
|
2023-02-02 08:58:12 -08:00
|
|
|
fsBlockCacheRoot: initializer.fsBlockDbRoot,
|
2021-09-15 05:21:29 -07:00
|
|
|
dataDb: initializer.dataDbURL,
|
2022-11-28 22:38:28 -08:00
|
|
|
spendParamsURL: initializer.spendParamsURL,
|
|
|
|
outputParamsURL: initializer.outputParamsURL,
|
2023-02-16 08:27:49 -08:00
|
|
|
saplingParamsSourceURL: initializer.saplingParamsSourceURL,
|
2023-03-10 03:58:28 -08:00
|
|
|
walletBirthdayProvider: walletBirthdayProvider,
|
2021-09-15 05:21:29 -07:00
|
|
|
network: initializer.network
|
|
|
|
),
|
2023-05-01 07:28:59 -07:00
|
|
|
accountRepository: initializer.accountRepository
|
2021-09-17 06:49:58 -07:00
|
|
|
)
|
2020-04-23 10:11:03 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
|
2023-08-09 01:03:36 -07:00
|
|
|
init(
|
|
|
|
container: DIContainer,
|
|
|
|
config: Configuration,
|
|
|
|
accountRepository: AccountRepository
|
|
|
|
) {
|
2023-05-01 07:28:59 -07:00
|
|
|
Dependencies.setupCompactBlockProcessor(
|
|
|
|
in: container,
|
|
|
|
config: config,
|
|
|
|
accountRepository: accountRepository
|
2023-03-22 05:47:32 -07:00
|
|
|
)
|
2023-02-02 06:10:24 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
let configProvider = ConfigProvider(config: config)
|
2023-09-07 03:54:47 -07:00
|
|
|
context = ActionContextImpl(state: .idle)
|
2023-05-05 08:04:13 -07:00
|
|
|
actions = Self.makeActions(container: container, configProvider: configProvider)
|
|
|
|
|
2023-05-01 07:28:59 -07:00
|
|
|
self.metrics = container.resolve(SDKMetrics.self)
|
|
|
|
self.logger = container.resolve(Logger.self)
|
|
|
|
self.latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
|
|
|
|
self.blockDownloaderService = container.resolve(BlockDownloaderService.self)
|
|
|
|
self.service = container.resolve(LightWalletService.self)
|
|
|
|
self.rustBackend = container.resolve(ZcashRustBackendWelding.self)
|
|
|
|
self.storage = container.resolve(CompactBlockRepository.self)
|
2019-10-18 11:45:19 -07:00
|
|
|
self.config = config
|
2023-05-01 07:28:59 -07:00
|
|
|
self.transactionRepository = container.resolve(TransactionRepository.self)
|
2021-04-08 10:18:16 -07:00
|
|
|
self.accountRepository = accountRepository
|
2023-05-05 08:04:13 -07:00
|
|
|
self.fileManager = container.resolve(ZcashFileManager.self)
|
|
|
|
self.configProvider = configProvider
|
2019-10-18 11:45:19 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
|
2019-10-18 11:45:19 -07:00
|
|
|
deinit {
|
2023-05-05 08:04:13 -07:00
|
|
|
syncTask?.cancel()
|
|
|
|
syncTask = nil
|
2022-09-01 05:58:41 -07:00
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
|
|
|
private static func makeActions(container: DIContainer, configProvider: ConfigProvider) -> [CBPState: Action] {
|
|
|
|
let actionsDefinition = CBPState.allCases.compactMap { state -> (CBPState, Action)? in
|
|
|
|
let action: Action
|
|
|
|
switch state {
|
|
|
|
case .migrateLegacyCacheDB:
|
|
|
|
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
|
|
|
|
case .validateServer:
|
|
|
|
action = ValidateServerAction(container: container, configProvider: configProvider)
|
2023-07-30 23:51:04 -07:00
|
|
|
case .updateSubtreeRoots:
|
2023-08-09 23:30:58 -07:00
|
|
|
action = UpdateSubtreeRootsAction(container: container, configProvider: configProvider)
|
2023-08-01 04:04:50 -07:00
|
|
|
case .updateChainTip:
|
|
|
|
action = UpdateChainTipAction(container: container)
|
2023-08-09 01:03:36 -07:00
|
|
|
case .processSuggestedScanRanges:
|
|
|
|
action = ProcessSuggestedScanRangesAction(container: container)
|
2023-08-09 06:24:45 -07:00
|
|
|
case .rewind:
|
|
|
|
action = RewindAction(container: container)
|
2023-05-05 08:04:13 -07:00
|
|
|
case .download:
|
|
|
|
action = DownloadAction(container: container, configProvider: configProvider)
|
|
|
|
case .scan:
|
|
|
|
action = ScanAction(container: container, configProvider: configProvider)
|
|
|
|
case .clearAlreadyScannedBlocks:
|
|
|
|
action = ClearAlreadyScannedBlocksAction(container: container)
|
|
|
|
case .enhance:
|
|
|
|
action = EnhanceAction(container: container, configProvider: configProvider)
|
|
|
|
case .fetchUTXO:
|
|
|
|
action = FetchUTXOsAction(container: container)
|
|
|
|
case .handleSaplingParams:
|
|
|
|
action = SaplingParamsAction(container: container)
|
|
|
|
case .clearCache:
|
|
|
|
action = ClearCacheAction(container: container)
|
|
|
|
case .finished, .failed, .stopped, .idle:
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-29 11:28:24 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
return (state, action)
|
|
|
|
}
|
2023-03-16 02:11:18 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
return Dictionary(uniqueKeysWithValues: actionsDefinition)
|
2023-03-16 02:11:18 -07:00
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// This is currently used only in tests. And it should be used only in tests.
|
|
|
|
func update(config: Configuration) async {
|
|
|
|
self.config = config
|
|
|
|
await configProvider.update(config: config)
|
2023-03-16 02:11:18 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2023-03-16 02:11:18 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - "Public" API
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
extension CompactBlockProcessor {
|
2023-01-12 04:05:11 -08:00
|
|
|
func start(retry: Bool = false) async {
|
2020-03-13 17:00:01 -07:00
|
|
|
if retry {
|
|
|
|
self.retryAttempts = 0
|
2024-02-06 01:10:02 -08:00
|
|
|
self.serviceFailureRetryAttempts = 0
|
2021-05-03 14:50:00 -07:00
|
|
|
self.backoffTimer?.invalidate()
|
|
|
|
self.backoffTimer = nil
|
2020-03-13 17:00:01 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
guard await canStartSync() else {
|
|
|
|
if await isIdle() {
|
|
|
|
logger.warn("max retry attempts reached on \(await context.state) state")
|
|
|
|
await send(event: .failed(ZcashError.compactBlockProcessorMaxAttemptsReached(config.retries)))
|
|
|
|
} else {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Warning: compact block processor was started while busy!!!!")
|
2023-02-20 01:53:04 -08:00
|
|
|
afterSyncHooksManager.insert(hook: .anotherSync)
|
2020-03-13 17:00:01 -07:00
|
|
|
}
|
2020-02-26 08:54:48 -08:00
|
|
|
return
|
2019-11-04 15:18:07 -08:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
syncTask = Task(priority: .userInitiated) {
|
|
|
|
await run()
|
2023-02-02 08:58:12 -08:00
|
|
|
}
|
2021-05-17 14:14:59 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-03-29 11:28:24 -07:00
|
|
|
func stop() async {
|
2023-05-05 08:04:13 -07:00
|
|
|
syncTask?.cancel()
|
2020-04-23 10:11:03 -07:00
|
|
|
self.backoffTimer?.invalidate()
|
|
|
|
self.backoffTimer = nil
|
2023-05-05 08:04:13 -07:00
|
|
|
await stopAllActions()
|
|
|
|
retryAttempts = 0
|
2024-02-06 01:10:02 -08:00
|
|
|
serviceFailureRetryAttempts = 0
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
func latestHeight() async throws -> BlockHeight {
|
|
|
|
try await blockDownloaderService.latestBlockHeight()
|
2019-10-30 13:18:57 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - Rewind
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
extension CompactBlockProcessor {
|
2023-02-02 08:58:12 -08:00
|
|
|
/// Rewinds to provided height.
|
|
|
|
/// - Parameter height: height to rewind to. If nil is provided, it will rescan to nearest height (quick rescan)
|
|
|
|
///
|
2023-03-02 04:19:25 -08:00
|
|
|
/// - Note: If this is called while sync is in progress then the sync process is stopped first and then rewind is executed.
|
2023-05-21 09:48:29 -07:00
|
|
|
func rewind(context: AfterSyncHooksManager.RewindContext) async throws {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Starting rewind")
|
2023-05-05 08:04:13 -07:00
|
|
|
if await isIdle() {
|
|
|
|
logger.debug("Sync doesn't run. Executing rewind.")
|
|
|
|
try await doRewind(context: context)
|
|
|
|
} else {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Stopping sync because of rewind")
|
2023-03-02 04:19:25 -08:00
|
|
|
afterSyncHooksManager.insert(hook: .rewind(context))
|
2023-03-29 11:28:24 -07:00
|
|
|
await stop()
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-21 09:48:29 -07:00
|
|
|
private func doRewind(context: AfterSyncHooksManager.RewindContext) async throws {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Executing rewind.")
|
2023-09-07 03:54:47 -07:00
|
|
|
let lastDownloaded = await latestBlocksDataProvider.maxScannedHeight
|
2023-03-02 04:19:25 -08:00
|
|
|
let height = Int32(context.height ?? lastDownloaded)
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-03-31 10:10:35 -07:00
|
|
|
let nearestHeight: Int32
|
|
|
|
do {
|
|
|
|
nearestHeight = try await rustBackend.getNearestRewindHeight(height: height)
|
|
|
|
} catch {
|
2023-05-05 08:04:13 -07:00
|
|
|
await failure(error)
|
2023-03-27 07:12:06 -07:00
|
|
|
return await context.completion(.failure(error))
|
2021-04-19 10:07:50 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-01-18 08:09:04 -08:00
|
|
|
// FIXME: [#719] this should be done on the rust layer, https://github.com/zcash/ZcashLightClientKit/issues/719
|
2021-09-17 06:49:58 -07:00
|
|
|
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
|
2023-03-31 10:10:35 -07:00
|
|
|
|
|
|
|
do {
|
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- ClearCache action right after the idle action, clearing out metadata so the sync process can be fully restored from the DB and live blockchain values only.
- InternalSyncProgress removed
- InternalSyncProgressStorage removed
- Sync process control logic updated, controlled by latestScannedHeight and firstUnenhancedHeight only
- cleaned up unused code
[#1140] ClearCache action before anything starts
- ChecksBeforeSyncAction removed
- Offline tests fixed
[#1140] ClearCache action before anything starts
- fixed injection of a wallet birthday, the sync range must start with wallet BD instead of lower bound
[#1140] ClearCache action before anything starts
- Network tests fixed
- DarkSideTests partially fixed
[#1140] ClearCache action before anything starts
- rewind actions extension in compact block processor added
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- DarkSideTests fixed
[#1140] ClearCache action before anything starts
- SyncRanges modified to be even less dependent on ranges, now it holds just 3 values (latest block height, latest scanned height if any, first unenhanced height if any), the rest is computed on the fly
[#1140] ClearCache action before anything starts
- SyncRanges struct not anymore, refactored to SyncControlData, holding just 3 mentioned values
[#1140] ClearCache action before anything starts
- cleanup
[#1140] ClearCache action before anything starts (#1148)
- TODO solved, the UTXOs fetcher doesn't work with range anymore, therefore reporting 100%
2023-06-20 02:59:56 -07:00
|
|
|
try await rewindDownloadBlockAction(to: BlockHeight(rewindHeight))
|
2023-03-31 10:10:35 -07:00
|
|
|
try await rustBackend.rewindToHeight(height: rewindHeight)
|
|
|
|
} catch {
|
2023-05-05 08:04:13 -07:00
|
|
|
await failure(error)
|
2023-03-27 07:12:06 -07:00
|
|
|
return await context.completion(.failure(error))
|
2021-03-26 15:56:51 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2021-03-26 15:56:51 -07:00
|
|
|
// clear cache
|
2022-11-23 10:05:49 -08:00
|
|
|
let rewindBlockHeight = BlockHeight(rewindHeight)
|
2023-03-02 04:19:25 -08:00
|
|
|
do {
|
2023-03-27 05:26:47 -07:00
|
|
|
try await blockDownloaderService.rewind(to: rewindBlockHeight)
|
2023-03-02 04:19:25 -08:00
|
|
|
} catch {
|
2023-03-27 07:12:06 -07:00
|
|
|
return await context.completion(.failure(error))
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
2023-10-12 06:59:47 -07:00
|
|
|
|
|
|
|
await resetContext(restoreLastEnhancedHeight: false)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
await context.completion(.success(rewindBlockHeight))
|
2021-03-26 15:56:51 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- ClearCache action right after the idle action, clearing out metadata so the sync process can be fully restored from the DB and live blockchain values only.
- InternalSyncProgress removed
- InternalSyncProgressStorage removed
- Sync process control logic updated, controlled by latestScannedHeight and firstUnenhancedHeight only
- cleaned up unused code
[#1140] ClearCache action before anything starts
- ChecksBeforeSyncAction removed
- Offline tests fixed
[#1140] ClearCache action before anything starts
- fixed injection of a wallet birthday, the sync range must start with wallet BD instead of lower bound
[#1140] ClearCache action before anything starts
- Network tests fixed
- DarkSideTests partially fixed
[#1140] ClearCache action before anything starts
- rewind actions extension in compact block processor added
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- DarkSideTests fixed
[#1140] ClearCache action before anything starts
- SyncRanges modified to be even less dependent on ranges, now it holds just 3 values (latest block height, latest scanned height if any, first unenhanced height if any), the rest is computed on the fly
[#1140] ClearCache action before anything starts
- SyncRanges struct not anymore, refactored to SyncControlData, holding just 3 mentioned values
[#1140] ClearCache action before anything starts
- cleanup
[#1140] ClearCache action before anything starts (#1148)
- TODO solved, the UTXOs fetcher doesn't work with range anymore, therefore reporting 100%
2023-06-20 02:59:56 -07:00
|
|
|
// MARK: - Actions
|
|
|
|
|
|
|
|
private extension CompactBlockProcessor {
|
|
|
|
func rewindDownloadBlockAction(to rewindHeight: BlockHeight?) async throws {
|
|
|
|
if let downloadAction = actions[.download] as? DownloadAction {
|
|
|
|
await downloadAction.downloader.rewind(latestDownloadedBlockHeight: rewindHeight)
|
|
|
|
} else {
|
|
|
|
throw ZcashError.compactBlockProcessorDownloadBlockActionRewind
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - Wipe
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
extension CompactBlockProcessor {
|
2023-05-21 09:48:29 -07:00
|
|
|
func wipe(context: AfterSyncHooksManager.WipeContext) async throws {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Starting wipe")
|
2023-05-05 08:04:13 -07:00
|
|
|
if await isIdle() {
|
|
|
|
logger.debug("Sync doesn't run. Executing wipe.")
|
|
|
|
try await doWipe(context: context)
|
|
|
|
} else {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Stopping sync because of wipe")
|
2023-02-20 01:53:04 -08:00
|
|
|
afterSyncHooksManager.insert(hook: .wipe(context))
|
2023-03-29 11:28:24 -07:00
|
|
|
await stop()
|
2022-12-19 06:15:23 -08:00
|
|
|
}
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
|
2023-05-21 09:48:29 -07:00
|
|
|
private func doWipe(context: AfterSyncHooksManager.WipeContext) async throws {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("Executing wipe.")
|
2023-02-20 01:53:04 -08:00
|
|
|
context.prewipe()
|
2022-12-19 06:15:23 -08:00
|
|
|
|
2023-02-20 01:53:04 -08:00
|
|
|
do {
|
|
|
|
try await self.storage.clear()
|
|
|
|
|
|
|
|
wipeLegacyCacheDbIfNeeded()
|
2023-02-02 08:58:12 -08:00
|
|
|
|
2023-02-20 01:53:04 -08:00
|
|
|
let fileManager = FileManager.default
|
|
|
|
if fileManager.fileExists(atPath: config.dataDb.path) {
|
2023-03-15 04:17:43 -07:00
|
|
|
try fileManager.removeItem(at: config.dataDb)
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
|
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- ClearCache action right after the idle action, clearing out metadata so the sync process can be fully restored from the DB and live blockchain values only.
- InternalSyncProgress removed
- InternalSyncProgressStorage removed
- Sync process control logic updated, controlled by latestScannedHeight and firstUnenhancedHeight only
- cleaned up unused code
[#1140] ClearCache action before anything starts
- ChecksBeforeSyncAction removed
- Offline tests fixed
[#1140] ClearCache action before anything starts
- fixed injection of a wallet birthday, the sync range must start with wallet BD instead of lower bound
[#1140] ClearCache action before anything starts
- Network tests fixed
- DarkSideTests partially fixed
[#1140] ClearCache action before anything starts
- rewind actions extension in compact block processor added
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- DarkSideTests fixed
[#1140] ClearCache action before anything starts
- SyncRanges modified to be even less dependent on ranges, now it holds just 3 values (latest block height, latest scanned height if any, first unenhanced height if any), the rest is computed on the fly
[#1140] ClearCache action before anything starts
- SyncRanges struct not anymore, refactored to SyncControlData, holding just 3 mentioned values
[#1140] ClearCache action before anything starts
- cleanup
[#1140] ClearCache action before anything starts (#1148)
- TODO solved, the UTXOs fetcher doesn't work with range anymore, therefore reporting 100%
2023-06-20 02:59:56 -07:00
|
|
|
try await rewindDownloadBlockAction(to: nil)
|
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
await context.completion(nil)
|
2023-02-20 01:53:04 -08:00
|
|
|
} catch {
|
2023-03-16 02:11:18 -07:00
|
|
|
await context.completion(error)
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
2022-12-19 06:15:23 -08:00
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func wipeLegacyCacheDbIfNeeded() {
|
|
|
|
guard let cacheDbURL = config.cacheDbURL else { return }
|
|
|
|
guard fileManager.isDeletableFile(atPath: cacheDbURL.pathExtension) else { return }
|
|
|
|
try? fileManager.removeItem(at: cacheDbURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 02:48:59 -08:00
|
|
|
// MARK: - Switch server
|
|
|
|
|
|
|
|
extension CompactBlockProcessor {
|
|
|
|
func updateService(_ container: DIContainer) {
|
|
|
|
// LightWalletGRPCService
|
|
|
|
let updatedLWDService = container.resolve(LightWalletService.self)
|
|
|
|
|
|
|
|
(actions[.processSuggestedScanRanges] as? ProcessSuggestedScanRangesAction)?.service = updatedLWDService
|
|
|
|
(actions[.updateChainTip] as? UpdateChainTipAction)?.service = updatedLWDService
|
|
|
|
(actions[.updateSubtreeRoots] as? UpdateSubtreeRootsAction)?.service = updatedLWDService
|
|
|
|
(actions[.validateServer] as? ValidateServerAction)?.service = updatedLWDService
|
|
|
|
self.service = updatedLWDService
|
|
|
|
|
|
|
|
// BlockDownloaderService
|
|
|
|
let updatedDownloaderService = container.resolve(BlockDownloaderService.self)
|
|
|
|
|
|
|
|
(actions[.rewind] as? RewindAction)?.downloaderService = updatedDownloaderService
|
|
|
|
self.blockDownloaderService = updatedDownloaderService
|
|
|
|
|
|
|
|
// LatestBlocksDataProvider
|
|
|
|
let updatedLBDProvider = container.resolve(LatestBlocksDataProvider.self)
|
|
|
|
|
|
|
|
(actions[.scan] as? ScanAction)?.latestBlocksDataProvider = updatedLBDProvider
|
|
|
|
(actions[.updateChainTip] as? UpdateChainTipAction)?.latestBlocksDataProvider = updatedLBDProvider
|
|
|
|
self.latestBlocksDataProvider = updatedLBDProvider
|
|
|
|
|
|
|
|
// BlockDownloader
|
|
|
|
let updatedBlockDownloader = container.resolve(BlockDownloader.self)
|
|
|
|
|
|
|
|
(actions[.download] as? DownloadAction)?.downloader = updatedBlockDownloader
|
|
|
|
(actions[.updateChainTip] as? UpdateChainTipAction)?.downloader = updatedBlockDownloader
|
|
|
|
(actions[.rewind] as? RewindAction)?.downloader = updatedBlockDownloader
|
|
|
|
self.blockDownloaderService = updatedDownloaderService
|
|
|
|
|
|
|
|
// BlockEnhancer
|
|
|
|
let updatedEnhancer = container.resolve(BlockEnhancer.self)
|
|
|
|
|
|
|
|
(actions[.enhance] as? EnhanceAction)?.blockEnhancer = updatedEnhancer
|
|
|
|
|
|
|
|
// UTXOFetcher
|
|
|
|
let updatedUTXOFetcher = container.resolve(UTXOFetcher.self)
|
|
|
|
|
|
|
|
(actions[.fetchUTXO] as? FetchUTXOsAction)?.utxoFetcher = updatedUTXOFetcher
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - Events
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
extension CompactBlockProcessor {
|
|
|
|
typealias EventClosure = (Event) async -> Void
|
|
|
|
|
|
|
|
enum Event {
|
|
|
|
/// Event sent when the CompactBlockProcessor presented an error.
|
|
|
|
case failed(Error)
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor has finished syncing the blockchain to latest height
|
|
|
|
case finished(_ lastScannedHeight: BlockHeight)
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor found a newly mined transaction
|
|
|
|
case minedTransaction(ZcashTransaction.Overview)
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor enhanced a bunch of transactions in some range.
|
|
|
|
case foundTransactions([ZcashTransaction.Overview], CompactBlockRange)
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor handled a ReOrg.
|
|
|
|
/// `reorgHeight` is the height on which the reorg was detected.
|
|
|
|
/// `rewindHeight` is the height that the processor backed to in order to solve the Reorg.
|
|
|
|
case handledReorg(_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight)
|
|
|
|
|
|
|
|
/// Event sent when progress of some specific action happened.
|
2023-09-11 00:29:21 -07:00
|
|
|
case syncProgress(Float)
|
2023-05-05 08:04:13 -07:00
|
|
|
|
|
|
|
/// Event sent when progress of the sync process changes.
|
|
|
|
case progressUpdated(Float)
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor fetched utxos from lightwalletd attempted to store them.
|
|
|
|
case storedUTXOs((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor starts enhancing of the transactions.
|
|
|
|
case startedEnhancing
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor starts fetching of the UTXOs.
|
|
|
|
case startedFetching
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor starts syncing.
|
|
|
|
case startedSyncing
|
|
|
|
|
|
|
|
/// Event sent when the CompactBlockProcessor stops syncing.
|
|
|
|
case stopped
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateEventClosure(identifier: String, closure: @escaping (Event) async -> Void) async {
|
|
|
|
eventClosures[identifier] = closure
|
|
|
|
}
|
|
|
|
|
|
|
|
private func send(event: Event) async {
|
|
|
|
for item in eventClosures {
|
|
|
|
await item.value(event)
|
2022-10-03 16:05:11 -07:00
|
|
|
}
|
2019-10-30 13:18:57 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Main loop
|
|
|
|
|
|
|
|
extension CompactBlockProcessor {
|
|
|
|
// This is main loop of the sync process. It simply takes state and try to find action which handles it. If action is found it executes the
|
|
|
|
// action. If action is not found then loop finishes. Thanks to this it's super easy to identify start point of sync process and end points
|
|
|
|
// of sync process without any side effects.
|
|
|
|
//
|
|
|
|
// Check `docs/cbp_state_machine.puml` file and `docs/images/cbp_state_machine.png` file to see all the state tree. Also when you update state
|
|
|
|
// tree in the code update this documentation. Image is generated by plantuml tool.
|
|
|
|
//
|
2023-05-21 09:48:29 -07:00
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
2023-05-05 08:04:13 -07:00
|
|
|
private func run() async {
|
|
|
|
logger.debug("Starting run")
|
2023-12-08 02:45:19 -08:00
|
|
|
metrics.cbpStart()
|
2023-05-05 08:04:13 -07:00
|
|
|
await resetContext()
|
2022-11-23 10:05:49 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
while true {
|
|
|
|
// Sync is starting when the state is `idle`.
|
|
|
|
if await context.state == .idle {
|
|
|
|
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
|
|
|
await stopAllActions()
|
|
|
|
// Update state to the first state in state machine that can be handled by action.
|
2023-09-13 06:25:25 -07:00
|
|
|
await context.update(state: .migrateLegacyCacheDB)
|
2023-05-05 08:04:13 -07:00
|
|
|
await syncStarted()
|
2022-11-04 02:03:52 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
if backoffTimer == nil {
|
|
|
|
await setTimer()
|
2022-11-23 10:05:49 -08:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2022-11-23 10:05:49 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
let state = await context.state
|
|
|
|
logger.debug("Handling state: \(state)")
|
|
|
|
|
|
|
|
// Try to find action for state.
|
|
|
|
guard let action = actions[state] else {
|
|
|
|
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
|
|
|
await stopAllActions()
|
|
|
|
if await syncFinished() {
|
|
|
|
await resetContext()
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
break
|
2022-11-23 10:05:49 -08:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2022-11-23 10:05:49 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
do {
|
|
|
|
try Task.checkCancellation()
|
|
|
|
|
|
|
|
// Execute action.
|
2023-12-08 02:45:19 -08:00
|
|
|
metrics.actionStart(state)
|
2023-05-05 08:04:13 -07:00
|
|
|
context = try await action.run(with: context) { [weak self] event in
|
|
|
|
await self?.send(event: event)
|
2023-08-10 07:08:01 -07:00
|
|
|
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
|
2023-05-05 08:04:13 -07:00
|
|
|
if let progress = await self?.compactBlockProgress.progress {
|
|
|
|
await self?.send(event: .progressUpdated(progress))
|
|
|
|
}
|
2023-04-28 10:13:21 -07:00
|
|
|
}
|
2022-11-23 10:05:49 -08:00
|
|
|
}
|
2022-11-04 02:03:52 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
await didFinishAction()
|
2022-09-01 05:58:41 -07:00
|
|
|
} catch {
|
2023-05-16 02:03:22 -07:00
|
|
|
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
2023-05-05 08:04:13 -07:00
|
|
|
await stopAllActions()
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.error("Sync failed with error: \(error)")
|
2023-02-01 03:45:09 -08:00
|
|
|
|
2024-02-06 01:10:02 -08:00
|
|
|
// catching the service errors
|
|
|
|
let serviceError: Bool
|
|
|
|
switch error {
|
|
|
|
case ZcashError.serviceGetInfoFailed, ZcashError.serviceLatestBlockFailed,
|
|
|
|
ZcashError.serviceLatestBlockHeightFailed, ZcashError.serviceBlockRangeFailed,
|
|
|
|
ZcashError.serviceSubmitFailed, ZcashError.serviceFetchTransactionFailed,
|
|
|
|
ZcashError.serviceFetchUTXOsFailed, ZcashError.serviceBlockStreamFailed,
|
|
|
|
ZcashError.serviceSubtreeRootsStreamFailed: serviceError = true
|
|
|
|
default: serviceError = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if serviceError && self.serviceFailureRetryAttempts < ZcashSDK.serviceFailureRetries {
|
2024-01-24 05:58:48 -08:00
|
|
|
// This may be false positive communication error that is usually resolved by retry.
|
2024-02-06 01:10:02 -08:00
|
|
|
// We will try to reset the sync and continue but this will we done at most `ZcashSDK.serviceFailureRetries` times.
|
|
|
|
logger.error("ServiceError: \(error), retry is available, starting the sync all over again.")
|
2024-01-24 05:58:48 -08:00
|
|
|
|
2024-02-06 01:10:02 -08:00
|
|
|
self.serviceFailureRetryAttempts += 1
|
2024-01-24 05:58:48 -08:00
|
|
|
|
|
|
|
// Start sync all over again
|
|
|
|
await resetContext()
|
|
|
|
} else if Task.isCancelled {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.info("Processing cancelled.")
|
2023-05-21 09:48:29 -07:00
|
|
|
do {
|
2023-05-05 08:04:13 -07:00
|
|
|
if try await syncTaskWasCancelled() {
|
|
|
|
// Start sync all over again
|
|
|
|
await resetContext()
|
|
|
|
} else {
|
|
|
|
// end the sync loop
|
|
|
|
break
|
|
|
|
}
|
2023-05-21 09:48:29 -07:00
|
|
|
} catch {
|
2023-05-05 08:04:13 -07:00
|
|
|
await failure(error)
|
|
|
|
break
|
2023-05-21 09:48:29 -07:00
|
|
|
}
|
2023-02-01 03:45:09 -08:00
|
|
|
} else {
|
2024-01-24 05:58:48 -08:00
|
|
|
await handleSyncFailure(action: action, error: error)
|
|
|
|
break
|
2022-09-01 05:58:41 -07:00
|
|
|
}
|
2021-07-14 16:43:02 -07:00
|
|
|
}
|
2021-06-03 07:51:12 -07:00
|
|
|
}
|
2022-12-01 08:57:13 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
logger.debug("Run ended")
|
|
|
|
syncTask = nil
|
|
|
|
}
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func syncTaskWasCancelled() async throws -> Bool {
|
|
|
|
logger.info("Sync cancelled.")
|
|
|
|
await context.update(state: .stopped)
|
|
|
|
await send(event: .stopped)
|
|
|
|
return try await handleAfterSyncHooks()
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
|
|
|
|
2024-01-24 05:58:48 -08:00
|
|
|
private func handleSyncFailure(action: Action, error: Error) async {
|
2023-05-05 08:04:13 -07:00
|
|
|
if action.removeBlocksCacheWhenFailed {
|
|
|
|
await ifTaskIsNotCanceledClearCompactBlockCache()
|
2022-12-22 05:05:29 -08:00
|
|
|
}
|
|
|
|
|
2023-09-13 00:24:26 -07:00
|
|
|
logger.error("Sync failed with error: \(error)")
|
|
|
|
await failure(error)
|
2019-10-31 10:33:21 -07:00
|
|
|
}
|
2022-12-22 05:05:29 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
|
|
|
private func didFinishAction() async {
|
|
|
|
// This is evalution of the state setup by previous action.
|
|
|
|
switch await context.state {
|
|
|
|
case .idle:
|
|
|
|
break
|
|
|
|
case .migrateLegacyCacheDB:
|
|
|
|
break
|
|
|
|
case .validateServer:
|
|
|
|
break
|
2023-07-30 23:51:04 -07:00
|
|
|
case .updateSubtreeRoots:
|
|
|
|
break
|
2023-08-01 04:04:50 -07:00
|
|
|
case .updateChainTip:
|
|
|
|
break
|
2023-08-09 01:03:36 -07:00
|
|
|
case .processSuggestedScanRanges:
|
2023-08-03 02:07:57 -07:00
|
|
|
break
|
2023-08-09 06:24:45 -07:00
|
|
|
case .rewind:
|
|
|
|
break
|
2023-05-05 08:04:13 -07:00
|
|
|
case .download:
|
|
|
|
break
|
|
|
|
case .scan:
|
|
|
|
break
|
|
|
|
case .clearAlreadyScannedBlocks:
|
|
|
|
break
|
|
|
|
case .enhance:
|
|
|
|
await send(event: .startedEnhancing)
|
|
|
|
case .fetchUTXO:
|
|
|
|
await send(event: .startedFetching)
|
|
|
|
case .handleSaplingParams:
|
|
|
|
break
|
|
|
|
case .clearCache:
|
|
|
|
break
|
|
|
|
case .finished:
|
|
|
|
break
|
|
|
|
case .failed:
|
|
|
|
break
|
|
|
|
case .stopped:
|
|
|
|
break
|
2022-12-01 08:57:13 -08:00
|
|
|
}
|
2019-10-31 10:33:21 -07:00
|
|
|
}
|
2022-12-01 08:57:13 -08:00
|
|
|
|
2023-10-12 06:59:47 -07:00
|
|
|
func resetContext(restoreLastEnhancedHeight: Bool = true) async {
|
|
|
|
let lastEnhancedHeight = await context.lastEnhancedHeight
|
2023-09-07 03:54:47 -07:00
|
|
|
context = ActionContextImpl(state: .idle)
|
2023-10-12 06:59:47 -07:00
|
|
|
if restoreLastEnhancedHeight {
|
|
|
|
await context.update(lastEnhancedHeight: lastEnhancedHeight)
|
|
|
|
}
|
2023-05-10 13:13:29 -07:00
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func syncStarted() async {
|
|
|
|
logger.debug("Sync started")
|
|
|
|
// handle start of the sync process
|
|
|
|
await send(event: .startedSyncing)
|
2020-04-23 10:11:03 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func syncFinished() async -> Bool {
|
|
|
|
logger.debug("Sync finished")
|
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- ClearCache action right after the idle action, clearing out metadata so the sync process can be fully restored from the DB and live blockchain values only.
- InternalSyncProgress removed
- InternalSyncProgressStorage removed
- Sync process control logic updated, controlled by latestScannedHeight and firstUnenhancedHeight only
- cleaned up unused code
[#1140] ClearCache action before anything starts
- ChecksBeforeSyncAction removed
- Offline tests fixed
[#1140] ClearCache action before anything starts
- fixed injection of a wallet birthday, the sync range must start with wallet BD instead of lower bound
[#1140] ClearCache action before anything starts
- Network tests fixed
- DarkSideTests partially fixed
[#1140] ClearCache action before anything starts
- rewind actions extension in compact block processor added
[#1140] ClearCache action before anything starts
- draft
[#1140] ClearCache action before anything starts
- DarkSideTests fixed
[#1140] ClearCache action before anything starts
- SyncRanges modified to be even less dependent on ranges, now it holds just 3 values (latest block height, latest scanned height if any, first unenhanced height if any), the rest is computed on the fly
[#1140] ClearCache action before anything starts
- SyncRanges struct not anymore, refactored to SyncControlData, holding just 3 mentioned values
[#1140] ClearCache action before anything starts
- cleanup
[#1140] ClearCache action before anything starts (#1148)
- TODO solved, the UTXOs fetcher doesn't work with range anymore, therefore reporting 100%
2023-06-20 02:59:56 -07:00
|
|
|
let latestBlockHeightWhenSyncing = await context.syncControlData.latestBlockHeight
|
2023-05-05 08:04:13 -07:00
|
|
|
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
|
|
|
|
// If `latestBlockHeightWhenSyncing` is 0 then it means that there was nothing to sync in last sync process.
|
|
|
|
let newerBlocksWereMinedDuringSync =
|
|
|
|
latestBlockHeightWhenSyncing > 0 && latestBlockHeightWhenSyncing < latestBlockHeight
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
retryAttempts = 0
|
2024-02-06 01:10:02 -08:00
|
|
|
serviceFailureRetryAttempts = 0
|
2023-05-05 08:04:13 -07:00
|
|
|
consecutiveChainValidationErrors = 0
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-09-07 03:54:47 -07:00
|
|
|
let lastScannedHeight = await latestBlocksDataProvider.maxScannedHeight
|
2023-05-05 08:04:13 -07:00
|
|
|
// Some actions may not run. For example there are no transactions to enhance and therefore there is no enhance progress. And in
|
|
|
|
// cases like this computation of final progress won't work properly. So let's fake 100% progress at the end of the sync process.
|
|
|
|
await send(event: .progressUpdated(1))
|
|
|
|
await send(event: .finished(lastScannedHeight))
|
|
|
|
await context.update(state: .finished)
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2024-01-26 19:07:18 -08:00
|
|
|
let walletSummary = try? await rustBackend.getWalletSummary()
|
|
|
|
await metrics.logCBPOverviewReport(logger, walletSummary: walletSummary)
|
2023-12-14 23:12:53 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// If new blocks were mined during previous sync run the sync process again
|
|
|
|
if newerBlocksWereMinedDuringSync {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
await setTimer()
|
|
|
|
return false
|
2022-08-23 12:58:15 -07:00
|
|
|
}
|
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func failure(_ error: Error) async {
|
|
|
|
await context.update(state: .failed)
|
2022-11-23 10:05:49 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
logger.error("Fail with error: \(error)")
|
|
|
|
|
|
|
|
self.retryAttempts += 1
|
|
|
|
await send(event: .failed(error))
|
|
|
|
|
|
|
|
// don't set a new timer if there are no more attempts.
|
|
|
|
if hasRetryAttempt() {
|
|
|
|
await self.setTimer()
|
2019-10-18 11:45:19 -07:00
|
|
|
}
|
2019-10-30 13:18:57 -07:00
|
|
|
}
|
2022-11-04 02:03:52 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func handleAfterSyncHooks() async throws -> Bool {
|
|
|
|
let afterSyncHooksManager = self.afterSyncHooksManager
|
|
|
|
self.afterSyncHooksManager = AfterSyncHooksManager()
|
|
|
|
|
|
|
|
if let wipeContext = afterSyncHooksManager.shouldExecuteWipeHook() {
|
|
|
|
try await doWipe(context: wipeContext)
|
|
|
|
return false
|
|
|
|
} else if let rewindContext = afterSyncHooksManager.shouldExecuteRewindHook() {
|
|
|
|
try await doRewind(context: rewindContext)
|
|
|
|
return false
|
|
|
|
} else if afterSyncHooksManager.shouldExecuteAnotherSyncHook() {
|
|
|
|
logger.debug("Starting new sync.")
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
2023-04-18 01:13:52 -07:00
|
|
|
}
|
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
}
|
2023-04-18 01:13:52 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - Utils
|
2023-03-29 11:28:24 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
extension CompactBlockProcessor {
|
2022-10-27 03:51:38 -07:00
|
|
|
private func setTimer() async {
|
2023-05-05 08:04:13 -07:00
|
|
|
let interval = config.blockPollInterval
|
2020-03-11 19:17:32 -07:00
|
|
|
self.backoffTimer?.invalidate()
|
2021-09-17 06:49:58 -07:00
|
|
|
let timer = Timer(
|
|
|
|
timeInterval: interval,
|
|
|
|
repeats: true,
|
|
|
|
block: { [weak self] _ in
|
2023-04-07 05:02:05 -07:00
|
|
|
Task { [weak self] in
|
|
|
|
guard let self else { return }
|
2023-05-05 08:04:13 -07:00
|
|
|
if await self.isIdle() {
|
|
|
|
if await self.canStartSync() {
|
2023-04-14 00:08:37 -07:00
|
|
|
self.logger.debug(
|
|
|
|
"""
|
|
|
|
Timer triggered: Starting compact Block processor!.
|
2023-05-05 08:04:13 -07:00
|
|
|
Processor State: \(await self.context.state)
|
2023-04-14 00:08:37 -07:00
|
|
|
attempts: \(await self.retryAttempts)
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
await self.start()
|
2023-08-23 08:29:42 -07:00
|
|
|
} else if await self.hasRetryAttempt() {
|
2023-05-05 08:04:13 -07:00
|
|
|
await self.failure(ZcashError.compactBlockProcessorMaxAttemptsReached(self.config.retries))
|
2023-04-14 00:08:37 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
} else {
|
|
|
|
await self.latestBlocksDataProvider.updateBlockData()
|
2020-04-23 10:11:03 -07:00
|
|
|
}
|
2020-02-26 08:54:48 -08:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
}
|
|
|
|
)
|
2020-02-26 08:54:48 -08:00
|
|
|
RunLoop.main.add(timer, forMode: .default)
|
|
|
|
self.backoffTimer = timer
|
2019-10-18 11:45:19 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func isIdle() async -> Bool {
|
|
|
|
return syncTask == nil
|
2019-10-30 13:18:57 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func canStartSync() async -> Bool {
|
|
|
|
return await isIdle() && hasRetryAttempt()
|
2020-03-13 17:00:01 -07:00
|
|
|
}
|
2019-10-18 11:45:19 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func hasRetryAttempt() -> Bool {
|
|
|
|
retryAttempts < config.retries
|
|
|
|
}
|
|
|
|
|
|
|
|
func determineLowerBound(errorHeight: Int, consecutiveErrors: Int, walletBirthday: BlockHeight) -> BlockHeight {
|
|
|
|
let offset = min(ZcashSDK.maxReorgSize, ZcashSDK.defaultRewindDistance * (consecutiveErrors + 1))
|
|
|
|
return max(errorHeight - offset, walletBirthday - ZcashSDK.maxReorgSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func stopAllActions() async {
|
|
|
|
for action in actions.values {
|
|
|
|
await action.stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func ifTaskIsNotCanceledClearCompactBlockCache() async {
|
|
|
|
guard !Task.isCancelled else { return }
|
|
|
|
do {
|
|
|
|
try await clearCompactBlockCache()
|
|
|
|
} catch {
|
|
|
|
logger.error("`clearCompactBlockCache` failed after error: \(error)")
|
2019-10-30 13:18:57 -07:00
|
|
|
}
|
2019-10-18 11:45:19 -07:00
|
|
|
}
|
2023-05-05 08:04:13 -07:00
|
|
|
|
|
|
|
private func clearCompactBlockCache() async throws {
|
|
|
|
await stopAllActions()
|
|
|
|
try await storage.clear()
|
|
|
|
logger.info("Cache removed")
|
|
|
|
}
|
2019-10-18 11:45:19 -07:00
|
|
|
}
|
2021-03-08 10:47:36 -08:00
|
|
|
|
2021-04-08 10:18:16 -07:00
|
|
|
extension CompactBlockProcessor {
|
2023-03-30 03:49:28 -07:00
|
|
|
func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress {
|
2023-03-31 10:10:35 -07:00
|
|
|
try await rustBackend.getCurrentAddress(account: Int32(accountIndex))
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress {
|
|
|
|
try await getUnifiedAddress(accountIndex: accountIndex).saplingReceiver()
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
func getTransparentAddress(accountIndex: Int) async throws -> TransparentAddress {
|
|
|
|
try await getUnifiedAddress(accountIndex: accountIndex).transparentReceiver()
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension CompactBlockProcessor {
|
2022-10-02 19:11:17 -07:00
|
|
|
func refreshUTXOs(tAddress: TransparentAddress, startHeight: BlockHeight) async throws -> RefreshedUTXOs {
|
2021-04-08 10:18:16 -07:00
|
|
|
let dataDb = self.config.dataDb
|
2022-09-01 05:58:41 -07:00
|
|
|
|
2023-01-31 02:11:00 -08:00
|
|
|
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = blockDownloaderService.fetchUnspentTransactionOutputs(
|
2023-01-18 08:09:04 -08:00
|
|
|
tAddress: tAddress.stringEncoded,
|
|
|
|
startHeight: startHeight
|
|
|
|
)
|
2022-09-01 05:58:41 -07:00
|
|
|
var utxos: [UnspentTransactionOutputEntity] = []
|
|
|
|
|
|
|
|
do {
|
|
|
|
for try await utxo in stream {
|
|
|
|
utxos.append(utxo)
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
2023-03-30 03:49:28 -07:00
|
|
|
return await storeUTXOs(utxos, in: dataDb)
|
2022-09-01 05:58:41 -07:00
|
|
|
} catch {
|
2023-04-24 14:15:20 -07:00
|
|
|
throw error
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
private func storeUTXOs(_ utxos: [UnspentTransactionOutputEntity], in dataDb: URL) async -> RefreshedUTXOs {
|
2021-09-17 06:49:58 -07:00
|
|
|
var refreshed: [UnspentTransactionOutputEntity] = []
|
|
|
|
var skipped: [UnspentTransactionOutputEntity] = []
|
2021-04-08 10:18:16 -07:00
|
|
|
for utxo in utxos {
|
|
|
|
do {
|
2023-03-31 10:10:35 -07:00
|
|
|
try await rustBackend.putUnspentTransparentOutput(
|
2021-04-08 10:18:16 -07:00
|
|
|
txid: utxo.txid.bytes,
|
|
|
|
index: utxo.index,
|
|
|
|
script: utxo.script.bytes,
|
|
|
|
value: Int64(utxo.valueZat),
|
2023-03-31 10:10:35 -07:00
|
|
|
height: utxo.height
|
|
|
|
)
|
|
|
|
|
|
|
|
refreshed.append(utxo)
|
2021-04-08 10:18:16 -07:00
|
|
|
} catch {
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.info("failed to put utxo - error: \(error)")
|
2021-04-08 10:18:16 -07:00
|
|
|
skipped.append(utxo)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (inserted: refreshed, skipped: skipped)
|
|
|
|
}
|
|
|
|
}
|
2021-05-17 14:14:59 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
// MARK: - Config provider
|
2023-02-06 10:30:49 -08:00
|
|
|
|
2021-06-19 16:40:45 -07:00
|
|
|
extension CompactBlockProcessor {
|
2023-05-05 08:04:13 -07:00
|
|
|
actor ConfigProvider {
|
|
|
|
var config: Configuration
|
|
|
|
init(config: Configuration) {
|
|
|
|
self.config = config
|
2021-06-19 16:40:45 -07:00
|
|
|
}
|
2023-02-02 08:58:12 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
func update(config: Configuration) async {
|
|
|
|
self.config = config
|
2023-03-22 05:47:32 -07:00
|
|
|
}
|
2023-03-09 10:40:06 -08:00
|
|
|
}
|
|
|
|
}
|