Migrate to Rust backend with fast spendability support

This commit is contained in:
Jack Grigg 2023-07-19 18:13:59 +01:00
parent 6bb054ba2f
commit 6eb39561ec
20 changed files with 232 additions and 270 deletions

View File

@ -104,7 +104,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "cdbc06f10b2d7cbe0d6362b30f68167825942e86"
"revision" : "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125"
}
}
],

View File

@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.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/zcash-hackworks/zcash-light-client-ffi", revision: "cdbc06f10b2d7cbe0d6362b30f68167825942e86")
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "57eb3bd4db3c26bf44d2d8d8b0d6f09f7602a125")
],
targets: [
.target(

View File

@ -36,7 +36,6 @@ enum CBPState: CaseIterable {
case validateServer
case computeSyncControlData
case download
case validate
case scan
case clearAlreadyScannedBlocks
case enhance

View File

@ -21,7 +21,7 @@ final class DownloadAction {
}
private func update(context: ActionContext) async -> ActionContext {
await context.update(state: .validate)
await context.update(state: .scan)
return context
}
}

View File

@ -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 { }
}

View File

@ -218,8 +218,6 @@ actor CompactBlockProcessor {
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
case .download:
action = DownloadAction(container: container, configProvider: configProvider)
case .validate:
action = ValidateAction(container: container)
case .scan:
action = ScanAction(container: container, configProvider: configProvider)
case .clearAlreadyScannedBlocks:
@ -591,8 +589,6 @@ extension CompactBlockProcessor {
break
case .download:
break
case .validate:
break
case .scan:
break
case .clearAlreadyScannedBlocks:

View File

@ -50,13 +50,14 @@ extension BlockScannerImpl: BlockScanner {
try Task.checkCancellation()
let previousScannedHeight = lastScannedHeight
let startHeight = previousScannedHeight + 1
// 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()
do {
try await self.rustBackend.scanBlocks(limit: batchSize)
try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize)
} catch {
logger.debug("block scanning failed with error: \(String(describing: error))")
throw error

View File

@ -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
)
}
}

View File

@ -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
}

View File

@ -276,6 +276,21 @@ public enum ZcashError: Equatable, Error {
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
/// ZRUST0045
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.
/// - `sqliteError` is error produced by SQLite library.
/// 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 .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 .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 .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
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 .rustGetTransparentReceiverInvalidAddress: return .rustGetTransparentReceiverInvalidAddress
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 .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy

View File

@ -153,6 +153,14 @@ public enum ZcashErrorCode: String {
case rustGetTransparentReceiverInvalidAddress = "ZRUST0044"
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
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.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.

View File

@ -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
}

View File

@ -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 {
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 {
let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId)
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws {
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 {
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()
}
}
}

View File

@ -159,26 +159,6 @@ protocol ZcashRustBackendWelding {
/// - `rustGetVerifiedTransparentBalance` if rust layer returns error.
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
/// - parameter height: height to rewind to.
/// - Throws: `rustRewindToHeight` if rust layer returns error.
@ -190,21 +170,35 @@ protocol ZcashRustBackendWelding {
/// - Throws: `rustRewindCacheToHeight` if rust layer returns error.
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
/// accounts.
/// 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
/// against previously-scanned blocks. In particular, this function **assumes** that the
/// caller is handling rollbacks.
/// accounts, while checking that they form a valid chan.
///
/// This function is built on the core assumption that the information provided in the
/// block cache 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.
///
/// This function **assumes** that the caller is handling rollbacks.
///
/// 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
/// [`initBlocksTable`] before this function.
///
/// Scanned blocks are required to be height-sequential. If a block is missing from the
/// 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.
func scanBlocks(limit: UInt32) async throws
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws
/// Upserts a UTXO into the data db database
/// - parameter txid: the txid bytes for the UTXO

View File

@ -114,18 +114,6 @@ enum Dependencies {
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
let rustBackend = di.resolve(ZcashRustBackendWelding.self)

View File

@ -48,8 +48,8 @@ final class DownloadActionTests: ZcashTestCase {
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .validate,
"nextContext after .download is expected to be .validate but received \(nextState)"
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
)
} catch {
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
@ -84,8 +84,8 @@ final class DownloadActionTests: ZcashTestCase {
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .validate,
"nextContext after .download is expected to be .validate but received \(nextState)"
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
)
} catch {
XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)")
@ -122,8 +122,8 @@ final class DownloadActionTests: ZcashTestCase {
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .validate,
"nextContext after .download is expected to be .validate but received \(nextState)"
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
)
} catch {
XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)")

View File

@ -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)")
}
}
}

View File

@ -16,7 +16,6 @@ extension BlockDownloader { }
extension BlockDownloaderService { }
extension BlockEnhancer { }
extension BlockScanner { }
extension BlockValidator { }
extension CompactBlockRepository { }
extension LatestBlocksDataProvider { }
extension LightWalletdInfo { }

View File

@ -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 {
@ -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
var rewindToHeightHeightThrowableError: Error?
@ -2630,6 +2580,37 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
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
var scanBlocksLimitThrowableError: Error?
@ -2640,19 +2621,21 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
var scanBlocksLimitCalled: Bool {
return scanBlocksLimitCallsCount > 0
}
var scanBlocksLimitReceivedFromHeight: Int32?
var scanBlocksLimitReceivedLimit: UInt32?
var scanBlocksLimitClosure: ((UInt32) async throws -> Void)?
func setScanBlocksLimitClosure(_ param: ((UInt32) async throws -> Void)?) async {
var scanBlocksLimitClosure: ((Int32, UInt32) async throws -> Void)?
func setScanBlocksLimitClosure(_ param: ((Int32, UInt32) async throws -> Void)?) async {
scanBlocksLimitClosure = param
}
func scanBlocks(limit: UInt32) async throws {
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws {
if let error = scanBlocksLimitThrowableError {
throw error
}
scanBlocksLimitCallsCount += 1
scanBlocksLimitReceivedFromHeight = fromHeight
scanBlocksLimitReceivedLimit = limit
try await scanBlocksLimitClosure!(limit)
try await scanBlocksLimitClosure!(fromHeight, limit)
}
// MARK: - putUnspentTransparentOutput

View File

@ -132,40 +132,18 @@ class RustBackendMockHelper {
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
try await rustBackend.rewindToHeight(height: height)
}
await rustBackendMock.setRewindCacheToHeightHeightClosure() { _ in }
await rustBackendMock.setScanBlocksLimitClosure() { limit in
try await rustBackend.scanBlocks(limit: limit)
await rustBackendMock.setSuggestScanRangesClosure() {
try await rustBackend.suggestScanRanges()
}
await rustBackendMock.setScanBlocksLimitClosure() { fromHeight, limit in
try await rustBackend.scanBlocks(fromHeight: fromHeight, limit: limit)
}
}