diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3840d78d..81341bb9 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -174,10 +174,9 @@ { "identity" : "zcash-light-client-ffi", "kind" : "remoteSourceControl", - "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", + "location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", "state" : { - "revision" : "8ed5b08d59ff5e7e11240be29b084dedbdf2f268", - "version" : "0.9.1" + "revision" : "a61f82942c1dad9161f346123f292a93081c7e36" } } ], diff --git a/Package.resolved b/Package.resolved index 7d3ab1dd..cd6c7c2b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "6ade19f0b57f5fc436dfecfced83f3c84d1095b9", - "version" : "1.21.0" + "revision" : "07123ed731671e800ab8d641006613612e954746", + "version" : "1.23.1" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stephencelis/SQLite.swift.git", "state" : { - "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", - "version" : "0.14.1" + "revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8", + "version" : "0.15.3" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62", - "version" : "2.63.0" + "revision" : "1b33db2dea6a64d5b619b9e888175133c6d7f410", + "version" : "2.73.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", - "version" : "1.30.0" + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", - "version" : "1.25.2" + "revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5", + "version" : "1.28.1" } }, { @@ -120,10 +120,9 @@ { "identity" : "zcash-light-client-ffi", "kind" : "remoteSourceControl", - "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", + "location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", "state" : { - "revision" : "8ed5b08d59ff5e7e11240be29b084dedbdf2f268", - "version" : "0.9.1" + "revision" : "a61f82942c1dad9161f346123f292a93081c7e36" } } ], diff --git a/Package.swift b/Package.swift index 9c97098b..4bdb7846 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.23.0"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3"), - .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.9.1") + .package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", revision: "a61f82942c1dad9161f346123f292a93081c7e36") ], targets: [ .target( diff --git a/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift index d5654d6d..09345b00 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift @@ -30,14 +30,28 @@ 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 { + guard let requestedRewindHeight = await context.requestedRewindHeight else { return await update(context: context) } + var rewindHeight = BlockHeight(requestedRewindHeight) logger.debug("Executing rewind.") + let rewindResult = try await rustBackend.rewindToHeight(height: rewindHeight) + switch rewindResult { + case let .success(height): + rewindHeight = height + case let .requestedHeightTooLow(safeHeight): + let retryResult = try await rustBackend.rewindToHeight(height: safeHeight) + switch retryResult { + case let .success(height): + rewindHeight = height + default: + throw ZcashError.rustRewindToHeight(Int32(safeHeight), lastErrorMessage(fallback: "`rewindToHeight` unable to rewind")) + } + } + await downloader.rewind(latestDownloadedBlockHeight: rewindHeight) - try await rustBackend.rewindToHeight(height: Int32(rewindHeight)) - + // clear cache try await downloaderService.rewind(to: rewindHeight) diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index b353aa42..0c629af2 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -320,22 +320,22 @@ extension CompactBlockProcessor { private func doRewind(context: AfterSyncHooksManager.RewindContext) async throws { logger.debug("Executing rewind.") let lastDownloaded = await latestBlocksDataProvider.maxScannedHeight - let height = Int32(context.height ?? lastDownloaded) - - let nearestHeight: Int32 - do { - nearestHeight = try await rustBackend.getNearestRewindHeight(height: height) - } catch { - await failure(error) - return await context.completion(.failure(error)) - } - - // FIXME: [#719] this should be done on the rust layer, https://github.com/zcash/ZcashLightClientKit/issues/719 - let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday)) - + var rewindHeight = BlockHeight(Int32(context.height ?? lastDownloaded) - 10) do { - try await rewindDownloadBlockAction(to: BlockHeight(rewindHeight)) - try await rustBackend.rewindToHeight(height: rewindHeight) + let rewindResult = try await rustBackend.rewindToHeight(height: rewindHeight) + switch rewindResult { + case let .success(height): + rewindHeight = height + case let .requestedHeightTooLow(safeHeight): + let retryResult = try await rustBackend.rewindToHeight(height: safeHeight) + switch retryResult { + case let .success(height): + rewindHeight = height + default: + throw ZcashError.rustRewindToHeight(Int32(safeHeight), lastErrorMessage(fallback: "`rewindToHeight` unable to rewind")) + } + } + try await rewindDownloadBlockAction(to: rewindHeight) } catch { await failure(error) return await context.completion(.failure(error)) diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 04fde49f..5fcb22a7 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -243,22 +243,6 @@ struct ZcashRustBackend: ZcashRustBackendWelding { return UnifiedAddress(validatedEncoding: address, networkType: networkType) } - @DBActor - func getNearestRewindHeight(height: Int32) async throws -> Int32 { - let result = zcashlc_get_nearest_rewind_height( - dbData.0, - dbData.1, - height, - networkType.networkId - ) - - guard result > 0 else { - throw ZcashError.rustGetNearestRewindHeight(lastErrorMessage(fallback: "`getNearestRewindHeight` failed with unknown error")) - } - - return result - } - @DBActor func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress { let addressCStr = zcashlc_get_next_available_address( @@ -501,11 +485,16 @@ struct ZcashRustBackend: ZcashRustBackendWelding { } @DBActor - func rewindToHeight(height: Int32) async throws { - let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId) - - guard result else { - throw ZcashError.rustRewindToHeight(height, lastErrorMessage(fallback: "`rewindToHeight` failed with unknown error")) + func rewindToHeight(height: BlockHeight) async throws -> RewindResult { + var safeRewindHeight: UInt32 = 0 + let result = zcashlc_rewind_to_height(dbData.0, dbData.1, UInt32(height), networkType.networkId, &safeRewindHeight) + + if result >= 0 { + return .success(BlockHeight(result)) + } else if result == -1 && safeRewindHeight > 0 { + return .requestedHeightTooLow(BlockHeight(safeRewindHeight)) + } else { + throw ZcashError.rustRewindToHeight(Int32(height), lastErrorMessage(fallback: "`rewindToHeight` failed with unknown error")) } } diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index 89054361..94198110 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -21,6 +21,17 @@ public enum DbInitResult { case seedNotRelevant } +/// Enumeration of potential return states for database rewind. +/// +public enum RewindResult { + /// The rewind succeeded. The associated block height indicates the maximum height of + /// stored block data retained by the database; this may be less than the block height that + /// was requested. + case success(BlockHeight) + /// The rewind did not succeed but the caller may re-attempt given the associated block height. + case requestedHeightTooLow(BlockHeight) +} + protocol ZcashRustBackendWelding { /// Returns a list of the accounts in the wallet. func listAccounts() async throws -> [Int32] @@ -64,18 +75,6 @@ protocol ZcashRustBackendWelding { /// - `rustGetCurrentAddressInvalidAddress` if generated unified address isn't valid. func getCurrentAddress(account: Int32) async throws -> UnifiedAddress - /// Wallets might need to be rewound because of a reorg, or by user request. - /// There are times where the wallet could get out of sync for many reasons and - /// users might be asked to rescan their wallets in order to fix that. This function - /// returns the nearest height where a rewind is possible. Currently pruning gets rid - /// of sapling witnesses older than 100 blocks. So in order to reconstruct the witness - /// tree that allows to spend notes from the given wallet the rewind can't be more than - /// 100 blocks or back to the oldest unspent note that this wallet contains. - /// - parameter height: height you would like to rewind to. - /// - Returns: the blockheight of the nearest rewind height. - /// - Throws: `rustGetNearestRewindHeight`. - func getNearestRewindHeight(height: Int32) async throws -> Int32 - /// Returns a newly-generated unified payment address for the specified account, with the next available diversifier. /// - parameter account: index of the given account /// - Throws: @@ -123,11 +122,11 @@ protocol ZcashRustBackendWelding { /// Resets the state of the database to only contain block and transaction information up to the given height. clears up all derived data as well /// - parameter height: height to rewind to. /// - Throws: `rustRewindToHeight` if rust layer returns error. - func rewindToHeight(height: Int32) async throws + func rewindToHeight(height: BlockHeight) async throws -> RewindResult /// Resets the state of the FsBlock database to only contain block and transaction information up to the given height. /// - Note: this does not delete the files. Only rolls back the database. - /// - parameter height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use `getNearestRewindHeight` when unsure + /// - parameter height: height to rewind to. This should be the height returned by a successful `rewindToHeight` call. /// - Throws: `rustRewindCacheToHeight` if rust layer returns error. func rewindCacheToHeight(height: Int32) async throws