Migrate to Rust backend with fast spendability support
This commit is contained in:
parent
6bb054ba2f
commit
6eb39561ec
|
@ -104,7 +104,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
|
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "cdbc06f10b2d7cbe0d6362b30f68167825942e86"
|
"revision" : "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -16,7 +16,7 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"),
|
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"),
|
||||||
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
|
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
|
||||||
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "cdbc06f10b2d7cbe0d6362b30f68167825942e86")
|
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|
|
@ -36,7 +36,6 @@ enum CBPState: CaseIterable {
|
||||||
case validateServer
|
case validateServer
|
||||||
case computeSyncControlData
|
case computeSyncControlData
|
||||||
case download
|
case download
|
||||||
case validate
|
|
||||||
case scan
|
case scan
|
||||||
case clearAlreadyScannedBlocks
|
case clearAlreadyScannedBlocks
|
||||||
case enhance
|
case enhance
|
||||||
|
|
|
@ -21,7 +21,7 @@ final class DownloadAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update(context: ActionContext) async -> ActionContext {
|
private func update(context: ActionContext) async -> ActionContext {
|
||||||
await context.update(state: .validate)
|
await context.update(state: .scan)
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
//
|
|
||||||
// ValidateAction.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michal Fousek on 05.05.2023.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class ValidateAction {
|
|
||||||
let validator: BlockValidator
|
|
||||||
|
|
||||||
init(container: DIContainer) {
|
|
||||||
validator = container.resolve(BlockValidator.self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ValidateAction: Action {
|
|
||||||
var removeBlocksCacheWhenFailed: Bool { true }
|
|
||||||
|
|
||||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
|
||||||
try await validator.validate()
|
|
||||||
await context.update(state: .scan)
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() async { }
|
|
||||||
}
|
|
|
@ -218,8 +218,6 @@ actor CompactBlockProcessor {
|
||||||
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
|
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
|
||||||
case .download:
|
case .download:
|
||||||
action = DownloadAction(container: container, configProvider: configProvider)
|
action = DownloadAction(container: container, configProvider: configProvider)
|
||||||
case .validate:
|
|
||||||
action = ValidateAction(container: container)
|
|
||||||
case .scan:
|
case .scan:
|
||||||
action = ScanAction(container: container, configProvider: configProvider)
|
action = ScanAction(container: container, configProvider: configProvider)
|
||||||
case .clearAlreadyScannedBlocks:
|
case .clearAlreadyScannedBlocks:
|
||||||
|
@ -591,8 +589,6 @@ extension CompactBlockProcessor {
|
||||||
break
|
break
|
||||||
case .download:
|
case .download:
|
||||||
break
|
break
|
||||||
case .validate:
|
|
||||||
break
|
|
||||||
case .scan:
|
case .scan:
|
||||||
break
|
break
|
||||||
case .clearAlreadyScannedBlocks:
|
case .clearAlreadyScannedBlocks:
|
||||||
|
|
|
@ -50,13 +50,14 @@ extension BlockScannerImpl: BlockScanner {
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
|
|
||||||
let previousScannedHeight = lastScannedHeight
|
let previousScannedHeight = lastScannedHeight
|
||||||
|
let startHeight = previousScannedHeight + 1
|
||||||
|
|
||||||
// TODO: [#576] remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576
|
// TODO: [#576] remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576
|
||||||
let batchSize = scanBatchSize(startScanHeight: previousScannedHeight + 1, network: config.networkType)
|
let batchSize = scanBatchSize(startScanHeight: startHeight, network: config.networkType)
|
||||||
|
|
||||||
let scanStartTime = Date()
|
let scanStartTime = Date()
|
||||||
do {
|
do {
|
||||||
try await self.rustBackend.scanBlocks(limit: batchSize)
|
try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize)
|
||||||
} catch {
|
} catch {
|
||||||
logger.debug("block scanning failed with error: \(String(describing: error))")
|
logger.debug("block scanning failed with error: \(String(describing: error))")
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
//
|
|
||||||
// CompactBlockValidationInformation.swift
|
|
||||||
// ZcashLightClientKit
|
|
||||||
//
|
|
||||||
// Created by Francisco Gindre on 10/30/19.
|
|
||||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
protocol BlockValidator {
|
|
||||||
/// Validate all the downloaded blocks that haven't been yet validated.
|
|
||||||
func validate() async throws
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BlockValidatorImpl {
|
|
||||||
let rustBackend: ZcashRustBackendWelding
|
|
||||||
let metrics: SDKMetrics
|
|
||||||
let logger: Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BlockValidatorImpl: BlockValidator {
|
|
||||||
/// - Throws:
|
|
||||||
/// - `rustValidateCombinedChainValidationFailed` if there was an error during validation unrelated to chain validity.
|
|
||||||
/// - `rustValidateCombinedChainInvalidChain(upperBound)` if the combined chain is invalid. `upperBound` is the height of the highest invalid
|
|
||||||
/// block(on the assumption that the highest block in the cache database is correct).
|
|
||||||
func validate() async throws {
|
|
||||||
try Task.checkCancellation()
|
|
||||||
|
|
||||||
let startTime = Date()
|
|
||||||
do {
|
|
||||||
try await rustBackend.validateCombinedChain(limit: 0)
|
|
||||||
pushProgressReport(startTime: startTime, finishTime: Date())
|
|
||||||
logger.debug("validateChainFinished")
|
|
||||||
} catch {
|
|
||||||
logger.debug("Validate chain failed with \(error)")
|
|
||||||
pushProgressReport(startTime: startTime, finishTime: Date())
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func pushProgressReport(startTime: Date, finishTime: Date) {
|
|
||||||
metrics.pushProgressReport(
|
|
||||||
progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0),
|
|
||||||
start: startTime,
|
|
||||||
end: finishTime,
|
|
||||||
batchSize: 0,
|
|
||||||
operation: .validateBlocks
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// SubtreeRoot.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Jack Grigg on 19/07/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct SubtreeRoot {
|
||||||
|
public let rootHash: Data
|
||||||
|
public let completingBlockHeight: BlockHeight
|
||||||
|
}
|
|
@ -276,6 +276,21 @@ public enum ZcashError: Equatable, Error {
|
||||||
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
|
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
|
||||||
/// ZRUST0045
|
/// ZRUST0045
|
||||||
case rustGetTransparentReceiverInvalidReceiver
|
case rustGetTransparentReceiverInvalidReceiver
|
||||||
|
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
|
||||||
|
/// ZRUST0046
|
||||||
|
case rustPutSaplingSubtreeRootsAllocationProblem
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
|
||||||
|
/// - `rustError` contains error generated by the rust layer.
|
||||||
|
/// ZRUST0047
|
||||||
|
case rustPutSaplingSubtreeRoots(_ rustError: String)
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
|
||||||
|
/// - `rustError` contains error generated by the rust layer.
|
||||||
|
/// ZRUST0048
|
||||||
|
case rustUpdateChainTip(_ rustError: String)
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
|
||||||
|
/// - `rustError` contains error generated by the rust layer.
|
||||||
|
/// ZRUST0049
|
||||||
|
case rustSuggestScanRanges(_ rustError: String)
|
||||||
/// SQLite query failed when fetching all accounts from the database.
|
/// SQLite query failed when fetching all accounts from the database.
|
||||||
/// - `sqliteError` is error produced by SQLite library.
|
/// - `sqliteError` is error produced by SQLite library.
|
||||||
/// ZADAO0001
|
/// ZADAO0001
|
||||||
|
@ -605,6 +620,10 @@ public enum ZcashError: Equatable, Error {
|
||||||
case .rustGetSaplingReceiverInvalidReceiver: return "Sapling receiver generated by rust layer is invalid when calling ZcashRustBackend.getSaplingReceiver"
|
case .rustGetSaplingReceiverInvalidReceiver: return "Sapling receiver generated by rust layer is invalid when calling ZcashRustBackend.getSaplingReceiver"
|
||||||
case .rustGetTransparentReceiverInvalidAddress: return "Error from rust layer when calling ZcashRustBackend.getTransparentReceiver"
|
case .rustGetTransparentReceiverInvalidAddress: return "Error from rust layer when calling ZcashRustBackend.getTransparentReceiver"
|
||||||
case .rustGetTransparentReceiverInvalidReceiver: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver"
|
case .rustGetTransparentReceiverInvalidReceiver: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver"
|
||||||
|
case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to store subtree roots when calling ZcashRustBackend.putSaplingSubtreeRoots"
|
||||||
|
case .rustPutSaplingSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots"
|
||||||
|
case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip"
|
||||||
|
case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges"
|
||||||
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
|
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
|
||||||
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
|
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
|
||||||
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
|
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
|
||||||
|
@ -763,6 +782,10 @@ public enum ZcashError: Equatable, Error {
|
||||||
case .rustGetSaplingReceiverInvalidReceiver: return .rustGetSaplingReceiverInvalidReceiver
|
case .rustGetSaplingReceiverInvalidReceiver: return .rustGetSaplingReceiverInvalidReceiver
|
||||||
case .rustGetTransparentReceiverInvalidAddress: return .rustGetTransparentReceiverInvalidAddress
|
case .rustGetTransparentReceiverInvalidAddress: return .rustGetTransparentReceiverInvalidAddress
|
||||||
case .rustGetTransparentReceiverInvalidReceiver: return .rustGetTransparentReceiverInvalidReceiver
|
case .rustGetTransparentReceiverInvalidReceiver: return .rustGetTransparentReceiverInvalidReceiver
|
||||||
|
case .rustPutSaplingSubtreeRootsAllocationProblem: return .rustPutSaplingSubtreeRootsAllocationProblem
|
||||||
|
case .rustPutSaplingSubtreeRoots: return .rustPutSaplingSubtreeRoots
|
||||||
|
case .rustUpdateChainTip: return .rustUpdateChainTip
|
||||||
|
case .rustSuggestScanRanges: return .rustSuggestScanRanges
|
||||||
case .accountDAOGetAll: return .accountDAOGetAll
|
case .accountDAOGetAll: return .accountDAOGetAll
|
||||||
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
|
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
|
||||||
case .accountDAOFindBy: return .accountDAOFindBy
|
case .accountDAOFindBy: return .accountDAOFindBy
|
||||||
|
|
|
@ -153,6 +153,14 @@ public enum ZcashErrorCode: String {
|
||||||
case rustGetTransparentReceiverInvalidAddress = "ZRUST0044"
|
case rustGetTransparentReceiverInvalidAddress = "ZRUST0044"
|
||||||
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
|
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
|
||||||
case rustGetTransparentReceiverInvalidReceiver = "ZRUST0045"
|
case rustGetTransparentReceiverInvalidReceiver = "ZRUST0045"
|
||||||
|
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
|
||||||
|
case rustPutSaplingSubtreeRootsAllocationProblem = "ZRUST0046"
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
|
||||||
|
case rustPutSaplingSubtreeRoots = "ZRUST0047"
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
|
||||||
|
case rustUpdateChainTip = "ZRUST0048"
|
||||||
|
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
|
||||||
|
case rustSuggestScanRanges = "ZRUST0049"
|
||||||
/// SQLite query failed when fetching all accounts from the database.
|
/// SQLite query failed when fetching all accounts from the database.
|
||||||
case accountDAOGetAll = "ZADAO0001"
|
case accountDAOGetAll = "ZADAO0001"
|
||||||
/// Fetched accounts from SQLite but can't decode them.
|
/// Fetched accounts from SQLite but can't decode them.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// ScanRange.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Jack Grigg on 17/07/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ScanRange {
|
||||||
|
let range: Range<BlockHeight>
|
||||||
|
let priority: UInt8
|
||||||
|
}
|
|
@ -490,21 +490,6 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCombinedChain(limit: UInt32 = 0) async throws {
|
|
||||||
let result = zcashlc_validate_combined_chain(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId)
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case -1:
|
|
||||||
return
|
|
||||||
case 0:
|
|
||||||
throw ZcashError.rustValidateCombinedChainValidationFailed(
|
|
||||||
lastErrorMessage(fallback: "`validateCombinedChain` failed with unknown error")
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
throw ZcashError.rustValidateCombinedChainInvalidChain(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rewindToHeight(height: Int32) async throws {
|
func rewindToHeight(height: Int32) async throws {
|
||||||
let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId)
|
let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId)
|
||||||
|
|
||||||
|
@ -521,8 +506,97 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanBlocks(limit: UInt32 = 0) async throws {
|
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws {
|
||||||
let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId)
|
var ffiSubtreeRootsVec: [FfiSubtreeRoot] = []
|
||||||
|
|
||||||
|
for root in roots {
|
||||||
|
let hashPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: root.rootHash.count)
|
||||||
|
|
||||||
|
let contiguousHashBytes = ContiguousArray(root.rootHash.bytes)
|
||||||
|
|
||||||
|
let result: Void? = contiguousHashBytes.withContiguousStorageIfAvailable { hashBytesPtr in
|
||||||
|
// swiftlint:disable:next force_unwrapping
|
||||||
|
hashPtr.initialize(from: hashBytesPtr.baseAddress!, count: hashBytesPtr.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard result != nil else {
|
||||||
|
defer {
|
||||||
|
hashPtr.deallocate()
|
||||||
|
ffiSubtreeRootsVec.deallocateElements()
|
||||||
|
}
|
||||||
|
throw ZcashError.rustPutSaplingSubtreeRootsAllocationProblem
|
||||||
|
}
|
||||||
|
|
||||||
|
ffiSubtreeRootsVec.append(
|
||||||
|
FfiSubtreeRoot(
|
||||||
|
root_hash_ptr: hashPtr,
|
||||||
|
root_hash_ptr_len: UInt(contiguousHashBytes.count),
|
||||||
|
completing_block_height: UInt32(root.completingBlockHeight)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var contiguousFfiRoots = ContiguousArray(ffiSubtreeRootsVec)
|
||||||
|
|
||||||
|
let len = UInt(contiguousFfiRoots.count)
|
||||||
|
|
||||||
|
let rootsPtr = UnsafeMutablePointer<FfiSubtreeRoots>.allocate(capacity: 1)
|
||||||
|
|
||||||
|
defer { ffiSubtreeRootsVec.deallocateElements() }
|
||||||
|
|
||||||
|
try contiguousFfiRoots.withContiguousMutableStorageIfAvailable { ptr in
|
||||||
|
var roots = FfiSubtreeRoots()
|
||||||
|
roots.ptr = ptr.baseAddress
|
||||||
|
roots.len = len
|
||||||
|
|
||||||
|
rootsPtr.initialize(to: roots)
|
||||||
|
|
||||||
|
let res = zcashlc_put_sapling_subtree_roots(dbData.0, dbData.1, startIndex, rootsPtr, networkType.networkId)
|
||||||
|
|
||||||
|
guard res else {
|
||||||
|
throw ZcashError.rustPutSaplingSubtreeRoots(lastErrorMessage(fallback: "`putSaplingSubtreeRoots` failed with unknown error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChainTip(height: Int32) async throws {
|
||||||
|
let result = zcashlc_update_chain_tip(dbData.0, dbData.1, height, networkType.networkId)
|
||||||
|
|
||||||
|
guard result else {
|
||||||
|
throw ZcashError.rustUpdateChainTip(lastErrorMessage(fallback: "`updateChainTip` failed with unknown error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func suggestScanRanges() async throws -> [ScanRange] {
|
||||||
|
let scanRangesPtr = zcashlc_suggest_scan_ranges(dbData.0, dbData.1, networkType.networkId)
|
||||||
|
|
||||||
|
guard let scanRangesPtr else {
|
||||||
|
throw ZcashError.rustSuggestScanRanges(lastErrorMessage(fallback: "`suggestScanRanges` failed with unknown error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer { zcashlc_free_scan_ranges(scanRangesPtr) }
|
||||||
|
|
||||||
|
var scanRanges: [ScanRange] = []
|
||||||
|
|
||||||
|
for i in (0 ..< Int(scanRangesPtr.pointee.len)) {
|
||||||
|
let scanRange = scanRangesPtr.pointee.ptr.advanced(by: i).pointee
|
||||||
|
|
||||||
|
scanRanges.append(
|
||||||
|
ScanRange(
|
||||||
|
range: Range(uncheckedBounds: (
|
||||||
|
BlockHeight(scanRange.start),
|
||||||
|
BlockHeight(scanRange.end)
|
||||||
|
)),
|
||||||
|
priority: scanRange.priority
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanBlocks(fromHeight: Int32, limit: UInt32 = 0) async throws {
|
||||||
|
let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, fromHeight, limit, networkType.networkId)
|
||||||
|
|
||||||
guard result != 0 else {
|
guard result != 0 else {
|
||||||
throw ZcashError.rustScanBlocks(lastErrorMessage(fallback: "`scanBlocks` failed with unknown error"))
|
throw ZcashError.rustScanBlocks(lastErrorMessage(fallback: "`scanBlocks` failed with unknown error"))
|
||||||
|
@ -657,3 +731,11 @@ extension Array where Element == FFIBlockMeta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array where Element == FfiSubtreeRoot {
|
||||||
|
func deallocateElements() {
|
||||||
|
self.forEach { element in
|
||||||
|
element.root_hash_ptr.deallocate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -159,26 +159,6 @@ protocol ZcashRustBackendWelding {
|
||||||
/// - `rustGetVerifiedTransparentBalance` if rust layer returns error.
|
/// - `rustGetVerifiedTransparentBalance` if rust layer returns error.
|
||||||
func getVerifiedTransparentBalance(account: Int32) async throws -> Int64
|
func getVerifiedTransparentBalance(account: Int32) async throws -> Int64
|
||||||
|
|
||||||
/// Checks that the scanned blocks in the data database, when combined with the recent
|
|
||||||
/// `CompactBlock`s in the cache database, form a valid chain.
|
|
||||||
/// This function is built on the core assumption that the information provided in the
|
|
||||||
/// cache database is more likely to be accurate than the previously-scanned information.
|
|
||||||
/// This follows from the design (and trust) assumption that the `lightwalletd` server
|
|
||||||
/// provides accurate block information as of the time it was requested.
|
|
||||||
/// - parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is.
|
|
||||||
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
|
|
||||||
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
|
|
||||||
/// - parameter dbData: location of the data db file
|
|
||||||
/// - parameter networkType: the network type
|
|
||||||
/// - parameter limit: a limit to validate a fixed number of blocks instead of the whole cache.
|
|
||||||
/// - Throws:
|
|
||||||
/// - `rustValidateCombinedChainValidationFailed` if there was an error during validation unrelated to chain validity.
|
|
||||||
/// - `rustValidateCombinedChainInvalidChain(upperBound)` if the combined chain is invalid. `upperBound` is the height of the highest invalid
|
|
||||||
/// block(on the assumption that the highest block in the cache database is correct).
|
|
||||||
///
|
|
||||||
/// - Important: This function does not mutate either of the databases.
|
|
||||||
func validateCombinedChain(limit: UInt32) async throws
|
|
||||||
|
|
||||||
/// 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
|
/// 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.
|
/// - parameter height: height to rewind to.
|
||||||
/// - Throws: `rustRewindToHeight` if rust layer returns error.
|
/// - Throws: `rustRewindToHeight` if rust layer returns error.
|
||||||
|
@ -190,21 +170,35 @@ protocol ZcashRustBackendWelding {
|
||||||
/// - Throws: `rustRewindCacheToHeight` if rust layer returns error.
|
/// - Throws: `rustRewindCacheToHeight` if rust layer returns error.
|
||||||
func rewindCacheToHeight(height: Int32) async throws
|
func rewindCacheToHeight(height: Int32) async throws
|
||||||
|
|
||||||
|
/// Returns a list of suggested scan ranges based upon the current wallet state.
|
||||||
|
///
|
||||||
|
/// This method should only be used in cases where the `CompactBlock` data that will be
|
||||||
|
/// made available to `scanBlocks` for the requested block ranges includes note
|
||||||
|
/// commitment tree size information for each block; or else the scan is likely to fail if
|
||||||
|
/// notes belonging to the wallet are detected.
|
||||||
|
func suggestScanRanges() async throws -> [ScanRange]
|
||||||
|
|
||||||
/// Scans new blocks added to the cache for any transactions received by the tracked
|
/// Scans new blocks added to the cache for any transactions received by the tracked
|
||||||
/// accounts.
|
/// accounts, while checking that they form a valid chan.
|
||||||
/// This function pays attention only to cached blocks with heights greater than the
|
///
|
||||||
/// highest scanned block in `db_data`. Cached blocks with lower heights are not verified
|
/// This function is built on the core assumption that the information provided in the
|
||||||
/// against previously-scanned blocks. In particular, this function **assumes** that the
|
/// block cache is more likely to be accurate than the previously-scanned information.
|
||||||
/// caller is handling rollbacks.
|
/// This follows from the design (and trust) assumption that the `lightwalletd` server
|
||||||
|
/// provides accurate block information as of the time it was requested.
|
||||||
|
///
|
||||||
|
/// This function **assumes** that the caller is handling rollbacks.
|
||||||
|
///
|
||||||
/// For brand-new light client databases, this function starts scanning from the Sapling
|
/// For brand-new light client databases, this function starts scanning from the Sapling
|
||||||
/// activation height. This height can be fast-forwarded to a more recent block by calling
|
/// activation height. This height can be fast-forwarded to a more recent block by calling
|
||||||
/// [`initBlocksTable`] before this function.
|
/// [`initBlocksTable`] before this function.
|
||||||
|
///
|
||||||
/// Scanned blocks are required to be height-sequential. If a block is missing from the
|
/// Scanned blocks are required to be height-sequential. If a block is missing from the
|
||||||
/// cache, an error will be signalled.
|
/// cache, an error will be signalled.
|
||||||
///
|
///
|
||||||
/// - parameter limit: scan up to limit blocks. pass 0 to set no limit.
|
/// - parameter fromHeight: scan starting from the given height.
|
||||||
|
/// - parameter limit: scan up to limit blocks.
|
||||||
/// - Throws: `rustScanBlocks` if rust layer returns error.
|
/// - Throws: `rustScanBlocks` if rust layer returns error.
|
||||||
func scanBlocks(limit: UInt32) async throws
|
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws
|
||||||
|
|
||||||
/// Upserts a UTXO into the data db database
|
/// Upserts a UTXO into the data db database
|
||||||
/// - parameter txid: the txid bytes for the UTXO
|
/// - parameter txid: the txid bytes for the UTXO
|
||||||
|
|
|
@ -114,18 +114,6 @@ enum Dependencies {
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
container.register(type: BlockValidator.self, isSingleton: true) { di in
|
|
||||||
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
|
||||||
let metrics = di.resolve(SDKMetrics.self)
|
|
||||||
let logger = di.resolve(Logger.self)
|
|
||||||
|
|
||||||
return BlockValidatorImpl(
|
|
||||||
rustBackend: rustBackend,
|
|
||||||
metrics: metrics,
|
|
||||||
logger: logger
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
container.register(type: BlockScanner.self, isSingleton: true) { di in
|
container.register(type: BlockScanner.self, isSingleton: true) { di in
|
||||||
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
||||||
|
|
|
@ -48,8 +48,8 @@ final class DownloadActionTests: ZcashTestCase {
|
||||||
|
|
||||||
let nextState = await nextContext.state
|
let nextState = await nextContext.state
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
nextState == .validate,
|
nextState == .scan,
|
||||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
"nextContext after .download is expected to be .scan but received \(nextState)"
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
|
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
|
||||||
|
@ -84,8 +84,8 @@ final class DownloadActionTests: ZcashTestCase {
|
||||||
|
|
||||||
let nextState = await nextContext.state
|
let nextState = await nextContext.state
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
nextState == .validate,
|
nextState == .scan,
|
||||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
"nextContext after .download is expected to be .scan but received \(nextState)"
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)")
|
XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)")
|
||||||
|
@ -122,8 +122,8 @@ final class DownloadActionTests: ZcashTestCase {
|
||||||
|
|
||||||
let nextState = await nextContext.state
|
let nextState = await nextContext.state
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
nextState == .validate,
|
nextState == .scan,
|
||||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
"nextContext after .download is expected to be .scan but received \(nextState)"
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)")
|
XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)")
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// ValidateActionTests.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Lukáš Korba on 17.05.2023.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
@testable import TestUtils
|
|
||||||
@testable import ZcashLightClientKit
|
|
||||||
|
|
||||||
final class ValidateActionTests: ZcashTestCase {
|
|
||||||
func testValidateAction_NextAction() async throws {
|
|
||||||
let blockValidatorMock = BlockValidatorMock()
|
|
||||||
|
|
||||||
blockValidatorMock.validateClosure = { }
|
|
||||||
|
|
||||||
mockContainer.mock(type: BlockValidator.self, isSingleton: true) { _ in blockValidatorMock }
|
|
||||||
|
|
||||||
let validateAction = ValidateAction(
|
|
||||||
container: mockContainer
|
|
||||||
)
|
|
||||||
|
|
||||||
do {
|
|
||||||
let nextContext = try await validateAction.run(with: .init(state: .validate)) { _ in }
|
|
||||||
XCTAssertTrue(blockValidatorMock.validateCalled, "validator.validate() is expected to be called.")
|
|
||||||
let nextState = await nextContext.state
|
|
||||||
XCTAssertTrue(
|
|
||||||
nextState == .scan,
|
|
||||||
"nextContext after .validate is expected to be .scan but received \(nextState)"
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
XCTFail("testValidateAction_NextAction is not expected to fail. \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,6 @@ extension BlockDownloader { }
|
||||||
extension BlockDownloaderService { }
|
extension BlockDownloaderService { }
|
||||||
extension BlockEnhancer { }
|
extension BlockEnhancer { }
|
||||||
extension BlockScanner { }
|
extension BlockScanner { }
|
||||||
extension BlockValidator { }
|
|
||||||
extension CompactBlockRepository { }
|
extension CompactBlockRepository { }
|
||||||
extension LatestBlocksDataProvider { }
|
extension LatestBlocksDataProvider { }
|
||||||
extension LightWalletdInfo { }
|
extension LightWalletdInfo { }
|
||||||
|
|
|
@ -378,31 +378,6 @@ class BlockScannerMock: BlockScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
class BlockValidatorMock: BlockValidator {
|
|
||||||
|
|
||||||
|
|
||||||
init(
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - validate
|
|
||||||
|
|
||||||
var validateThrowableError: Error?
|
|
||||||
var validateCallsCount = 0
|
|
||||||
var validateCalled: Bool {
|
|
||||||
return validateCallsCount > 0
|
|
||||||
}
|
|
||||||
var validateClosure: (() async throws -> Void)?
|
|
||||||
|
|
||||||
func validate() async throws {
|
|
||||||
if let error = validateThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
validateCallsCount += 1
|
|
||||||
try await validateClosure!()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
class CompactBlockRepositoryMock: CompactBlockRepository {
|
class CompactBlockRepositoryMock: CompactBlockRepository {
|
||||||
|
|
||||||
|
@ -2555,31 +2530,6 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - validateCombinedChain
|
|
||||||
|
|
||||||
var validateCombinedChainLimitThrowableError: Error?
|
|
||||||
func setValidateCombinedChainLimitThrowableError(_ param: Error?) async {
|
|
||||||
validateCombinedChainLimitThrowableError = param
|
|
||||||
}
|
|
||||||
var validateCombinedChainLimitCallsCount = 0
|
|
||||||
var validateCombinedChainLimitCalled: Bool {
|
|
||||||
return validateCombinedChainLimitCallsCount > 0
|
|
||||||
}
|
|
||||||
var validateCombinedChainLimitReceivedLimit: UInt32?
|
|
||||||
var validateCombinedChainLimitClosure: ((UInt32) async throws -> Void)?
|
|
||||||
func setValidateCombinedChainLimitClosure(_ param: ((UInt32) async throws -> Void)?) async {
|
|
||||||
validateCombinedChainLimitClosure = param
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCombinedChain(limit: UInt32) async throws {
|
|
||||||
if let error = validateCombinedChainLimitThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
validateCombinedChainLimitCallsCount += 1
|
|
||||||
validateCombinedChainLimitReceivedLimit = limit
|
|
||||||
try await validateCombinedChainLimitClosure!(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - rewindToHeight
|
// MARK: - rewindToHeight
|
||||||
|
|
||||||
var rewindToHeightHeightThrowableError: Error?
|
var rewindToHeightHeightThrowableError: Error?
|
||||||
|
@ -2630,6 +2580,37 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
||||||
try await rewindCacheToHeightHeightClosure!(height)
|
try await rewindCacheToHeightHeightClosure!(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - suggestScanRanges
|
||||||
|
|
||||||
|
var suggestScanRangesThrowableError: Error?
|
||||||
|
func setSuggestScanRangesThrowableError(_ param: Error?) async {
|
||||||
|
suggestScanRangesThrowableError = param
|
||||||
|
}
|
||||||
|
var suggestScanRangesCallsCount = 0
|
||||||
|
var suggestScanRangesCalled: Bool {
|
||||||
|
return suggestScanRangesCallsCount > 0
|
||||||
|
}
|
||||||
|
var suggestScanRangesReturnValue: [ScanRange]!
|
||||||
|
func setSuggestScanRangesReturnValue(_ param: [ScanRange]) async {
|
||||||
|
suggestScanRangesReturnValue = param
|
||||||
|
}
|
||||||
|
var suggestScanRangesClosure: (() async throws -> [ScanRange])?
|
||||||
|
func setSuggestScanRangesClosure(_ param: (() async throws -> [ScanRange])?) async {
|
||||||
|
suggestScanRangesClosure = param
|
||||||
|
}
|
||||||
|
|
||||||
|
func suggestScanRanges() async throws -> [ScanRange] {
|
||||||
|
if let error = suggestScanRangesThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
suggestScanRangesCallsCount += 1
|
||||||
|
if let closure = suggestScanRangesClosure {
|
||||||
|
return try await closure()
|
||||||
|
} else {
|
||||||
|
return suggestScanRangesReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - scanBlocks
|
// MARK: - scanBlocks
|
||||||
|
|
||||||
var scanBlocksLimitThrowableError: Error?
|
var scanBlocksLimitThrowableError: Error?
|
||||||
|
@ -2640,19 +2621,21 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
||||||
var scanBlocksLimitCalled: Bool {
|
var scanBlocksLimitCalled: Bool {
|
||||||
return scanBlocksLimitCallsCount > 0
|
return scanBlocksLimitCallsCount > 0
|
||||||
}
|
}
|
||||||
|
var scanBlocksLimitReceivedFromHeight: Int32?
|
||||||
var scanBlocksLimitReceivedLimit: UInt32?
|
var scanBlocksLimitReceivedLimit: UInt32?
|
||||||
var scanBlocksLimitClosure: ((UInt32) async throws -> Void)?
|
var scanBlocksLimitClosure: ((Int32, UInt32) async throws -> Void)?
|
||||||
func setScanBlocksLimitClosure(_ param: ((UInt32) async throws -> Void)?) async {
|
func setScanBlocksLimitClosure(_ param: ((Int32, UInt32) async throws -> Void)?) async {
|
||||||
scanBlocksLimitClosure = param
|
scanBlocksLimitClosure = param
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanBlocks(limit: UInt32) async throws {
|
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws {
|
||||||
if let error = scanBlocksLimitThrowableError {
|
if let error = scanBlocksLimitThrowableError {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
scanBlocksLimitCallsCount += 1
|
scanBlocksLimitCallsCount += 1
|
||||||
|
scanBlocksLimitReceivedFromHeight = fromHeight
|
||||||
scanBlocksLimitReceivedLimit = limit
|
scanBlocksLimitReceivedLimit = limit
|
||||||
try await scanBlocksLimitClosure!(limit)
|
try await scanBlocksLimitClosure!(fromHeight, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - putUnspentTransparentOutput
|
// MARK: - putUnspentTransparentOutput
|
||||||
|
|
|
@ -132,40 +132,18 @@ class RustBackendMockHelper {
|
||||||
return try await rustBackend.getVerifiedBalance(account: account)
|
return try await rustBackend.getVerifiedBalance(account: account)
|
||||||
}
|
}
|
||||||
|
|
||||||
await rustBackendMock.setValidateCombinedChainLimitClosure() { [weak self] limit in
|
|
||||||
guard let self else { throw ZcashError.rustValidateCombinedChainValidationFailed("Self is nil") }
|
|
||||||
if let rate = mockValidateCombinedChainSuccessRate {
|
|
||||||
if Self.shouldSucceed(successRate: rate) {
|
|
||||||
return try await rustBackend.validateCombinedChain(limit: limit)
|
|
||||||
} else {
|
|
||||||
throw mockValidateCombinedChainFailureError
|
|
||||||
}
|
|
||||||
} else if let attempts = self.mockValidateCombinedChainFailAfterAttempts {
|
|
||||||
self.mockValidateCombinedChainFailAfterAttempts = attempts - 1
|
|
||||||
if attempts > 0 {
|
|
||||||
return try await rustBackend.validateCombinedChain(limit: limit)
|
|
||||||
} else {
|
|
||||||
if attempts == 0 {
|
|
||||||
throw mockValidateCombinedChainFailureError
|
|
||||||
} else if attempts < 0 && mockValidateCombinedChainKeepFailing {
|
|
||||||
throw mockValidateCombinedChainFailureError
|
|
||||||
} else {
|
|
||||||
return try await rustBackend.validateCombinedChain(limit: limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return try await rustBackend.validateCombinedChain(limit: limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await rustBackendMock.setRewindToHeightHeightClosure() { height in
|
await rustBackendMock.setRewindToHeightHeightClosure() { height in
|
||||||
try await rustBackend.rewindToHeight(height: height)
|
try await rustBackend.rewindToHeight(height: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
await rustBackendMock.setRewindCacheToHeightHeightClosure() { _ in }
|
await rustBackendMock.setRewindCacheToHeightHeightClosure() { _ in }
|
||||||
|
|
||||||
await rustBackendMock.setScanBlocksLimitClosure() { limit in
|
await rustBackendMock.setSuggestScanRangesClosure() {
|
||||||
try await rustBackend.scanBlocks(limit: limit)
|
try await rustBackend.suggestScanRanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
await rustBackendMock.setScanBlocksLimitClosure() { fromHeight, limit in
|
||||||
|
try await rustBackend.scanBlocks(fromHeight: fromHeight, limit: limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue