diff --git a/Sources/ZcashLightClientKit/Block/Actions/Action.swift b/Sources/ZcashLightClientKit/Block/Actions/Action.swift index 397930d6..ab850e37 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/Action.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/Action.swift @@ -13,6 +13,7 @@ actor ActionContext { var syncControlData: SyncControlData let preferredSyncAlgorithm: SyncAlgorithm var supportedSyncAlgorithm: SyncAlgorithm? + var requestedRewindHeight: BlockHeight? var totalProgressRange: CompactBlockRange = 0...0 var lastScannedHeight: BlockHeight? var lastDownloadedHeight: BlockHeight? @@ -34,6 +35,7 @@ actor ActionContext { func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight } func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight } func update(supportedSyncAlgorithm: SyncAlgorithm) async { self.supportedSyncAlgorithm = supportedSyncAlgorithm } + func update(requestedRewindHeight: BlockHeight) async { self.requestedRewindHeight = requestedRewindHeight } } enum CBPState: CaseIterable { @@ -43,6 +45,7 @@ enum CBPState: CaseIterable { case updateSubtreeRoots case updateChainTip case processSuggestedScanRanges + case rewind case computeSyncControlData case download case scan diff --git a/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift index 6ec14221..6cbfce0d 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift @@ -27,13 +27,6 @@ extension ProcessSuggestedScanRangesAction: Action { 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 @@ -55,7 +48,14 @@ extension ProcessSuggestedScanRangesAction: Action { await context.update(syncControlData: syncControlData) await context.update(totalProgressRange: lowerBound...upperBound) - await context.update(state: .download) + // 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 { + await context.update(requestedRewindHeight: lowerBound + 1) + await context.update(state: .rewind) + } else { + await context.update(state: .download) + } } else { await context.update(state: .finished) } diff --git a/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift new file mode 100644 index 00000000..7d8d9272 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift @@ -0,0 +1,48 @@ +// +// RewindAction.swift +// +// +// Created by Lukáš Korba on 09.08.2023. +// + +import Foundation + +final class RewindAction { + let downloader: BlockDownloader + let rustBackend: ZcashRustBackendWelding + let downloaderService: BlockDownloaderService + let logger: Logger + + init(container: DIContainer) { + downloader = container.resolve(BlockDownloader.self) + rustBackend = container.resolve(ZcashRustBackendWelding.self) + downloaderService = container.resolve(BlockDownloaderService.self) + logger = container.resolve(Logger.self) + } + + private func update(context: ActionContext) async -> ActionContext { + await context.update(state: .download) + return context + } +} + +extension RewindAction: Action { + var removeBlocksCacheWhenFailed: Bool { false } + + func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext { + guard let rewindHeight = await context.requestedRewindHeight else { + return await update(context: context) + } + + logger.debug("Executing rewind.") + await downloader.rewind(latestDownloadedBlockHeight: rewindHeight) + try await rustBackend.rewindToHeight(height: Int32(rewindHeight)) + + // clear cache + try await downloaderService.rewind(to: rewindHeight) + + return await update(context: context) + } + + func stop() async { } +} diff --git a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift index 064b1d6f..ebd6dba0 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift @@ -63,9 +63,15 @@ extension ScanAction: Action { // ScanAction is controlled locally so it must report back the updated scanned height await context.update(lastScannedHeight: lastScannedHeight) } + } catch ZcashError.rustScanBlocks(let errorMsg) { + if isContinuityError(errorMsg) { + await context.update(requestedRewindHeight: batchRange.lowerBound - 10) + await context.update(state: .download) + return context + } else { + throw ZcashError.rustScanBlocks(errorMsg) + } } 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 } @@ -74,3 +80,11 @@ extension ScanAction: Action { func stop() async { } } + +private extension ScanAction { + func isContinuityError(_ errorMsg: String) -> Bool { + errorMsg.contains("The parent hash of proposed block does not correspond to the block hash at height") + || errorMsg.contains("Block height discontinuity at height") + || errorMsg.contains("note commitment tree size provided by a compact block did not match the expected size at height") + } +} diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 18c7b2ed..3bd1ea43 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -230,6 +230,8 @@ actor CompactBlockProcessor { action = UpdateChainTipAction(container: container) case .processSuggestedScanRanges: action = ProcessSuggestedScanRangesAction(container: container) + case .rewind: + action = RewindAction(container: container) case .computeSyncControlData: action = ComputeSyncControlDataAction(container: container, configProvider: configProvider) case .download: @@ -607,6 +609,8 @@ extension CompactBlockProcessor { break case .processSuggestedScanRanges: break + case .rewind: + break case .computeSyncControlData: break case .download: