Merge pull request #1149 from zcash/feature/pre-dag

The feature branch consists of 3 main parts of the new synchronization management

- the State Machine being aware of previous state
- the new firstUnenhancedHeight API
- ClearCache being called at the beginning of the State Machine
This commit is contained in:
Lukas Korba 2023-06-28 15:00:37 +02:00 committed by GitHub
commit 605af0417f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 674 additions and 1668 deletions

View File

@ -20,6 +20,7 @@ enum DemoAppConfig {
static let host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "lightwalletd.testnet.electriccoin.co"
static let port: Int = 9067
static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor

View File

@ -29,6 +29,6 @@ class GetBalanceViewController: UIViewController {
extension Zatoshi {
var formattedString: String? {
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self.amount))
decimalString(formatter: NumberFormatter.zcashNumberFormatter)
}
}

View File

@ -9,25 +9,32 @@ import Foundation
actor ActionContext {
var state: CBPState
var syncRanges: SyncRanges
var prevState: CBPState?
var syncControlData: SyncControlData
var totalProgressRange: CompactBlockRange = 0...0
var lastDownloadedHeight: BlockHeight?
var lastEnhancedHeight: BlockHeight?
init(state: CBPState) {
self.state = state
syncRanges = SyncRanges.empty
syncControlData = SyncControlData.empty
}
func update(state: CBPState) async { self.state = state }
func update(syncRanges: SyncRanges) async { self.syncRanges = syncRanges }
func update(state: CBPState) async {
prevState = self.state
self.state = state
}
func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
func update(totalProgressRange: CompactBlockRange) async { self.totalProgressRange = totalProgressRange }
func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
}
enum CBPState: CaseIterable {
case idle
case migrateLegacyCacheDB
case validateServer
case computeSyncRanges
case checksBeforeSync
case computeSyncControlData
case download
case validate
case scan

View File

@ -1,62 +0,0 @@
//
// ChecksBeforeSyncAction.swift
//
//
// Created by Michal Fousek on 05.05.2023.
//
import Foundation
final class ChecksBeforeSyncAction {
let internalSyncProgress: InternalSyncProgress
let storage: CompactBlockRepository
init(container: DIContainer) {
internalSyncProgress = container.resolve(InternalSyncProgress.self)
storage = container.resolve(CompactBlockRepository.self)
}
/// Tells whether the state represented by these sync ranges evidence some sort of
/// outdated state on the cache or the internal state of the compact block processor.
///
/// - Note: this can mean that the processor has synced over the height that the internal
/// state knows of because the sync process was interrupted before it could reflect
/// it in the internal state storage. This could happen because of many factors, the
/// most feasible being OS shutting down a background process or the user abruptly
/// exiting the app.
/// - Returns: an ``Optional<BlockHeight>`` where Some represents what's the
/// new state the internal state should reflect and indicating that the cache should be cleared
/// as well. `nil` means that no action is required.
func shouldClearBlockCacheAndUpdateInternalState(syncRange: SyncRanges) -> BlockHeight? {
guard syncRange.downloadRange != nil, syncRange.scanRange != nil else { return nil }
guard
let latestScannedHeight = syncRange.latestScannedHeight,
let latestDownloadedHeight = syncRange.latestDownloadedBlockHeight,
latestScannedHeight > latestDownloadedHeight
else { return nil }
return latestScannedHeight
}
}
extension ChecksBeforeSyncAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
// clear any present cached state if needed.
// this checks if there was a sync in progress that was
// interrupted abruptly and cache was not able to be cleared
// properly and internal state set to the appropriate value
if let newLatestDownloadedHeight = shouldClearBlockCacheAndUpdateInternalState(syncRange: await context.syncRanges) {
try await storage.clear()
try await internalSyncProgress.set(newLatestDownloadedHeight, .latestDownloadedBlockHeight)
} else {
try await storage.create()
}
await context.update(state: .fetchUTXO)
return context
}
func stop() async { }
}

View File

@ -10,6 +10,7 @@ import Foundation
final class ClearAlreadyScannedBlocksAction {
let storage: CompactBlockRepository
let transactionRepository: TransactionRepository
init(container: DIContainer) {
storage = container.resolve(CompactBlockRepository.self)
transactionRepository = container.resolve(TransactionRepository.self)

View File

@ -9,6 +9,7 @@ import Foundation
final class ClearCacheAction {
let storage: CompactBlockRepository
init(container: DIContainer) {
storage = container.resolve(CompactBlockRepository.self)
}
@ -19,7 +20,11 @@ extension ClearCacheAction: Action {
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
try await storage.clear()
await context.update(state: .finished)
if await context.prevState == .idle {
await context.update(state: .migrateLegacyCacheDB)
} else {
await context.update(state: .finished)
}
return context
}

View File

@ -0,0 +1,81 @@
//
// ComputeSyncControlDataAction.swift
//
//
// Created by Michal Fousek on 05.05.2023.
//
import Foundation
final class ComputeSyncControlDataAction {
let configProvider: CompactBlockProcessor.ConfigProvider
let downloaderService: BlockDownloaderService
let latestBlocksDataProvider: LatestBlocksDataProvider
let logger: Logger
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
downloaderService = container.resolve(BlockDownloaderService.self)
latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
logger = container.resolve(Logger.self)
}
}
extension ComputeSyncControlDataAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
let config = await configProvider.config
await latestBlocksDataProvider.updateScannedData()
await latestBlocksDataProvider.updateBlockData()
// Here we know:
// - latest scanned height from the DB, if none the wallet's birthday is automatically used
// - first unenhanced height from the DB, could be nil = up to latestScannedHeight nothing to enhance
// - latest downloaded height reported by downloaderService
// - latest block height on the blockchain
// - wallet birthday for the initial scan
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
let latestScannedHeightDB = await latestBlocksDataProvider.latestScannedHeight
let latestScannedHeight = latestScannedHeightDB < config.walletBirthday ? config.walletBirthday : latestScannedHeightDB
let firstUnenhancedHeight = await latestBlocksDataProvider.firstUnenhancedHeight
let enhanceStart: BlockHeight
if let firstUnenhancedHeight {
enhanceStart = min(firstUnenhancedHeight, latestScannedHeight)
} else {
enhanceStart = latestScannedHeight
}
logger.debug("""
Init numbers:
latestBlockHeight [BC]: \(latestBlockHeight)
latestScannedHeight [DB]: \(latestScannedHeight)
firstUnenhancedHeight [DB]: \(firstUnenhancedHeight ?? -1)
enhanceStart: \(enhanceStart)
walletBirthday: \(config.walletBirthday)
""")
let syncControlData = SyncControlData(
latestBlockHeight: latestBlockHeight,
latestScannedHeight: latestScannedHeight,
firstUnenhancedHeight: enhanceStart
)
await context.update(lastDownloadedHeight: latestScannedHeight)
await context.update(syncControlData: syncControlData)
await context.update(totalProgressRange: latestScannedHeight...latestBlockHeight)
// if there is nothing sync just switch to finished state
if latestBlockHeight < latestScannedHeight || latestBlockHeight == latestScannedHeight {
await context.update(state: .finished)
} else {
await context.update(state: .fetchUTXO)
}
return context
}
func stop() async { }
}

View File

@ -1,93 +0,0 @@
//
// ComputeSyncRangesAction.swift
//
//
// Created by Michal Fousek on 05.05.2023.
//
import Foundation
final class ComputeSyncRangesAction {
let configProvider: CompactBlockProcessor.ConfigProvider
let downloaderService: BlockDownloaderService
let internalSyncProgress: InternalSyncProgress
let latestBlocksDataProvider: LatestBlocksDataProvider
let logger: Logger
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
downloaderService = container.resolve(BlockDownloaderService.self)
internalSyncProgress = container.resolve(InternalSyncProgress.self)
latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
logger = container.resolve(Logger.self)
}
/// This method analyses what must be done and computes range that should be used to compute reported progress.
func computeTotalProgressRange(from syncRanges: SyncRanges) -> CompactBlockRange {
guard syncRanges.downloadRange != nil || syncRanges.scanRange != nil else {
// In this case we are sure that no downloading or scanning happens so this returned range won't be even used. And it's easier to return
// this "fake" range than to handle nil.
return 0...0
}
// Thanks to guard above we can be sure that one of these two ranges is not nil.
let lowerBound = syncRanges.scanRange?.lowerBound ?? syncRanges.downloadRange?.lowerBound ?? 0
let upperBound = syncRanges.scanRange?.upperBound ?? syncRanges.downloadRange?.upperBound ?? 0
return lowerBound...upperBound
}
}
extension ComputeSyncRangesAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
// call internalSyncProgress and compute sync ranges and store them in context
// if there is nothing sync just switch to finished state
let config = await configProvider.config
let latestDownloadHeight = try await downloaderService.lastDownloadedBlockHeight()
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadHeight, alias: config.alias)
await latestBlocksDataProvider.updateScannedData()
await latestBlocksDataProvider.updateBlockData()
let nextState = try await internalSyncProgress.computeNextState(
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
walletBirthday: config.walletBirthday
)
switch nextState {
case .finishProcessing:
await context.update(state: .finished)
case .processNewBlocks(let ranges):
let totalProgressRange = computeTotalProgressRange(from: ranges)
await context.update(totalProgressRange: totalProgressRange)
await context.update(syncRanges: ranges)
await context.update(state: .checksBeforeSync)
logger.debug("""
Syncing with ranges:
download: \(ranges.downloadRange?.lowerBound ?? -1)...\(ranges.downloadRange?.upperBound ?? -1)
scan: \(ranges.scanRange?.lowerBound ?? -1)...\(ranges.scanRange?.upperBound ?? -1)
enhance range: \(ranges.enhanceRange?.lowerBound ?? -1)...\(ranges.enhanceRange?.upperBound ?? -1)
fetchUTXO range: \(ranges.fetchUTXORange?.lowerBound ?? -1)...\(ranges.fetchUTXORange?.upperBound ?? -1)
total progress range: \(totalProgressRange.lowerBound)...\(totalProgressRange.upperBound)
""")
case let .wait(latestHeight, latestDownloadHeight):
// Lightwalletd might be syncing
logger.info(
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight) " +
"while latest blockheight is reported at: \(latestHeight)"
)
await context.update(state: .finished)
}
return context
}
func stop() async { }
}

View File

@ -30,16 +30,17 @@ extension DownloadAction: Action {
var removeBlocksCacheWhenFailed: Bool { true }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
guard let downloadRange = await context.syncRanges.downloadRange else {
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
return await update(context: context)
}
let config = await configProvider.config
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
let latestBlockHeight = await context.syncControlData.latestBlockHeight
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
// So the right range for this batch must be computed.
let batchRangeStart = max(downloadRange.lowerBound, lastScannedHeight)
let batchRangeEnd = min(downloadRange.upperBound, batchRangeStart + config.batchSize)
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
guard batchRangeStart <= batchRangeEnd else {
return await update(context: context)
@ -49,7 +50,8 @@ extension DownloadAction: Action {
let downloadLimit = batchRange.upperBound + (2 * config.batchSize)
logger.debug("Starting download with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
try await downloader.setSyncRange(downloadRange, batchSize: config.batchSize)
await downloader.update(latestDownloadedBlockHeight: batchRange.lowerBound)
try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
await downloader.setDownloadLimit(downloadLimit)
await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)

View File

@ -10,24 +10,24 @@ import Foundation
final class EnhanceAction {
let blockEnhancer: BlockEnhancer
let configProvider: CompactBlockProcessor.ConfigProvider
let internalSyncProgress: InternalSyncProgress
let logger: Logger
let transactionRepository: TransactionRepository
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
blockEnhancer = container.resolve(BlockEnhancer.self)
self.configProvider = configProvider
internalSyncProgress = container.resolve(InternalSyncProgress.self)
logger = container.resolve(Logger.self)
transactionRepository = container.resolve(TransactionRepository.self)
}
func decideWhatToDoNext(context: ActionContext, lastScannedHeight: BlockHeight) async -> ActionContext {
guard let scanRange = await context.syncRanges.scanRange else {
guard await context.syncControlData.latestScannedHeight != nil else {
await context.update(state: .clearCache)
return context
}
if lastScannedHeight >= scanRange.upperBound {
let latestBlockHeight = await context.syncControlData.latestBlockHeight
if lastScannedHeight >= latestBlockHeight {
await context.update(state: .clearCache)
} else {
await context.update(state: .download)
@ -43,7 +43,6 @@ extension EnhanceAction: Action {
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
// Use `BlockEnhancer` to enhance blocks.
// This action is executed on each downloaded and scanned batch (typically each 100 blocks). But we want to run enhancement each 1000 blocks.
// This action can use `InternalSyncProgress` and last scanned height to compute when it should do work.
// if latestScannedHeight >= context.scanRanges.scanRange.upperBound then everything is processed and sync process should continue to end.
// If latestScannedHeight < context.scanRanges.scanRange.upperBound then set state to `download` because there are blocks to
@ -52,22 +51,28 @@ extension EnhanceAction: Action {
let config = await configProvider.config
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
guard let range = await context.syncRanges.enhanceRange else {
guard let firstUnenhancedHeight = await context.syncControlData.firstUnenhancedHeight else {
return await decideWhatToDoNext(context: context, lastScannedHeight: lastScannedHeight)
}
let lastEnhancedHeight = try await internalSyncProgress.load(.latestEnhancedHeight)
let enhanceRangeStart = max(range.lowerBound, lastEnhancedHeight)
let enhanceRangeEnd = min(range.upperBound, lastScannedHeight)
let latestBlockHeight = await context.syncControlData.latestBlockHeight
let lastEnhancedHeight: BlockHeight
if let lastEnhancedHeightInContext = await context.lastEnhancedHeight {
lastEnhancedHeight = lastEnhancedHeightInContext
} else {
lastEnhancedHeight = -1
}
let enhanceRangeStart = max(firstUnenhancedHeight, lastEnhancedHeight + 1)
let enhanceRangeEnd = min(latestBlockHeight, lastScannedHeight)
// This may happen:
// For example whole enhance range is 0...2100 Without this force enhance is done for ranges: 0...1000, 1001...2000. And that's it.
// Last 100 blocks isn't enhanced.
//
// This force makes sure that all the blocks are enhanced even when last enhance happened < 1000 blocks ago.
let forceEnhance = enhanceRangeEnd == range.upperBound && enhanceRangeEnd - enhanceRangeStart <= config.enhanceBatchSize
let forceEnhance = enhanceRangeEnd == latestBlockHeight && enhanceRangeEnd - enhanceRangeStart <= config.enhanceBatchSize
if forceEnhance || (enhanceRangeStart <= enhanceRangeEnd && lastScannedHeight - lastEnhancedHeight >= config.enhanceBatchSize) {
if enhanceRangeStart <= enhanceRangeEnd && (forceEnhance || (lastScannedHeight - lastEnhancedHeight >= config.enhanceBatchSize)) {
let enhanceRange = enhanceRangeStart...enhanceRangeEnd
let transactions = try await blockEnhancer.enhance(
at: enhanceRange,
@ -79,6 +84,8 @@ extension EnhanceAction: Action {
}
)
await context.update(lastEnhancedHeight: enhanceRange.upperBound)
if let transactions {
await didUpdate(.foundTransactions(transactions, enhanceRange))
}

View File

@ -21,14 +21,12 @@ extension FetchUTXOsAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
if let range = await context.syncRanges.fetchUTXORange {
logger.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
let result = try await utxoFetcher.fetch(at: range) { fetchProgress in
await didUpdate(.progressPartialUpdate(.fetch(fetchProgress)))
}
await didUpdate(.storedUTXOs(result))
logger.debug("Fetching UTXOs")
let result = try await utxoFetcher.fetch() { fetchProgress in
await didUpdate(.progressPartialUpdate(.fetch(fetchProgress)))
}
await didUpdate(.storedUTXOs(result))
await context.update(state: .handleSaplingParams)
return context
}

View File

@ -9,14 +9,12 @@ import Foundation
final class MigrateLegacyCacheDBAction {
private let configProvider: CompactBlockProcessor.ConfigProvider
private let internalSyncProgress: InternalSyncProgress
private let storage: CompactBlockRepository
private let transactionRepository: TransactionRepository
private let fileManager: ZcashFileManager
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
internalSyncProgress = container.resolve(InternalSyncProgress.self)
storage = container.resolve(CompactBlockRepository.self)
transactionRepository = container.resolve(TransactionRepository.self)
fileManager = container.resolve(ZcashFileManager.self)
@ -43,7 +41,7 @@ extension MigrateLegacyCacheDBAction: Action {
// Instance with alias `default` is same as instance before the Alias was introduced. So it makes sense that only this instance handles
// legacy cache DB. Any instance with different than `default` alias was created after the Alias was introduced and at this point legacy
// cache DB is't anymore. So there is nothing to migrate for instances with not default Alias.
// cache DB doesn't exist anymore. So there is nothing to migrate for instances with not default Alias.
guard config.alias == .default else {
return await updateState(context)
}
@ -65,13 +63,6 @@ extension MigrateLegacyCacheDBAction: Action {
// create the storage
try await self.storage.create()
// The database has been deleted, so we have adjust the internal state of the
// `CompactBlockProcessor` so that it doesn't rely on download heights set
// by a previous processing cycle.
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
try await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
return await updateState(context)
}

View File

@ -30,16 +30,17 @@ extension ScanAction: Action {
var removeBlocksCacheWhenFailed: Bool { true }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
guard let scanRange = await context.syncRanges.scanRange else {
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
return await update(context: context)
}
let config = await configProvider.config
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
let latestBlockHeight = await context.syncControlData.latestBlockHeight
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `scanRange` are scanned.
// So the right range for this batch must be computed.
let batchRangeStart = max(scanRange.lowerBound, lastScannedHeight)
let batchRangeEnd = min(scanRange.upperBound, batchRangeStart + config.batchSize)
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
guard batchRangeStart <= batchRangeEnd else {
return await update(context: context)

View File

@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
}
await context.update(state: .computeSyncRanges)
await context.update(state: .computeSyncControlData)
return context
}

View File

@ -30,7 +30,6 @@ actor CompactBlockProcessor {
private let accountRepository: AccountRepository
let blockDownloaderService: BlockDownloaderService
private let internalSyncProgress: InternalSyncProgress
private let latestBlocksDataProvider: LatestBlocksDataProvider
private let logger: Logger
private let metrics: SDKMetrics
@ -190,7 +189,6 @@ actor CompactBlockProcessor {
self.metrics = container.resolve(SDKMetrics.self)
self.logger = container.resolve(Logger.self)
self.latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
self.internalSyncProgress = container.resolve(InternalSyncProgress.self)
self.blockDownloaderService = container.resolve(BlockDownloaderService.self)
self.service = container.resolve(LightWalletService.self)
self.rustBackend = container.resolve(ZcashRustBackendWelding.self)
@ -216,10 +214,8 @@ actor CompactBlockProcessor {
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
case .validateServer:
action = ValidateServerAction(container: container, configProvider: configProvider)
case .computeSyncRanges:
action = ComputeSyncRangesAction(container: container, configProvider: configProvider)
case .checksBeforeSync:
action = ChecksBeforeSyncAction(container: container)
case .computeSyncControlData:
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
case .download:
action = DownloadAction(container: container, configProvider: configProvider)
case .validate:
@ -313,7 +309,7 @@ extension CompactBlockProcessor {
private func doRewind(context: AfterSyncHooksManager.RewindContext) async throws {
logger.debug("Executing rewind.")
let lastDownloaded = try await internalSyncProgress.latestDownloadedBlockHeight
let lastDownloaded = await latestBlocksDataProvider.latestScannedHeight
let height = Int32(context.height ?? lastDownloaded)
let nearestHeight: Int32
@ -328,6 +324,7 @@ extension CompactBlockProcessor {
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
do {
try await rewindDownloadBlockAction(to: BlockHeight(rewindHeight))
try await rustBackend.rewindToHeight(height: rewindHeight)
} catch {
await failure(error)
@ -342,12 +339,22 @@ extension CompactBlockProcessor {
return await context.completion(.failure(error))
}
try await internalSyncProgress.rewind(to: rewindBlockHeight)
await context.completion(.success(rewindBlockHeight))
}
}
// 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
}
}
}
// MARK: - Wipe
extension CompactBlockProcessor {
@ -369,7 +376,6 @@ extension CompactBlockProcessor {
do {
try await self.storage.clear()
try await internalSyncProgress.rewind(to: 0)
wipeLegacyCacheDbIfNeeded()
@ -378,6 +384,8 @@ extension CompactBlockProcessor {
try fileManager.removeItem(at: config.dataDb)
}
try await rewindDownloadBlockAction(to: nil)
await context.completion(nil)
} catch {
await context.completion(error)
@ -468,7 +476,7 @@ extension CompactBlockProcessor {
// 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.
await context.update(state: .migrateLegacyCacheDB)
await context.update(state: .clearCache)
await syncStarted()
if backoffTimer == nil {
@ -579,9 +587,7 @@ extension CompactBlockProcessor {
break
case .validateServer:
break
case .computeSyncRanges:
break
case .checksBeforeSync:
case .computeSyncControlData:
break
case .download:
break
@ -609,7 +615,9 @@ extension CompactBlockProcessor {
}
private func resetContext() async {
let lastEnhancedheight = await context.lastEnhancedHeight
context = ActionContext(state: .idle)
await context.update(lastEnhancedHeight: lastEnhancedheight)
await compactBlockProgress.reset()
}
@ -621,7 +629,7 @@ extension CompactBlockProcessor {
private func syncFinished() async -> Bool {
logger.debug("Sync finished")
let latestBlockHeightWhenSyncing = await context.syncRanges.latestBlockHeight
let latestBlockHeightWhenSyncing = await context.syncControlData.latestBlockHeight
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
// If `latestBlockHeightWhenSyncing` is 0 then it means that there was nothing to sync in last sync process.
let newerBlocksWereMinedDuringSync =
@ -659,7 +667,8 @@ extension CompactBlockProcessor {
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
try await blockDownloaderService.rewind(to: rewindHeight)
try await internalSyncProgress.rewind(to: rewindHeight)
try await rewindDownloadBlockAction(to: rewindHeight)
await send(event: .handledReorg(height, rewindHeight))
}
@ -758,20 +767,7 @@ extension CompactBlockProcessor {
private func ifTaskIsNotCanceledClearCompactBlockCache() async {
guard !Task.isCancelled else { return }
let lastScannedHeight = await latestBlocksDataProvider.latestScannedHeight
do {
// Blocks download work in parallel with scanning. So imagine this scenario:
//
// Scanning is done until height 10300. Blocks are downloaded until height 10400.
// And now validation fails and this method is called. And `.latestDownloadedBlockHeight` in `internalSyncProgress` is set to 10400. And
// all the downloaded blocks are removed here.
//
// If this line doesn't happen then when sync starts next time it thinks that all the blocks are downloaded until 10400. But all were
// removed. So blocks between 10300 and 10400 wouldn't ever be scanned.
//
// Scanning is done until 10300 so the SDK can be sure that blocks with height below 10300 are not required. So it makes sense to set
// `.latestDownloadedBlockHeight` to `lastScannedHeight`. And sync will work fine in next run.
try await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
try await clearCompactBlockCache()
} catch {
logger.error("`clearCompactBlockCache` failed after error: \(error)")

View File

@ -52,6 +52,14 @@ protocol BlockDownloader {
/// called before then nothing is downloaded.
/// - Parameter range: Wait until blocks from `range` are downloaded.
func waitUntilRequestedBlocksAreDownloaded(in range: CompactBlockRange) async throws
/// Updates the internal in memory value of latest downloaded block height. This way the `BlockDownloader` works with the current latest height and can
/// continue on parallel downloading of next batch.
func update(latestDownloadedBlockHeight: BlockHeight) async
/// Provides the value of latest downloaded height.
func latestDownloadedBlockHeight() async -> BlockHeight
/// In case rewind is needed, the latestDownloadedBlockHeight is rewritten forcefully.
func rewind(latestDownloadedBlockHeight: BlockHeight?) async
}
actor BlockDownloaderImpl {
@ -62,9 +70,9 @@ actor BlockDownloaderImpl {
let service: LightWalletService
let downloaderService: BlockDownloaderService
let storage: CompactBlockRepository
let internalSyncProgress: InternalSyncProgress
let metrics: SDKMetrics
let logger: Logger
var latestDownloadedBlockHeight: BlockHeight = -1
private var downloadStreamCreatedAtRange: CompactBlockRange = 0...0
private var downloadStream: BlockDownloaderStream?
@ -80,14 +88,12 @@ actor BlockDownloaderImpl {
service: LightWalletService,
downloaderService: BlockDownloaderService,
storage: CompactBlockRepository,
internalSyncProgress: InternalSyncProgress,
metrics: SDKMetrics,
logger: Logger
) {
self.service = service
self.downloaderService = downloaderService
self.storage = storage
self.internalSyncProgress = internalSyncProgress
self.metrics = metrics
self.logger = logger
}
@ -100,8 +106,6 @@ actor BlockDownloaderImpl {
throw ZcashError.blockDownloadSyncRangeNotSet
}
let latestDownloadedBlockHeight = try await internalSyncProgress.latestDownloadedBlockHeight
let downloadFrom = max(syncRange.lowerBound, latestDownloadedBlockHeight + 1)
let downloadTo = min(downloadToHeight, syncRange.upperBound)
@ -240,11 +244,25 @@ actor BlockDownloaderImpl {
private func blocksBufferWritten(_ buffer: [ZcashCompactBlock]) async throws {
guard let lastBlock = buffer.last else { return }
try await internalSyncProgress.set(lastBlock.height, .latestDownloadedBlockHeight)
latestDownloadedBlockHeight = lastBlock.height
}
}
extension BlockDownloaderImpl: BlockDownloader {
func rewind(latestDownloadedBlockHeight: BlockHeight?) async {
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight ?? -1
}
func update(latestDownloadedBlockHeight: BlockHeight) async {
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight {
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight
}
}
func latestDownloadedBlockHeight() async -> BlockHeight {
latestDownloadedBlockHeight
}
func setDownloadLimit(_ limit: BlockHeight) async {
downloadToHeight = limit
}
@ -283,13 +301,13 @@ extension BlockDownloaderImpl: BlockDownloader {
func waitUntilRequestedBlocksAreDownloaded(in range: CompactBlockRange) async throws {
logger.debug("Waiting until requested blocks are downloaded at \(range)")
var latestDownloadedBlock = try await internalSyncProgress.latestDownloadedBlockHeight
var latestDownloadedBlock = latestDownloadedBlockHeight
while latestDownloadedBlock < range.upperBound {
if let error = lastError {
throw error
}
try await Task.sleep(milliseconds: 10)
latestDownloadedBlock = try await internalSyncProgress.latestDownloadedBlockHeight
latestDownloadedBlock = latestDownloadedBlockHeight
}
logger.debug("Waiting done. Blocks are downloaded at \(range)")
}

View File

@ -57,7 +57,6 @@ protocol BlockEnhancer {
struct BlockEnhancerImpl {
let blockDownloaderService: BlockDownloaderService
let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
@ -96,7 +95,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
let transactions = try await transactionRepository.find(in: range, limit: Int.max, kind: .all)
guard !transactions.isEmpty else {
try await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
return nil
}
@ -126,10 +124,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
)
await didEnhance(progress)
if let minedHeight = confirmedTx.minedHeight {
try await internalSyncProgress.set(minedHeight, .latestEnhancedHeight)
}
} catch {
retries += 1
logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
@ -155,8 +149,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
logger.error("error enhancing transactions! \(error)")
throw error
}
try await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
if Task.isCancelled {
logger.debug("Warning: compactBlockEnhancement on range \(range) cancelled")

View File

@ -18,7 +18,6 @@ struct UTXOFetcherConfig {
protocol UTXOFetcher {
func fetch(
at range: CompactBlockRange,
didFetch: @escaping (Float) async -> Void
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
}
@ -27,7 +26,6 @@ struct UTXOFetcherImpl {
let accountRepository: AccountRepository
let blockDownloaderService: BlockDownloaderService
let config: UTXOFetcherConfig
let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding
let metrics: SDKMetrics
let logger: Logger
@ -35,7 +33,6 @@ struct UTXOFetcherImpl {
extension UTXOFetcherImpl: UTXOFetcher {
func fetch(
at range: CompactBlockRange,
didFetch: @escaping (Float) async -> Void
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
try Task.checkCancellation()
@ -82,7 +79,6 @@ extension UTXOFetcherImpl: UTXOFetcher {
counter += 1
await didFetch(counter / all)
try await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight)
} catch {
logger.error("failed to put utxo - error: \(error)")
skipped.append(utxo)
@ -91,22 +87,20 @@ extension UTXOFetcherImpl: UTXOFetcher {
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: range.lowerBound,
targetHeight: range.upperBound,
progressHeight: range.upperBound
startHeight: 0,
targetHeight: 1,
progressHeight: 1
),
start: startTime,
end: Date(),
batchSize: range.count,
batchSize: 1,
operation: .fetchUTXOs
)
let result = (inserted: refreshed, skipped: skipped)
try await internalSyncProgress.set(range.upperBound, .latestUTXOFetchedHeight)
if Task.isCancelled {
logger.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
logger.debug("Warning: fetchUnspentTxOutputs cancelled")
}
return result

View File

@ -1,223 +0,0 @@
//
// InternalSyncProgress.swift
//
//
// Created by Michal Fousek on 23.11.2022.
//
import Foundation
struct SyncRanges: Equatable {
let latestBlockHeight: BlockHeight
// Range of blocks that are not yet downloaded
let downloadRange: CompactBlockRange?
/// Range of blocks that are not yet scanned.
let scanRange: CompactBlockRange?
/// Range of blocks that are not enhanced yet.
let enhanceRange: CompactBlockRange?
/// Range of blocks for which no UTXOs are fetched yet.
let fetchUTXORange: CompactBlockRange?
let latestScannedHeight: BlockHeight?
let latestDownloadedBlockHeight: BlockHeight?
static var empty: SyncRanges {
SyncRanges(
latestBlockHeight: 0,
downloadRange: nil,
scanRange: nil,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
)
}
}
enum NextState: Equatable {
case finishProcessing(height: BlockHeight)
case processNewBlocks(ranges: SyncRanges)
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
}
protocol InternalSyncProgressStorage {
func initialize() async throws
func bool(for key: String) async throws -> Bool
func integer(for key: String) async throws -> Int
func set(_ value: Int, for key: String) async throws
// sourcery: mockedName="setBool"
func set(_ value: Bool, for key: String) async throws
}
actor InternalSyncProgress {
enum Key: String, CaseIterable {
case latestDownloadedBlockHeight
case latestEnhancedHeight
case latestUTXOFetchedHeight
func with(_ alias: ZcashSynchronizerAlias) -> String {
switch alias {
case .`default`:
return self.rawValue
case let .custom(rawAlias):
return "\(self.rawValue)_\(rawAlias)"
}
}
}
private let alias: ZcashSynchronizerAlias
private let storage: InternalSyncProgressStorage
let logger: Logger
var latestDownloadedBlockHeight: BlockHeight {
get async throws { try await load(.latestDownloadedBlockHeight) }
}
var latestEnhancedHeight: BlockHeight {
get async throws { try await load(.latestEnhancedHeight) }
}
var latestUTXOFetchedHeight: BlockHeight {
get async throws { try await load(.latestUTXOFetchedHeight) }
}
init(alias: ZcashSynchronizerAlias, storage: InternalSyncProgressStorage, logger: Logger) {
self.alias = alias
self.storage = storage
self.logger = logger
}
func initialize() async throws {
try await storage.initialize()
}
func load(_ key: Key) async throws -> BlockHeight {
return try await storage.integer(for: key.with(alias))
}
func set(_ value: BlockHeight, _ key: Key) async throws {
try await storage.set(value, for: key.with(alias))
}
func rewind(to: BlockHeight) async throws {
for key in Key.allCases {
let finalRewindHeight = min(try await load(key), to)
try await self.set(finalRewindHeight, key)
}
}
/// `InternalSyncProgress` is from now on used to track which block were already downloaded. Previous versions of the SDK were using cache DB to
/// track this. Because of this we have to migrate height of latest downloaded block from cache DB to here.
///
/// - Parameter latestDownloadedBlockHeight: Height of latest downloaded block from cache DB.
func migrateIfNeeded(
latestDownloadedBlockHeightFromCacheDB latestDownloadedBlockHeight: BlockHeight,
alias: ZcashSynchronizerAlias
) async throws {
// If no latest downloaded height is stored in storage store there latest downloaded height from blocks storage. If there are no blocks
// downloaded then it will be 0 anyway. If there are blocks downloaded real height is stored.
if try await storage.integer(for: Key.latestDownloadedBlockHeight.with(alias)) == 0 {
try await set(latestDownloadedBlockHeight, .latestDownloadedBlockHeight)
}
for key in Key.allCases {
let finalKey = key.with(alias)
let value = UserDefaults.standard.integer(forKey: finalKey)
if value > 0 {
try await storage.set(value, for: finalKey)
}
UserDefaults.standard.set(0, forKey: finalKey)
}
}
/// Computes the next state for the sync process. Thanks to this it's possible to interrupt the sync process at any phase and then it can be safely
/// resumed.
///
/// The sync process has 4 phases (download, scan, enhance, fetch UTXO). `InternalSyncProgress` tracks independently which blocks were already
/// processed in each phase. To compute the next state these 4 numbers are compared with `latestBlockHeight`.
///
/// - If any of these numbers are larger than `latestBlockHeight` then `wait` is used as the next state. We have locally higher block heights than
/// are currently available at LightWalletd.
/// - If any of these numbers are lower than `latestBlockHeight` then `processNewBlocks` is used as the next state. The sync process should run.
/// - Otherwise `finishProcessing` is used as the next state. It means that local data are synced with what is available at LightWalletd.
///
/// - Parameters:
/// - latestBlockHeight: Latest height fetched from LightWalletd API.
/// - latestScannedHeight: Latest height of latest block scanned.
/// - walletBirthday: Wallet birthday.
/// - Returns: Computed state.
func computeNextState(
latestBlockHeight: BlockHeight,
latestScannedHeight: BlockHeight,
walletBirthday: BlockHeight
) async throws -> NextState {
let latestDownloadedBlockHeight = try await self.latestDownloadedBlockHeight
let latestEnhancedHeight = try await self.latestEnhancedHeight
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
logger.debug("""
Init numbers:
latestBlockHeight: \(latestBlockHeight)
latestDownloadedHeight: \(latestDownloadedBlockHeight)
latestScannedHeight: \(latestScannedHeight)
latestEnhancedHeight: \(latestEnhancedHeight)
latestUTXOFetchedHeight: \(latestUTXOFetchedHeight)
""")
if latestDownloadedBlockHeight > latestBlockHeight ||
latestScannedHeight > latestBlockHeight ||
latestEnhancedHeight > latestBlockHeight ||
latestUTXOFetchedHeight > latestBlockHeight {
return .wait(latestHeight: latestBlockHeight, latestDownloadHeight: latestDownloadedBlockHeight)
} else if latestDownloadedBlockHeight < latestBlockHeight ||
latestScannedHeight < latestBlockHeight ||
latestEnhancedHeight < latestEnhancedHeight ||
latestUTXOFetchedHeight < latestBlockHeight {
let ranges = try await computeSyncRanges(
birthday: walletBirthday,
latestBlockHeight: latestBlockHeight,
latestScannedHeight: latestScannedHeight
)
return .processNewBlocks(ranges: ranges)
} else {
return .finishProcessing(height: latestBlockHeight)
}
}
func computeSyncRanges(
birthday: BlockHeight,
latestBlockHeight: BlockHeight,
latestScannedHeight: BlockHeight
) async throws -> SyncRanges {
let latestDownloadedBlockHeight = try await self.latestDownloadedBlockHeight
let latestEnhancedHeight = try await self.latestEnhancedHeight
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
if latestScannedHeight > latestDownloadedBlockHeight {
logger.warn("""
InternalSyncProgress found inconsistent state.
latestBlockHeight: \(latestBlockHeight)
--> latestDownloadedHeight: \(latestDownloadedBlockHeight)
latestScannedHeight: \(latestScannedHeight)
latestEnhancedHeight: \(latestEnhancedHeight)
latestUTXOFetchedHeight: \(latestUTXOFetchedHeight)
""")
}
let downloadRange = computeRange(latestHeight: latestDownloadedBlockHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
let scanRange = computeRange(latestHeight: latestScannedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
return SyncRanges(
latestBlockHeight: latestBlockHeight,
downloadRange: downloadRange,
scanRange: scanRange,
enhanceRange: computeRange(latestHeight: latestEnhancedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
fetchUTXORange: computeRange(latestHeight: latestUTXOFetchedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
latestScannedHeight: latestScannedHeight,
latestDownloadedBlockHeight: latestDownloadedBlockHeight
)
}
private func computeRange(latestHeight: BlockHeight, birthday: BlockHeight, latestBlockHeight: BlockHeight) -> CompactBlockRange? {
guard latestHeight < latestBlockHeight else { return nil }
let lowerBound = latestHeight <= birthday ? birthday : latestHeight + 1
return lowerBound...latestBlockHeight
}
}

View File

@ -1,97 +0,0 @@
//
// InternalSyncProgressDiskStorage.swift
//
//
// Created by Michal Fousek on 21.05.2023.
//
import Foundation
actor InternalSyncProgressDiskStorage {
private let storageURL: URL
private let logger: Logger
init(storageURL: URL, logger: Logger) {
self.storageURL = storageURL
self.logger = logger
}
private func fileURL(for key: String) -> URL {
return storageURL.appendingPathComponent(key)
}
}
extension InternalSyncProgressDiskStorage: InternalSyncProgressStorage {
/// - If object on the file system at `generalStorageURL` exists and it is directory then do nothing.
/// - If object on the file system at `generalStorageURL` exists and it is file then throw error. Because `generalStorageURL` should be directory.
/// - If object on the file system at `generalStorageURL` doesn't exists then create directory at this URL. And set `isExcludedFromBackup` URL
/// flag to prevent backup of the progress information to system backup.
func initialize() async throws {
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
let exists = fileManager.fileExists(atPath: storageURL.pathExtension, isDirectory: &isDirectory)
if exists && !isDirectory.boolValue {
throw ZcashError.initializerGeneralStorageExistsButIsFile(storageURL)
} else if !exists {
do {
try fileManager.createDirectory(at: storageURL, withIntermediateDirectories: true)
} catch {
throw ZcashError.initializerGeneralStorageCantCreate(storageURL, error)
}
do {
// Prevent from backing up progress information to system backup.
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
var generalStorageURL = storageURL
try generalStorageURL.setResourceValues(resourceValues)
} catch {
throw ZcashError.initializerCantSetNoBackupFlagToGeneralStorageURL(storageURL, error)
}
}
}
func bool(for key: String) async throws -> Bool {
let fileURL = self.fileURL(for: key)
do {
return try Data(contentsOf: fileURL).toBool()
} catch {
if !FileManager.default.fileExists(atPath: fileURL.path) {
return false
} else {
throw ZcashError.ispStorageCantLoad(fileURL, error)
}
}
}
func integer(for key: String) async throws -> Int {
let fileURL = self.fileURL(for: key)
do {
return try Data(contentsOf: fileURL).toInt()
} catch {
if !FileManager.default.fileExists(atPath: fileURL.path) {
return 0
} else {
throw ZcashError.ispStorageCantLoad(fileURL, error)
}
}
}
func set(_ value: Int, for key: String) async throws {
let fileURL = self.fileURL(for: key)
do {
try value.toData().write(to: fileURL, options: [.atomic])
} catch {
throw ZcashError.ispStorageCantWrite(fileURL, error)
}
}
func set(_ value: Bool, for key: String) async throws {
let fileURL = self.fileURL(for: key)
do {
try value.toData().write(to: fileURL, options: [.atomic])
} catch {
throw ZcashError.ispStorageCantWrite(fileURL, error)
}
}
}

View File

@ -0,0 +1,25 @@
//
// SyncControlData.swift
//
//
// Created by Michal Fousek on 23.11.2022.
//
import Foundation
struct SyncControlData: Equatable {
/// The tip of the blockchain
let latestBlockHeight: BlockHeight
/// The last height that has been scanned
let latestScannedHeight: BlockHeight?
/// The height from the enhancement must start
let firstUnenhancedHeight: BlockHeight?
static var empty: SyncControlData {
SyncControlData(
latestBlockHeight: 0,
latestScannedHeight: nil,
firstUnenhancedHeight: nil
)
}
}

View File

@ -34,15 +34,74 @@ struct Block: Codable {
let hash: Data
let time: Int
let saplingTree: Data
static let table = Table("blocks")
}
struct VTransaction: Codable {
enum CodingKeys: String, CodingKey {
case accountId = "account_id"
case idTx = "id_tx"
case minedHeight = "mined_height"
case txIndex = "tx_index"
case txId = "txid"
case expiryHeight = "expiry_height"
case raw = "raw"
case accountBalanceDelta = "account_balance_delta"
case feePaid = "fee_paid"
case expiredUnmined = "expired_unmined"
case hasChange = "has_change"
case sentNoteCount = "sent_note_count"
case recievedNoteCount = "received_note_count"
case memoCount = "memo_count"
case blockTime = "block_time"
}
enum TableStructure {
static let accountId = Expression<Int>(VTransaction.CodingKeys.accountId.rawValue)
static let idTx = Expression<Int>(VTransaction.CodingKeys.idTx.rawValue)
static let minedHeight = Expression<Int>(VTransaction.CodingKeys.minedHeight.rawValue)
static let txIndex = Expression<Int>(VTransaction.CodingKeys.txIndex.rawValue)
static let txId = Expression<Data>(VTransaction.CodingKeys.txId.rawValue)
static let expiryHeight = Expression<Int?>(VTransaction.CodingKeys.expiryHeight.rawValue)
static let raw = Expression<Data?>(VTransaction.CodingKeys.raw.rawValue)
static let accountBalanceDelta = Expression<Int>(VTransaction.CodingKeys.accountBalanceDelta.rawValue)
static let feePaid = Expression<Int?>(VTransaction.CodingKeys.feePaid.rawValue)
static let expiredUnmined = Expression<Int>(VTransaction.CodingKeys.expiredUnmined.rawValue)
static let hasChange = Expression<Bool>(VTransaction.CodingKeys.hasChange.rawValue)
static let sentNoteCount = Expression<Int>(VTransaction.CodingKeys.sentNoteCount.rawValue)
static let recievedNoteCount = Expression<Int>(VTransaction.CodingKeys.recievedNoteCount.rawValue)
static let memoCount = Expression<Int>(VTransaction.CodingKeys.memoCount.rawValue)
static let blockTime = Expression<Int>(VTransaction.CodingKeys.blockTime.rawValue)
}
let accountId: Int
let idTx: Int
let minedHeight: Int
let txIndex: Int
let txId: Data
let expiryHeight: Int?
let raw: Data?
let accountBalanceDelta: Int
let feePaid: Int?
let expiredUnmined: Int
let hasChange: Bool
let sentNoteCount: Int
let recievedNoteCount: Int
let memoCount: Int
let blockTime: Int
static let table = Table("v_transactions")
}
class BlockSQLDAO: BlockDao {
let dbProvider: ConnectionProvider
let table: Table
let height = Expression<Int>("height")
let minedHeight = Expression<Int>("mined_height")
let raw = Expression<Data?>("raw")
init(dbProvider: ConnectionProvider) {
self.dbProvider = dbProvider
self.table = Table("Blocks")
@ -103,6 +162,30 @@ class BlockSQLDAO: BlockDao {
}
}
}
func firstUnenhancedHeight(in range: CompactBlockRange? = nil) throws -> BlockHeight? {
do {
return try dbProvider
.connection()
.prepare(
VTransaction.table
.order(minedHeight.asc)
.filter(raw == nil)
.limit(1)
)
.map {
do {
let vTransaction: VTransaction = try $0.decode()
return vTransaction.minedHeight
} catch {
throw ZcashError.blockDAOFirstUnenhancedCantDecode(error)
}
}
.first
} catch {
throw ZcashError.blockDAOFirstUnenhancedHeight(error)
}
}
}
extension BlockSQLDAO: BlockRepository {

View File

@ -49,6 +49,10 @@ class TransactionSQLDAO: TransactionRepository {
try blockDao.latestBlock()
}
func firstUnenhancedHeight() throws -> BlockHeight? {
try blockDao.firstUnenhancedHeight()
}
func isInitialized() async throws -> Bool {
true
}

View File

@ -94,10 +94,18 @@ public enum ZcashError: Equatable, Error {
/// - `sqliteError` is error produced by SQLite library.
/// ZBDAO0004
case blockDAOLatestBlock(_ sqliteError: Error)
/// Fetched latesxt block information from DB but can't decode them.
/// Fetched latest block information from DB but can't decode them.
/// - `error` is decoding error.
/// ZBDAO0005
case blockDAOLatestBlockCantDecode(_ error: Error)
/// SQLite query failed when fetching the first unenhanced block from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZBDAO0006
case blockDAOFirstUnenhancedHeight(_ sqliteError: Error)
/// Fetched unenhanced block information from DB but can't decode them.
/// - `error` is decoding error.
/// ZBDAO0007
case blockDAOFirstUnenhancedCantDecode(_ error: Error)
/// Error from rust layer when calling ZcashRustBackend.createAccount
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0001
@ -501,6 +509,9 @@ public enum ZcashError: Equatable, Error {
/// Consensus BranchIDs don't match this is probably an API or programming error.
/// ZCBPEO0017
case compactBlockProcessorConsensusBranchID
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
/// ZCBPEO0018
case compactBlockProcessorDownloadBlockActionRewind
/// The synchronizer is unprepared.
/// ZSYNCO0001
case synchronizerNotPrepared
@ -519,12 +530,6 @@ public enum ZcashError: Equatable, Error {
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
/// ZSYNCO0006
case synchronizerDisconnected
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
/// ZISPDS0001
case ispStorageCantLoad(_ fileURL: URL, _ error: Error)
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
/// ZISPDS0002
case ispStorageCantWrite(_ fileURL: URL, _ error: Error)
public var message: String {
switch self {
@ -552,7 +557,9 @@ public enum ZcashError: Equatable, Error {
case .blockDAOCantDecode: return "Fetched block information from DB but can't decode them."
case .blockDAOLatestBlockHeight: return "SQLite query failed when fetching height of the latest block from the database."
case .blockDAOLatestBlock: return "SQLite query failed when fetching the latest block from the database."
case .blockDAOLatestBlockCantDecode: return "Fetched latesxt block information from DB but can't decode them."
case .blockDAOLatestBlockCantDecode: return "Fetched latest block information from DB but can't decode them."
case .blockDAOFirstUnenhancedHeight: return "SQLite query failed when fetching the first unenhanced block from the database."
case .blockDAOFirstUnenhancedCantDecode: return "Fetched unenhanced block information from DB but can't decode them."
case .rustCreateAccount: return "Error from rust layer when calling ZcashRustBackend.createAccount"
case .rustCreateToAddress: return "Error from rust layer when calling ZcashRustBackend.createToAddress"
case .rustDecryptAndStoreTransaction: return "Error from rust layer when calling ZcashRustBackend.decryptAndStoreTransaction"
@ -672,14 +679,13 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb: return "Deletion of readable file at the provided URL failed."
case .compactBlockProcessorChainName: return "Chain name does not match. Expected either 'test' or 'main'. This is probably an API or programming error."
case .compactBlockProcessorConsensusBranchID: return "Consensus BranchIDs don't match this is probably an API or programming error."
case .compactBlockProcessorDownloadBlockActionRewind: return "Rewind of DownloadBlockAction failed as no action is possible to unwrapp."
case .synchronizerNotPrepared: return "The synchronizer is unprepared."
case .synchronizerSendMemoToTransparentAddress: return "Memos can't be sent to transparent addresses."
case .synchronizerShieldFundsInsuficientTransparentFunds: return "There is not enough transparent funds to cover fee for the shielding."
case .synchronizerLatestUTXOsInvalidTAddress: return "LatestUTXOs for the address failed, invalid t-address."
case .synchronizerRewindUnknownArchorHeight: return "Rewind failed, unknown archor height"
case .synchronizerDisconnected: return "Indicates that this Synchronizer is disconnected from its lightwalletd server."
case .ispStorageCantLoad: return "`InternalSyncProgressDiskStorage` can't read data from specific file."
case .ispStorageCantWrite: return "`InternalSyncProgressDiskStorage` can't write data from specific file."
}
}
@ -710,6 +716,8 @@ public enum ZcashError: Equatable, Error {
case .blockDAOLatestBlockHeight: return .blockDAOLatestBlockHeight
case .blockDAOLatestBlock: return .blockDAOLatestBlock
case .blockDAOLatestBlockCantDecode: return .blockDAOLatestBlockCantDecode
case .blockDAOFirstUnenhancedHeight: return .blockDAOFirstUnenhancedHeight
case .blockDAOFirstUnenhancedCantDecode: return .blockDAOFirstUnenhancedCantDecode
case .rustCreateAccount: return .rustCreateAccount
case .rustCreateToAddress: return .rustCreateToAddress
case .rustDecryptAndStoreTransaction: return .rustDecryptAndStoreTransaction
@ -829,14 +837,13 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb: return .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb
case .compactBlockProcessorChainName: return .compactBlockProcessorChainName
case .compactBlockProcessorConsensusBranchID: return .compactBlockProcessorConsensusBranchID
case .compactBlockProcessorDownloadBlockActionRewind: return .compactBlockProcessorDownloadBlockActionRewind
case .synchronizerNotPrepared: return .synchronizerNotPrepared
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds
case .synchronizerLatestUTXOsInvalidTAddress: return .synchronizerLatestUTXOsInvalidTAddress
case .synchronizerRewindUnknownArchorHeight: return .synchronizerRewindUnknownArchorHeight
case .synchronizerDisconnected: return .synchronizerDisconnected
case .ispStorageCantLoad: return .ispStorageCantLoad
case .ispStorageCantWrite: return .ispStorageCantWrite
}
}

View File

@ -57,8 +57,12 @@ public enum ZcashErrorCode: String {
case blockDAOLatestBlockHeight = "ZBDAO0003"
/// SQLite query failed when fetching the latest block from the database.
case blockDAOLatestBlock = "ZBDAO0004"
/// Fetched latesxt block information from DB but can't decode them.
/// Fetched latest block information from DB but can't decode them.
case blockDAOLatestBlockCantDecode = "ZBDAO0005"
/// SQLite query failed when fetching the first unenhanced block from the database.
case blockDAOFirstUnenhancedHeight = "ZBDAO0006"
/// Fetched unenhanced block information from DB but can't decode them.
case blockDAOFirstUnenhancedCantDecode = "ZBDAO0007"
/// Error from rust layer when calling ZcashRustBackend.createAccount
case rustCreateAccount = "ZRUST0001"
/// Error from rust layer when calling ZcashRustBackend.createToAddress
@ -297,6 +301,8 @@ public enum ZcashErrorCode: String {
case compactBlockProcessorChainName = "ZCBPEO0016"
/// Consensus BranchIDs don't match this is probably an API or programming error.
case compactBlockProcessorConsensusBranchID = "ZCBPEO0017"
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
case compactBlockProcessorDownloadBlockActionRewind = "ZCBPEO0018"
/// The synchronizer is unprepared.
case synchronizerNotPrepared = "ZSYNCO0001"
/// Memos can't be sent to transparent addresses.
@ -309,8 +315,4 @@ public enum ZcashErrorCode: String {
case synchronizerRewindUnknownArchorHeight = "ZSYNCO0005"
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
case synchronizerDisconnected = "ZSYNCO0006"
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
case ispStorageCantLoad = "ZISPDS0001"
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
case ispStorageCantWrite = "ZISPDS0002"
}

View File

@ -122,10 +122,18 @@ enum ZcashErrorDefinition {
/// - `sqliteError` is error produced by SQLite library.
// sourcery: code="ZBDAO0004"
case blockDAOLatestBlock(_ sqliteError: Error)
/// Fetched latesxt block information from DB but can't decode them.
/// Fetched latest block information from DB but can't decode them.
/// - `error` is decoding error.
// sourcery: code="ZBDAO0005"
case blockDAOLatestBlockCantDecode(_ error: Error)
/// SQLite query failed when fetching the first unenhanced block from the database.
/// - `sqliteError` is error produced by SQLite library.
// sourcery: code="ZBDAO0006"
case blockDAOFirstUnenhancedHeight(_ sqliteError: Error)
/// Fetched unenhanced block information from DB but can't decode them.
/// - `error` is decoding error.
// sourcery: code="ZBDAO0007"
case blockDAOFirstUnenhancedCantDecode(_ error: Error)
// MARK: - Rust
@ -581,7 +589,10 @@ enum ZcashErrorDefinition {
/// Consensus BranchIDs don't match this is probably an API or programming error.
// sourcery: code="ZCBPEO0017"
case compactBlockProcessorConsensusBranchID
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
// sourcery: code="ZCBPEO0018"
case compactBlockProcessorDownloadBlockActionRewind
// MARK: - SDKSynchronizer
/// The synchronizer is unprepared.
@ -602,13 +613,4 @@ enum ZcashErrorDefinition {
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
// sourcery: code="ZSYNCO0006"
case synchronizerDisconnected
// MARK: - InternalSyncProgressDiskStorage
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
// sourcery: code="ZISPDS0001"
case ispStorageCantLoad(_ fileURL: URL, _ error: Error)
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
// sourcery: code="ZISPDS0002"
case ispStorageCantWrite(_ fileURL: URL, _ error: Error)
}

View File

@ -12,6 +12,7 @@ protocol LatestBlocksDataProvider {
var latestScannedTime: TimeInterval { get async }
var latestBlockHeight: BlockHeight { get async }
var walletBirthday: BlockHeight { get async }
var firstUnenhancedHeight: BlockHeight? { get async }
func updateScannedData() async
func updateBlockData() async
@ -27,6 +28,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
// Valid values are stored here after Synchronizer's `prepare` is called.
private(set) var latestScannedHeight: BlockHeight = .zero
private(set) var latestScannedTime: TimeInterval = 0.0
private(set) var firstUnenhancedHeight: BlockHeight?
// Valid value is stored here after block processor's `nextState` is called.
private(set) var latestBlockHeight: BlockHeight = .zero
// Valid values are stored here after Synchronizer's `prepare` is called.
@ -54,7 +56,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
latestBlockHeight = newLatestBlockHeight
}
}
func updateWalletBirthday(_ walletBirthday: BlockHeight) async {
self.walletBirthday = walletBirthday
}

View File

@ -14,6 +14,7 @@ protocol TransactionRepository {
func blockForHeight(_ height: BlockHeight) async throws -> Block?
func lastScannedHeight() async throws -> BlockHeight
func lastScannedBlock() async throws -> Block?
func firstUnenhancedHeight() throws -> BlockHeight?
func isInitialized() async throws -> Bool
func find(id: Int) async throws -> ZcashTransaction.Overview
func find(rawID: Data) async throws -> ZcashTransaction.Overview

View File

@ -88,12 +88,6 @@ enum Dependencies {
container.register(type: SyncSessionIDGenerator.self, isSingleton: false) { _ in
UniqueSyncSessionIDGenerator()
}
container.register(type: InternalSyncProgress.self, isSingleton: true) { di in
let logger = di.resolve(Logger.self)
let storage = InternalSyncProgressDiskStorage(storageURL: urls.generalStorageURL, logger: logger)
return InternalSyncProgress(alias: alias, storage: storage, logger: logger)
}
container.register(type: ZcashFileManager.self, isSingleton: true) { _ in
FileManager.default
@ -109,7 +103,6 @@ enum Dependencies {
let service = di.resolve(LightWalletService.self)
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
let storage = di.resolve(CompactBlockRepository.self)
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
let metrics = di.resolve(SDKMetrics.self)
let logger = di.resolve(Logger.self)
@ -117,7 +110,6 @@ enum Dependencies {
service: service,
downloaderService: blockDownloaderService,
storage: storage,
internalSyncProgress: internalSyncProgress,
metrics: metrics,
logger: logger
)
@ -159,7 +151,6 @@ enum Dependencies {
container.register(type: BlockEnhancer.self, isSingleton: true) { di in
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
let transactionRepository = di.resolve(TransactionRepository.self)
let metrics = di.resolve(SDKMetrics.self)
@ -167,7 +158,6 @@ enum Dependencies {
return BlockEnhancerImpl(
blockDownloaderService: blockDownloaderService,
internalSyncProgress: internalSyncProgress,
rustBackend: rustBackend,
transactionRepository: transactionRepository,
metrics: metrics,
@ -178,7 +168,6 @@ enum Dependencies {
container.register(type: UTXOFetcher.self, isSingleton: true) { di in
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
let utxoFetcherConfig = UTXOFetcherConfig(walletBirthdayProvider: config.walletBirthdayProvider)
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
let metrics = di.resolve(SDKMetrics.self)
let logger = di.resolve(Logger.self)
@ -187,7 +176,6 @@ enum Dependencies {
accountRepository: accountRepository,
blockDownloaderService: blockDownloaderService,
config: utxoFetcherConfig,
internalSyncProgress: internalSyncProgress,
rustBackend: rustBackend,
metrics: metrics,
logger: logger

View File

@ -40,7 +40,6 @@ public class SDKSynchronizer: Synchronizer {
private let transactionEncoder: TransactionEncoder
private let transactionRepository: TransactionRepository
private let utxoRepository: UnspentTransactionOutputRepository
let internalSyncProgress: InternalSyncProgress
private let syncSessionIDGenerator: SyncSessionIDGenerator
private let syncSession: SyncSession
@ -88,7 +87,6 @@ public class SDKSynchronizer: Synchronizer {
self.syncSession = SyncSession(.nullID)
self.syncSessionTicker = syncSessionTicker
self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self)
internalSyncProgress = initializer.container.resolve(InternalSyncProgress.self)
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
self?.connectivityStateChanged(oldState: oldState, newState: newState)
@ -139,7 +137,6 @@ public class SDKSynchronizer: Synchronizer {
}
try await utxoRepository.initialise()
try await internalSyncProgress.initialize()
if case .seedRequired = try await self.initializer.initialize(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday) {
return .seedRequired

View File

@ -231,16 +231,6 @@ final class SynchronizerTests: ZcashTestCase {
XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist")
XCTAssertEqual(try fm.contentsOfDirectory(atPath: storage.blocksDirectory.path), [], "FS Cache directory should be empty")
let internalSyncProgress = coordinator.synchronizer.internalSyncProgress
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
let latestEnhancedHeight = try await internalSyncProgress.load(.latestEnhancedHeight)
let latestUTXOFetchedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
XCTAssertEqual(latestDownloadedBlockHeight, 0, "internalSyncProgress latestDownloadedBlockHeight should be 0")
XCTAssertEqual(latestEnhancedHeight, 0, "internalSyncProgress latestEnhancedHeight should be 0")
XCTAssertEqual(latestUTXOFetchedHeight, 0, "internalSyncProgress latestUTXOFetchedHeight should be 0")
let status = await coordinator.synchronizer.status
XCTAssertEqual(status, .unprepared, "SDKSynchronizer state should be unprepared")
}

View File

@ -15,7 +15,6 @@ class BlockStreamingTest: ZcashTestCase {
var endpoint: LightWalletEndpoint!
var service: LightWalletService!
var storage: FSCompactBlockRepository!
var internalSyncProgress: InternalSyncProgress!
var processorConfig: CompactBlockProcessor.Configuration!
var latestBlockHeight: BlockHeight!
var startHeight: BlockHeight!
@ -65,7 +64,6 @@ class BlockStreamingTest: ZcashTestCase {
endpoint = nil
service = nil
storage = nil
internalSyncProgress = nil
processorConfig = nil
}
@ -92,10 +90,6 @@ class BlockStreamingTest: ZcashTestCase {
)
try await storage.create()
let internalSyncProgressStorage = InternalSyncProgressMemoryStorage()
try await internalSyncProgressStorage.set(startHeight, for: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue)
internalSyncProgress = InternalSyncProgress(alias: .default, storage: internalSyncProgressStorage, logger: logger)
processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
@ -113,7 +107,6 @@ class BlockStreamingTest: ZcashTestCase {
service: service,
downloaderService: BlockDownloaderServiceImpl(service: service, storage: storage),
storage: storage,
internalSyncProgress: internalSyncProgress,
metrics: SDKMetrics(),
logger: logger
)
@ -141,24 +134,21 @@ class BlockStreamingTest: ZcashTestCase {
try await makeDependencies(timeout: 10000)
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
let syncRanges = SyncRanges(
let blockDownloader = mockContainer.resolve(BlockDownloader.self)
let syncControlData = SyncControlData(
latestBlockHeight: latestBlockHeight,
downloadRange: startHeight...latestBlockHeight,
scanRange: nil,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: startHeight,
latestDownloadedBlockHeight: startHeight
firstUnenhancedHeight: nil
)
let context = ActionContext(state: .download)
await context.update(syncRanges: syncRanges)
await context.update(syncControlData: syncControlData)
let expectation = XCTestExpectation()
let cancelableTask = Task {
do {
_ = try await action.run(with: context, didUpdate: { _ in })
let lastDownloadedHeight = try await internalSyncProgress.latestDownloadedBlockHeight
let lastDownloadedHeight = await blockDownloader.latestDownloadedBlockHeight()
// Just to be sure that download was interrupted before download was finished.
XCTAssertLessThan(lastDownloadedHeight, latestBlockHeight)
expectation.fulfill()
@ -180,17 +170,13 @@ class BlockStreamingTest: ZcashTestCase {
try await makeDependencies(timeout: 100)
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
let syncRanges = SyncRanges(
let syncControlData = SyncControlData(
latestBlockHeight: latestBlockHeight,
downloadRange: startHeight...latestBlockHeight,
scanRange: nil,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: startHeight,
latestDownloadedBlockHeight: startHeight
firstUnenhancedHeight: nil
)
let context = ActionContext(state: .download)
await context.update(syncRanges: syncRanges)
await context.update(syncControlData: syncControlData)
let date = Date()

View File

@ -17,7 +17,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var rustBackend: ZcashRustBackendWelding!
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var syncStartedExpectation: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
@ -30,10 +30,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
try await super.setUp()
logger = OSLogger(logLevel: .debug)
for key in InternalSyncProgress.Key.allCases {
UserDefaults.standard.set(0, forKey: key.with(.default))
}
let pathProvider = DefaultResourceProvider(network: network)
processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
@ -109,7 +105,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
return
}
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")
syncStartedExpectation = XCTestExpectation(description: "\(self.description) syncStartedExpectation")
stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation")
updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation")
finishedNotificationExpectation = XCTestExpectation(description: "\(self.description) finishedNotificationExpectation")
@ -147,7 +143,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
XCTAssertNotNil(processor)
let expectations: [CompactBlockProcessorEventHandler.EventIdentifier: XCTestExpectation] = [
.startedSyncing: syncStartedExpect,
.startedSyncing: syncStartedExpectation,
.stopped: stopNotificationExpectation,
.progressUpdated: updatedNotificationExpectation,
.finished: finishedNotificationExpectation
@ -162,7 +158,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
await fulfillment(
of: [
syncStartedExpect,
syncStartedExpectation,
finishedNotificationExpectation
],
timeout: 30,
@ -186,108 +182,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
(abs(currentHeight - targetHeight) / batchSize)
}
func testNextBatchBlockRange() async throws {
// test first range
var latestDownloadedHeight = processorConfig.walletBirthday // this can be either this or Wallet Birthday.
var latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
var expectedSyncRanges = SyncRanges(
latestBlockHeight: latestBlockchainHeight,
downloadRange: latestDownloadedHeight...latestBlockchainHeight,
scanRange: latestDownloadedHeight...latestBlockchainHeight,
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
latestScannedHeight: 0,
latestDownloadedBlockHeight: latestDownloadedHeight
)
var internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
var syncRanges = try await internalSyncProgress.computeSyncRanges(
birthday: processorConfig.walletBirthday,
latestBlockHeight: latestBlockchainHeight,
latestScannedHeight: 0
)
XCTAssertEqual(
expectedSyncRanges,
syncRanges,
"Failure when testing first range"
)
// Test mid-range
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultBatchSize)
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
expectedSyncRanges = SyncRanges(
latestBlockHeight: latestBlockchainHeight,
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
latestScannedHeight: 0,
latestDownloadedBlockHeight: latestDownloadedHeight
)
internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
syncRanges = try await internalSyncProgress.computeSyncRanges(
birthday: processorConfig.walletBirthday,
latestBlockHeight: latestBlockchainHeight,
latestScannedHeight: 0
)
XCTAssertEqual(
expectedSyncRanges,
syncRanges,
"Failure when testing mid range"
)
// Test last batch range
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + 950)
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
expectedSyncRanges = SyncRanges(
latestBlockHeight: latestBlockchainHeight,
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
latestScannedHeight: 0,
latestDownloadedBlockHeight: latestDownloadedHeight
)
internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
syncRanges = try await internalSyncProgress.computeSyncRanges(
birthday: processorConfig.walletBirthday,
latestBlockHeight: latestBlockchainHeight,
latestScannedHeight: 0
)
XCTAssertEqual(
expectedSyncRanges,
syncRanges,
"Failure when testing last range"
)
}
func testDetermineLowerBoundPastBirthday() async {
let errorHeight = 781_906

View File

@ -0,0 +1,35 @@
//
// ActionContextStateTests.swift
//
//
// Created by Lukáš Korba on 15.06.2023.
//
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
final class ActionContextStateTests: XCTestCase {
func testPreviousState() async throws {
let syncContext: ActionContext = .init(state: .idle)
await syncContext.update(state: .clearCache)
let currentState = await syncContext.state
let prevState = await syncContext.prevState
XCTAssertTrue(
currentState == .clearCache,
"syncContext.state after update is expected to be .clearCache but received \(currentState)"
)
if let prevState {
XCTAssertTrue(
prevState == .idle,
"syncContext.prevState after update is expected to be .idle but received \(prevState)"
)
} else {
XCTFail("syncContext.prevState is not expected to be nil.")
}
}
}

View File

@ -1,157 +0,0 @@
//
// ChecksBeforeSyncActionTests.swift
//
//
// Created by Lukáš Korba on 22.05.2023.
//
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
final class ChecksBeforeSyncActionTests: ZcashTestCase {
var underlyingDownloadRange: CompactBlockRange?
var underlyingScanRange: CompactBlockRange?
var underlyingLatestScannedHeight: BlockHeight?
var underlyingLatestDownloadedBlockHeight: BlockHeight?
override func setUp() {
super.setUp()
underlyingDownloadRange = nil
underlyingScanRange = nil
underlyingLatestScannedHeight = nil
underlyingLatestDownloadedBlockHeight = nil
}
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_noDownloadNoScanRange() async throws {
let checksBeforeSyncAction = setupAction()
let syncRanges = setupSyncRanges()
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
XCTAssertNil(latestScannedHeight, "latestScannedHeight is expected to be nil.")
}
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_nothingToClear() async throws {
let checksBeforeSyncAction = setupAction()
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingLatestScannedHeight = BlockHeight(2000)
underlyingLatestDownloadedBlockHeight = BlockHeight(2000)
let syncRanges = setupSyncRanges()
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
XCTAssertNil(latestScannedHeight, "latestScannedHeight is expected to be nil.")
}
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_somethingToClear() async throws {
let checksBeforeSyncAction = setupAction()
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingLatestScannedHeight = BlockHeight(2000)
underlyingLatestDownloadedBlockHeight = BlockHeight(1000)
let syncRanges = setupSyncRanges()
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
XCTAssertNotNil(latestScannedHeight, "latestScannedHeight is not expected to be nil.")
}
func testChecksBeforeSyncAction_NextAction_ClearStorage() async throws {
let compactBlockRepository = CompactBlockRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
compactBlockRepository.clearClosure = { }
internalSyncProgressStorageMock.setForClosure = { _, _ in }
let checksBeforeSyncAction = setupAction(
compactBlockRepository,
internalSyncProgressStorageMock
)
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingLatestScannedHeight = BlockHeight(2000)
underlyingLatestDownloadedBlockHeight = BlockHeight(1000)
let syncContext = await setupActionContext()
do {
let nextContext = try await checksBeforeSyncAction.run(with: syncContext) { _ in }
XCTAssertTrue(compactBlockRepository.clearCalled, "storage.clear() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .fetchUTXO,
"nextContext after .checksBeforeSync is expected to be .fetchUTXO but received \(nextState)"
)
} catch {
XCTFail("testChecksBeforeSyncAction_NextAction_ClearStorage is not expected to fail. \(error)")
}
}
func testChecksBeforeSyncAction_NextAction_CreateStorage() async throws {
let compactBlockRepository = CompactBlockRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
compactBlockRepository.createClosure = { }
let checksBeforeSyncAction = setupAction(compactBlockRepository)
let syncContext = await setupActionContext()
do {
let nextContext = try await checksBeforeSyncAction.run(with: syncContext) { _ in }
XCTAssertTrue(compactBlockRepository.createCalled, "storage.create() is expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .fetchUTXO,
"nextContext after .checksBeforeSync is expected to be .fetchUTXO but received \(nextState)"
)
} catch {
XCTFail("testChecksBeforeSyncAction_NextAction_CreateStorage is not expected to fail. \(error)")
}
}
private func setupAction(
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock(),
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ChecksBeforeSyncAction {
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
}
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
return ChecksBeforeSyncAction(
container: mockContainer
)
}
private func setupSyncRanges() -> SyncRanges {
SyncRanges(
latestBlockHeight: 0,
downloadRange: underlyingDownloadRange,
scanRange: underlyingScanRange,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: underlyingLatestScannedHeight,
latestDownloadedBlockHeight: underlyingLatestDownloadedBlockHeight
)
}
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .checksBeforeSync)
await syncContext.update(syncRanges: setupSyncRanges())
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext
}
}

View File

@ -0,0 +1,146 @@
//
// ComputeSyncControlDataActionTests.swift
//
//
// Created by Lukáš Korba on 22.05.2023.
//
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
final class ComputeSyncControlDataActionTests: ZcashTestCase {
var underlyingDownloadRange: CompactBlockRange?
var underlyingScanRange: CompactBlockRange?
override func setUp() {
super.setUp()
underlyingDownloadRange = nil
underlyingScanRange = nil
}
func testComputeSyncControlDataAction_finishProcessingCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let loggerMock = LoggerMock()
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(
latestBlocksDataProviderMock.updateBlockDataCalled,
"latestBlocksDataProvider.updateBlockData() is expected to be called."
)
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .finished,
"nextContext after .computeSyncControlData is expected to be .finished but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncControlDataAction_finishProcessingCase is not expected to fail. \(error)")
}
}
func testComputeSyncControlDataAction_fetchUTXOsCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let loggerMock = LoggerMock()
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 10
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .fetchUTXO,
"nextContext after .computeSyncControlData is expected to be .fetchUTXO but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncControlDataAction_checksBeforeSyncCase is not expected to fail. \(error)")
}
}
private func setupSyncControlData() -> SyncControlData {
SyncControlData(
latestBlockHeight: 0,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
}
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .computeSyncControlData)
await syncContext.update(syncControlData: setupSyncControlData())
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext
}
private func setupAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncControlDataAction {
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in blockDownloaderServiceMock }
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in latestBlocksDataProviderMock }
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
let config: CompactBlockProcessor.Configuration = .standard(
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
)
return ComputeSyncControlDataAction(
container: mockContainer,
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
)
}
private func setupDefaultMocksAndReturnAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncControlDataAction {
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 1
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 1
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 1
latestBlocksDataProviderMock.updateScannedDataClosure = { }
latestBlocksDataProviderMock.updateBlockDataClosure = { }
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
return setupAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
}
}

View File

@ -1,242 +0,0 @@
//
// ComputeSyncRangesActionTests.swift
//
//
// Created by Lukáš Korba on 22.05.2023.
//
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
final class ComputeSyncRangesActionTests: ZcashTestCase {
var underlyingDownloadRange: CompactBlockRange?
var underlyingScanRange: CompactBlockRange?
override func setUp() {
super.setUp()
underlyingDownloadRange = nil
underlyingScanRange = nil
}
func testComputeSyncRangesAction_computeTotalProgressRange_noDownloadNoScanRange() async throws {
let computeSyncRangesAction = setupAction()
let syncRanges = setupSyncRanges()
let totalProgressRange = computeSyncRangesAction.computeTotalProgressRange(from: syncRanges)
XCTAssertTrue(
totalProgressRange == 0...0,
"testComputeSyncRangesAction_computeTotalProgressRange_noDownloadNoScanRange is expected to be 0...0 but received \(totalProgressRange)"
)
}
func testComputeSyncRangesAction_computeTotalProgressRange_ValidRange() async throws {
let computeSyncRangesAction = setupAction()
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
let syncRanges = setupSyncRanges()
let totalProgressRange = computeSyncRangesAction.computeTotalProgressRange(from: syncRanges)
let expectedRange = 1000...2000
XCTAssertTrue(
totalProgressRange == expectedRange,
"testComputeSyncRangesAction_computeTotalProgressRange_ValidRange is expected to be \(expectedRange) but received \(totalProgressRange)"
)
}
func testComputeSyncRangesAction_finishProcessingCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
let loggerMock = LoggerMock()
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
internalSyncProgressStorageMock,
loggerMock
)
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
XCTAssertTrue(
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
)
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .finished,
"nextContext after .computeSyncRanges is expected to be .finished but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncRangesAction_finishProcessingCase is not expected to fail. \(error)")
}
}
func testComputeSyncRangesAction_checksBeforeSyncCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
let loggerMock = LoggerMock()
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
internalSyncProgressStorageMock,
loggerMock
)
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 10
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
XCTAssertTrue(
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
)
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .checksBeforeSync,
"nextContext after .computeSyncRanges is expected to be .checksBeforeSync but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncRangesAction_checksBeforeSyncCase is not expected to fail. \(error)")
}
}
func testComputeSyncRangesAction_waitCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
let loggerMock = LoggerMock()
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
internalSyncProgressStorageMock,
loggerMock
)
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 10
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 10
internalSyncProgressStorageMock.integerForReturnValue = 10
loggerMock.infoFileFunctionLineClosure = { _, _, _, _ in }
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
XCTAssertTrue(
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
)
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
XCTAssertTrue(loggerMock.infoFileFunctionLineCalled, "logger.info() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .finished,
"nextContext after .computeSyncRanges is expected to be .finished but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncRangesAction_waitCase is not expected to fail. \(error)")
}
}
private func setupSyncRanges() -> SyncRanges {
SyncRanges(
latestBlockHeight: 0,
downloadRange: underlyingDownloadRange,
scanRange: underlyingScanRange,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
)
}
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .computeSyncRanges)
await syncContext.update(syncRanges: setupSyncRanges())
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext
}
private func setupAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncRangesAction {
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
}
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in blockDownloaderServiceMock }
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in latestBlocksDataProviderMock }
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
let config: CompactBlockProcessor.Configuration = .standard(
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
)
return ComputeSyncRangesAction(
container: mockContainer,
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
)
}
private func setupDefaultMocksAndReturnAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncRangesAction {
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 1
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 1
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 1
latestBlocksDataProviderMock.updateScannedDataClosure = { }
latestBlocksDataProviderMock.updateBlockDataClosure = { }
internalSyncProgressStorageMock.integerForReturnValue = 1
internalSyncProgressStorageMock.boolForReturnValue = true
internalSyncProgressStorageMock.setBoolClosure = { _, _ in }
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
return setupAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
internalSyncProgressStorageMock,
loggerMock
)
}
}

View File

@ -17,11 +17,12 @@ final class DownloadActionTests: ZcashTestCase {
let blockDownloaderMock = BlockDownloaderMock()
let transactionRepositoryMock = TransactionRepositoryMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1
transactionRepositoryMock.lastScannedHeightReturnValue = 1000
blockDownloaderMock.setSyncRangeBatchSizeClosure = { _, _ in }
blockDownloaderMock.setDownloadLimitClosure = { _ in }
blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in }
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in }
blockDownloaderMock.updateLatestDownloadedBlockHeightClosure = { _ in }
let downloadAction = setupAction(
blockDownloaderMock,
@ -146,17 +147,13 @@ final class DownloadActionTests: ZcashTestCase {
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .download)
let syncRanges = SyncRanges(
latestBlockHeight: 0,
downloadRange: underlyingDownloadRange,
scanRange: underlyingScanRange,
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
let syncControlData = SyncControlData(
latestBlockHeight: 2000,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
await syncContext.update(syncRanges: syncRanges)
await syncContext.update(syncControlData: syncControlData)
return syncContext
}

View File

@ -68,14 +68,12 @@ final class EnhanceActionTests: ZcashTestCase {
func testEnhanceAction_NoEnhanceRange() async throws {
let blockEnhancerMock = BlockEnhancerMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1
let enhanceAction = setupAction(
blockEnhancerMock,
transactionRepositoryMock,
internalSyncProgressStorageMock
transactionRepositoryMock
)
let syncContext = await setupActionContext()
@ -84,7 +82,6 @@ final class EnhanceActionTests: ZcashTestCase {
_ = try await enhanceAction.run(with: syncContext) { _ in }
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is not expected to be called.")
} catch {
XCTFail("testEnhanceAction_NoEnhanceRange is not expected to fail. \(error)")
}
@ -93,15 +90,12 @@ final class EnhanceActionTests: ZcashTestCase {
func testEnhanceAction_1000BlocksConditionNotFulfilled() async throws {
let blockEnhancerMock = BlockEnhancerMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1
internalSyncProgressStorageMock.integerForReturnValue = 1
let enhanceAction = setupAction(
blockEnhancerMock,
transactionRepositoryMock,
internalSyncProgressStorageMock
transactionRepositoryMock
)
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
@ -111,7 +105,6 @@ final class EnhanceActionTests: ZcashTestCase {
do {
_ = try await enhanceAction.run(with: syncContext) { _ in }
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
} catch {
XCTFail("testEnhanceAction_1000BlocksConditionNotFulfilled is not expected to fail. \(error)")
@ -121,10 +114,8 @@ final class EnhanceActionTests: ZcashTestCase {
func testEnhanceAction_EnhancementOfBlocksCalled_FoundTransactions() async throws {
let blockEnhancerMock = BlockEnhancerMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1500
internalSyncProgressStorageMock.integerForReturnValue = 1
let transaction = ZcashTransaction.Overview(
accountId: 0,
@ -151,8 +142,7 @@ final class EnhanceActionTests: ZcashTestCase {
let enhanceAction = setupAction(
blockEnhancerMock,
transactionRepositoryMock,
internalSyncProgressStorageMock
transactionRepositoryMock
)
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
@ -174,7 +164,6 @@ final class EnhanceActionTests: ZcashTestCase {
XCTAssertEqual(receivedTransaction.expiryHeight, transaction.expiryHeight, "ReceivedTransaction differs from mocked one.")
}
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
} catch {
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_FoundTransactions is not expected to fail. \(error)")
@ -184,10 +173,8 @@ final class EnhanceActionTests: ZcashTestCase {
func testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction() async throws {
let blockEnhancerMock = BlockEnhancerMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1500
internalSyncProgressStorageMock.integerForReturnValue = 1
let transaction = ZcashTransaction.Overview(
accountId: 0,
@ -222,8 +209,7 @@ final class EnhanceActionTests: ZcashTestCase {
let enhanceAction = setupAction(
blockEnhancerMock,
transactionRepositoryMock,
internalSyncProgressStorageMock
transactionRepositoryMock
)
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
@ -241,7 +227,6 @@ final class EnhanceActionTests: ZcashTestCase {
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
}
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
} catch {
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
@ -251,10 +236,8 @@ final class EnhanceActionTests: ZcashTestCase {
func testEnhanceAction_EnhancementOfBlocksCalled_usingSmallRange_minedTransaction() async throws {
let blockEnhancerMock = BlockEnhancerMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 200
internalSyncProgressStorageMock.integerForReturnValue = 1
transactionRepositoryMock.lastScannedHeightReturnValue = 2000
let transaction = ZcashTransaction.Overview(
accountId: 0,
@ -289,11 +272,10 @@ final class EnhanceActionTests: ZcashTestCase {
let enhanceAction = setupAction(
blockEnhancerMock,
transactionRepositoryMock,
internalSyncProgressStorageMock
transactionRepositoryMock
)
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (100, 200))
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1900, 2000))
let syncContext = await setupActionContext()
@ -308,7 +290,6 @@ final class EnhanceActionTests: ZcashTestCase {
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
}
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
} catch {
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
@ -318,17 +299,13 @@ final class EnhanceActionTests: ZcashTestCase {
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .enhance)
let syncRanges = SyncRanges(
latestBlockHeight: 0,
downloadRange: underlyingDownloadRange,
scanRange: underlyingScanRange,
enhanceRange: underlyingEnhanceRange,
fetchUTXORange: nil,
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
let syncControlData = SyncControlData(
latestBlockHeight: 2000,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: underlyingEnhanceRange?.lowerBound
)
await syncContext.update(syncRanges: syncRanges)
await syncContext.update(syncControlData: syncControlData)
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext
@ -337,13 +314,8 @@ final class EnhanceActionTests: ZcashTestCase {
private func setupAction(
_ blockEnhancerMock: BlockEnhancerMock = BlockEnhancerMock(),
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> EnhanceAction {
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
}
mockContainer.mock(type: BlockEnhancer.self, isSingleton: true) { _ in blockEnhancerMock }
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }

View File

@ -17,7 +17,7 @@ final class FetchUTXOsActionTests: ZcashTestCase {
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
let insertedEntity = UnspentTransactionOutputEntityMock(address: "addr", txid: Data(), index: 0, script: Data(), valueZat: 1, height: 2)
let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3)
uTXOFetcherMock.fetchAtDidFetchReturnValue = (inserted: [insertedEntity], skipped: [skippedEntity])
uTXOFetcherMock.fetchDidFetchReturnValue = (inserted: [insertedEntity], skipped: [skippedEntity])
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
mockContainer.mock(type: UTXOFetcher.self, isSingleton: true) { _ in uTXOFetcherMock }
@ -26,17 +26,13 @@ final class FetchUTXOsActionTests: ZcashTestCase {
let syncContext: ActionContext = .init(state: .fetchUTXO)
let syncRanges = SyncRanges(
let syncControlData = SyncControlData(
latestBlockHeight: 0,
downloadRange: nil,
scanRange: nil,
enhanceRange: nil,
fetchUTXORange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
latestScannedHeight: 0,
firstUnenhancedHeight: nil
)
await syncContext.update(syncRanges: syncRanges)
await syncContext.update(syncControlData: syncControlData)
do {
let nextContext = try await fetchUTXOsAction.run(with: syncContext) { event in
@ -48,7 +44,7 @@ final class FetchUTXOsActionTests: ZcashTestCase {
XCTAssertEqual(result.skipped as! [UnspentTransactionOutputEntityMock], [skippedEntity])
}
XCTAssertTrue(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is expected to be called.")
XCTAssertTrue(uTXOFetcherMock.fetchAtDidFetchCalled, "utxoFetcher.fetch() is expected to be called.")
XCTAssertTrue(uTXOFetcherMock.fetchDidFetchCalled, "utxoFetcher.fetch() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .handleSaplingParams,

View File

@ -26,13 +26,11 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
@ -45,7 +43,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
)
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
@ -61,15 +58,13 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
@ -83,7 +78,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
)
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
} catch {
XCTFail("""
testMigrateLegacyCacheDBAction_noFsBlockCacheRoot is expected to fail with \
@ -96,7 +90,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
// any valid URL needed...
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
@ -107,8 +100,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
@ -121,7 +113,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
)
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
@ -137,7 +128,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
// any valid URL needed...
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
@ -148,8 +138,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
@ -162,7 +151,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
)
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
@ -178,7 +166,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
// any valid URL needed...
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
@ -190,8 +177,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
@ -204,7 +190,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
)
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
XCTAssertTrue(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is expected to be called.")
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
} catch {
XCTFail("""
testMigrateLegacyCacheDBAction_removeItemFailed is expected to fail with \
@ -217,7 +202,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
let zcashFileManagerMock = ZcashFileManagerMock()
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
// any valid URL needed...
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
@ -226,24 +210,19 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
zcashFileManagerMock.isReadableFileAtPathReturnValue = true
zcashFileManagerMock.removeItemAtClosure = { _ in }
compactBlockRepositoryMock.createClosure = { }
transactionRepositoryMock.lastScannedHeightReturnValue = 1
internalSyncProgressStorageMock.setForClosure = { _, _ in }
let migrateLegacyCacheDBAction = setupAction(
compactBlockRepositoryMock,
transactionRepositoryMock,
zcashFileManagerMock,
internalSyncProgressStorageMock
zcashFileManagerMock
)
do {
let nextContext = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
XCTAssertTrue(compactBlockRepositoryMock.createCalled, "storage.create() is expected to be called.")
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
XCTAssertTrue(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is expected to be called.")
XCTAssertTrue(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
@ -259,12 +238,8 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock(),
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),
_ zcashFileManagerMock: ZcashFileManagerMock = ZcashFileManagerMock(),
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> MigrateLegacyCacheDBAction {
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
}
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
mockContainer.mock(type: ZcashFileManager.self, isSingleton: true) { _ in zcashFileManagerMock }

View File

@ -108,17 +108,13 @@ final class ScanActionTests: ZcashTestCase {
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .scan)
let syncRanges = SyncRanges(
latestBlockHeight: 0,
downloadRange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
scanRange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
enhanceRange: nil,
fetchUTXORange: nil,
latestScannedHeight: nil,
latestDownloadedBlockHeight: nil
let syncControlData = SyncControlData(
latestBlockHeight: 2000,
latestScannedHeight: 1000,
firstUnenhancedHeight: nil
)
await syncContext.update(syncRanges: syncRanges)
await syncContext.update(syncControlData: syncControlData)
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext

View File

@ -31,8 +31,8 @@ final class ValidateServerActionTests: ZcashTestCase {
let nextContext = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .computeSyncRanges,
"nextContext after .validateServer is expected to be .computeSyncRanges but received \(nextState)"
nextState == .computeSyncControlData,
"nextContext after .validateServer is expected to be .computeSyncControlData but received \(nextState)"
)
} catch {
XCTFail("testValidateServerAction_NextAction is not expected to fail. \(error)")

View File

@ -1,225 +0,0 @@
//
// InternalSyncProgressTests.swift
//
//
// Created by Michal Fousek on 30.11.2022.
//
@testable import TestUtils
import XCTest
@testable import ZcashLightClientKit
class InternalSyncProgressTests: ZcashTestCase {
var storage: InternalSyncProgressDiskStorage!
var internalSyncProgress: InternalSyncProgress!
override func setUp() async throws {
try await super.setUp()
for key in InternalSyncProgress.Key.allCases {
UserDefaults.standard.removeObject(forKey: key.with(.default))
}
storage = InternalSyncProgressDiskStorage(storageURL: testGeneralStorageDirectory, logger: logger)
internalSyncProgress = InternalSyncProgress(alias: .default, storage: storage, logger: logger)
try await internalSyncProgress.initialize()
}
override func tearDownWithError() throws {
try super.tearDownWithError()
storage = nil
internalSyncProgress = nil
}
func test__trackedValuesAreHigherThanLatestHeight__nextStateIsWait() async throws {
let latestHeight = 623000
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
let nextState = try await internalSyncProgress.computeNextState(
latestBlockHeight: latestHeight,
latestScannedHeight: 630000,
walletBirthday: 600000
)
switch nextState {
case let .wait(latestHeight, latestDownloadHeight):
XCTAssertEqual(latestHeight, 623000)
XCTAssertEqual(latestDownloadHeight, 630000)
default:
XCTFail("State should be wait. Unexpected state: \(nextState)")
}
}
func test__trackedValuesAreLowerThanLatestHeight__nextStateIsProcessNewBlocks() async throws {
let latestHeight = 640000
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
let nextState = try await internalSyncProgress.computeNextState(
latestBlockHeight: latestHeight,
latestScannedHeight: 620000,
walletBirthday: 600000
)
switch nextState {
case let .processNewBlocks(ranges):
XCTAssertEqual(ranges.downloadRange, 630001...640000)
XCTAssertEqual(ranges.scanRange, 620001...640000)
XCTAssertEqual(ranges.enhanceRange, 630001...640000)
XCTAssertEqual(ranges.fetchUTXORange, 630001...640000)
default:
XCTFail("State should be processNewBlocks. Unexpected state: \(nextState)")
}
}
func test__trackedValuesAreSameAsLatestHeight__nextStateIsFinishProcessing() async throws {
let latestHeight = 630000
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
let nextState = try await internalSyncProgress.computeNextState(
latestBlockHeight: latestHeight,
latestScannedHeight: 630000,
walletBirthday: 600000
)
switch nextState {
case let .finishProcessing(height):
XCTAssertEqual(height, latestHeight)
default:
XCTFail("State should be finishProcessing. Unexpected state: \(nextState)")
}
}
func test__rewindToHeightThatIsHigherThanTrackedHeight__rewindsToTrackedHeight() async throws {
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
try await internalSyncProgress.rewind(to: 640000)
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
XCTAssertEqual(latestEnhancedHeight, 630000)
XCTAssertEqual(latestUTXOFetchedHeight, 630000)
}
func test__rewindToHeightThatIsLowerThanTrackedHeight__rewindsToRewindHeight() async throws {
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
try await internalSyncProgress.rewind(to: 620000)
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
XCTAssertEqual(latestEnhancedHeight, 620000)
XCTAssertEqual(latestUTXOFetchedHeight, 620000)
}
func test__get__returnsStoredValue() async throws {
try await storage.set(621000, for: "latestEnhancedHeight")
let latestEnhancedHeight = try await internalSyncProgress.latestEnhancedHeight
XCTAssertEqual(latestEnhancedHeight, 621000)
try await storage.set(619000, for: "latestUTXOFetchedHeight")
let latestUTXOFetchedHeight = try await internalSyncProgress.latestUTXOFetchedHeight
XCTAssertEqual(latestUTXOFetchedHeight, 619000)
}
func test__set__storeValue() async throws {
try await internalSyncProgress.set(521000, .latestEnhancedHeight)
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
XCTAssertEqual(latestEnhancedHeight, 521000)
try await internalSyncProgress.set(519000, .latestUTXOFetchedHeight)
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
XCTAssertEqual(latestUTXOFetchedHeight, 519000)
}
func test__whenUsingDefaultAliasKeysAreBackwardsCompatible() async throws {
try await internalSyncProgress.set(630000, .latestDownloadedBlockHeight)
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
let latestDownloadedBlockHeight = try await storage.integer(for: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue)
let latestUTXOFetchedHeight = try await storage.integer(for: InternalSyncProgress.Key.latestUTXOFetchedHeight.rawValue)
let latestEnhancedHeight = try await storage.integer(for: InternalSyncProgress.Key.latestEnhancedHeight.rawValue)
XCTAssertEqual(latestDownloadedBlockHeight, 630000)
XCTAssertEqual(latestUTXOFetchedHeight, 630000)
XCTAssertEqual(latestEnhancedHeight, 630000)
}
func test__usingDifferentAliasesStoreValuesIndependently() async throws {
let internalSyncProgress1 = InternalSyncProgress(alias: .custom("alias1"), storage: storage, logger: logger)
try await internalSyncProgress1.set(121000, .latestDownloadedBlockHeight)
try await internalSyncProgress1.set(121000, .latestUTXOFetchedHeight)
try await internalSyncProgress1.set(121000, .latestEnhancedHeight)
let internalSyncProgress2 = InternalSyncProgress(alias: .custom("alias2"), storage: storage, logger: logger)
try await internalSyncProgress2.set(630000, .latestDownloadedBlockHeight)
try await internalSyncProgress2.set(630000, .latestUTXOFetchedHeight)
try await internalSyncProgress2.set(630000, .latestEnhancedHeight)
let latestDownloadedBlockHeight1 = try await internalSyncProgress1.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh1 = try await internalSyncProgress1.load(.latestUTXOFetchedHeight)
let latestEnhancedHeight1 = try await internalSyncProgress1.load(.latestEnhancedHeight)
XCTAssertEqual(latestDownloadedBlockHeight1, 121000)
XCTAssertEqual(latestUTXOFetchedHeigh1, 121000)
XCTAssertEqual(latestEnhancedHeight1, 121000)
let latestDownloadedBlockHeight2 = try await internalSyncProgress2.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh2 = try await internalSyncProgress2.load(.latestUTXOFetchedHeight)
let latestEnhancedHeight2 = try await internalSyncProgress2.load(.latestEnhancedHeight)
XCTAssertEqual(latestDownloadedBlockHeight2, 630000)
XCTAssertEqual(latestUTXOFetchedHeigh2, 630000)
XCTAssertEqual(latestEnhancedHeight2, 630000)
}
func test__migrateFromUserDefaults__withDefaultAlias() async throws {
let userDefaults = UserDefaults.standard
userDefaults.set(113000, forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(.default))
userDefaults.set(114000, forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(.default))
userDefaults.set(115000, forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(.default))
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 150000, alias: .default)
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh = try await internalSyncProgress.load(.latestEnhancedHeight)
let latestEnhancedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
XCTAssertEqual(latestDownloadedBlockHeight, 113000)
XCTAssertEqual(latestUTXOFetchedHeigh, 114000)
XCTAssertEqual(latestEnhancedHeight, 115000)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(.default)), 0)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(.default)), 0)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(.default)), 0)
}
func test__migrateFromUserDefaults__withAlias() async throws {
let userDefaults = UserDefaults.standard
let alias: ZcashSynchronizerAlias = .custom("something")
internalSyncProgress = InternalSyncProgress(alias: alias, storage: storage, logger: logger)
userDefaults.set(113000, forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(alias))
userDefaults.set(114000, forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(alias))
userDefaults.set(115000, forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(alias))
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 150000, alias: alias)
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh = try await internalSyncProgress.load(.latestEnhancedHeight)
let latestEnhancedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
XCTAssertEqual(latestDownloadedBlockHeight, 113000)
XCTAssertEqual(latestUTXOFetchedHeigh, 114000)
XCTAssertEqual(latestEnhancedHeight, 115000)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(alias)), 0)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(alias)), 0)
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(alias)), 0)
}
}

View File

@ -92,7 +92,6 @@ class SynchronizerTests: ZcashTestCase {
let syncSyncedExpectation = XCTestExpectation(description: "synchronizerSynced Expectation")
sdkSynchronizerInternalSyncStatusHandler.subscribe(to: synchronizer.stateStream, expectations: [.synced: syncSyncedExpectation])
try await resetDefaultInternalSyncProgress(to: birthday)
await (synchronizer.blockProcessor.service as? LightWalletGRPCService)?.latestBlockHeightProvider = MockLatestBlockHeightProvider(
birthday: self.birthday + 99
)

View File

@ -1,34 +0,0 @@
//
// InternalSyncProgressMemoryStorage.swift
//
//
// Created by Michal Fousek on 24.11.2022.
//
import Foundation
@testable import ZcashLightClientKit
class InternalSyncProgressMemoryStorage: InternalSyncProgressStorage {
private var boolStorage: [String: Bool] = [:]
private var storage: [String: Int] = [:]
func initialize() async throws { }
func bool(for key: String) async throws -> Bool {
return boolStorage[key, default: false]
}
func integer(for key: String) async throws -> Int {
return storage[key, default: 0]
}
func set(_ value: Int, for key: String) async throws {
storage[key] = value
}
func set(_ value: Bool, for key: String) async throws {
boolStorage[key] = value
}
func synchronize() -> Bool { true }
}

View File

@ -115,6 +115,10 @@ extension MockTransactionRepository: TransactionRepository {
nil
}
func firstUnenhancedHeight() throws -> ZcashLightClientKit.BlockHeight? {
nil
}
func isInitialized() throws -> Bool {
true
}

View File

@ -18,7 +18,6 @@ extension BlockEnhancer { }
extension BlockScanner { }
extension BlockValidator { }
extension CompactBlockRepository { }
extension InternalSyncProgressStorage { }
extension LatestBlocksDataProvider { }
extension LightWalletdInfo { }
extension LightWalletService { }

View File

@ -95,6 +95,54 @@ class BlockDownloaderMock: BlockDownloader {
try await waitUntilRequestedBlocksAreDownloadedInClosure!(range)
}
// MARK: - update
var updateLatestDownloadedBlockHeightCallsCount = 0
var updateLatestDownloadedBlockHeightCalled: Bool {
return updateLatestDownloadedBlockHeightCallsCount > 0
}
var updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight: BlockHeight?
var updateLatestDownloadedBlockHeightClosure: ((BlockHeight) async -> Void)?
func update(latestDownloadedBlockHeight: BlockHeight) async {
updateLatestDownloadedBlockHeightCallsCount += 1
updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight = latestDownloadedBlockHeight
await updateLatestDownloadedBlockHeightClosure!(latestDownloadedBlockHeight)
}
// MARK: - latestDownloadedBlockHeight
var latestDownloadedBlockHeightCallsCount = 0
var latestDownloadedBlockHeightCalled: Bool {
return latestDownloadedBlockHeightCallsCount > 0
}
var latestDownloadedBlockHeightReturnValue: BlockHeight!
var latestDownloadedBlockHeightClosure: (() async -> BlockHeight)?
func latestDownloadedBlockHeight() async -> BlockHeight {
latestDownloadedBlockHeightCallsCount += 1
if let closure = latestDownloadedBlockHeightClosure {
return await closure()
} else {
return latestDownloadedBlockHeightReturnValue
}
}
// MARK: - rewind
var rewindLatestDownloadedBlockHeightCallsCount = 0
var rewindLatestDownloadedBlockHeightCalled: Bool {
return rewindLatestDownloadedBlockHeightCallsCount > 0
}
var rewindLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight: BlockHeight?
var rewindLatestDownloadedBlockHeightClosure: ((BlockHeight?) async -> Void)?
func rewind(latestDownloadedBlockHeight: BlockHeight?) async {
rewindLatestDownloadedBlockHeightCallsCount += 1
rewindLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight = latestDownloadedBlockHeight
await rewindLatestDownloadedBlockHeightClosure!(latestDownloadedBlockHeight)
}
}
class BlockDownloaderServiceMock: BlockDownloaderService {
@ -472,117 +520,6 @@ class CompactBlockRepositoryMock: CompactBlockRepository {
try await clearClosure!()
}
}
class InternalSyncProgressStorageMock: InternalSyncProgressStorage {
init(
) {
}
// MARK: - initialize
var initializeThrowableError: Error?
var initializeCallsCount = 0
var initializeCalled: Bool {
return initializeCallsCount > 0
}
var initializeClosure: (() async throws -> Void)?
func initialize() async throws {
if let error = initializeThrowableError {
throw error
}
initializeCallsCount += 1
try await initializeClosure!()
}
// MARK: - bool
var boolForThrowableError: Error?
var boolForCallsCount = 0
var boolForCalled: Bool {
return boolForCallsCount > 0
}
var boolForReceivedKey: String?
var boolForReturnValue: Bool!
var boolForClosure: ((String) async throws -> Bool)?
func bool(for key: String) async throws -> Bool {
if let error = boolForThrowableError {
throw error
}
boolForCallsCount += 1
boolForReceivedKey = key
if let closure = boolForClosure {
return try await closure(key)
} else {
return boolForReturnValue
}
}
// MARK: - integer
var integerForThrowableError: Error?
var integerForCallsCount = 0
var integerForCalled: Bool {
return integerForCallsCount > 0
}
var integerForReceivedKey: String?
var integerForReturnValue: Int!
var integerForClosure: ((String) async throws -> Int)?
func integer(for key: String) async throws -> Int {
if let error = integerForThrowableError {
throw error
}
integerForCallsCount += 1
integerForReceivedKey = key
if let closure = integerForClosure {
return try await closure(key)
} else {
return integerForReturnValue
}
}
// MARK: - set
var setForThrowableError: Error?
var setForCallsCount = 0
var setForCalled: Bool {
return setForCallsCount > 0
}
var setForReceivedArguments: (value: Int, key: String)?
var setForClosure: ((Int, String) async throws -> Void)?
func set(_ value: Int, for key: String) async throws {
if let error = setForThrowableError {
throw error
}
setForCallsCount += 1
setForReceivedArguments = (value: value, key: key)
try await setForClosure!(value, key)
}
// MARK: - set
var setBoolThrowableError: Error?
var setBoolCallsCount = 0
var setBoolCalled: Bool {
return setBoolCallsCount > 0
}
var setBoolReceivedArguments: (value: Bool, key: String)?
var setBoolClosure: ((Bool, String) async throws -> Void)?
func set(_ value: Bool, for key: String) async throws {
if let error = setBoolThrowableError {
throw error
}
setBoolCallsCount += 1
setBoolReceivedArguments = (value: value, key: key)
try await setBoolClosure!(value, key)
}
}
class LatestBlocksDataProviderMock: LatestBlocksDataProvider {
@ -606,6 +543,7 @@ class LatestBlocksDataProviderMock: LatestBlocksDataProvider {
get { return underlyingWalletBirthday }
}
var underlyingWalletBirthday: BlockHeight!
var firstUnenhancedHeight: BlockHeight?
// MARK: - updateScannedData
@ -1709,6 +1647,28 @@ class TransactionRepositoryMock: TransactionRepository {
}
}
// MARK: - firstUnenhancedHeight
var firstUnenhancedHeightThrowableError: Error?
var firstUnenhancedHeightCallsCount = 0
var firstUnenhancedHeightCalled: Bool {
return firstUnenhancedHeightCallsCount > 0
}
var firstUnenhancedHeightReturnValue: BlockHeight?
var firstUnenhancedHeightClosure: (() throws -> BlockHeight?)?
func firstUnenhancedHeight() throws -> BlockHeight? {
if let error = firstUnenhancedHeightThrowableError {
throw error
}
firstUnenhancedHeightCallsCount += 1
if let closure = firstUnenhancedHeightClosure {
return try closure()
} else {
return firstUnenhancedHeightReturnValue
}
}
// MARK: - isInitialized
var isInitializedThrowableError: Error?
@ -2005,25 +1965,25 @@ class UTXOFetcherMock: UTXOFetcher {
// MARK: - fetch
var fetchAtDidFetchThrowableError: Error?
var fetchAtDidFetchCallsCount = 0
var fetchAtDidFetchCalled: Bool {
return fetchAtDidFetchCallsCount > 0
var fetchDidFetchThrowableError: Error?
var fetchDidFetchCallsCount = 0
var fetchDidFetchCalled: Bool {
return fetchDidFetchCallsCount > 0
}
var fetchAtDidFetchReceivedArguments: (range: CompactBlockRange, didFetch: (Float) async -> Void)?
var fetchAtDidFetchReturnValue: (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])!
var fetchAtDidFetchClosure: ((CompactBlockRange, @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))?
var fetchDidFetchReceivedDidFetch: ((Float) async -> Void)?
var fetchDidFetchReturnValue: (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])!
var fetchDidFetchClosure: ((@escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))?
func fetch(at range: CompactBlockRange, didFetch: @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
if let error = fetchAtDidFetchThrowableError {
func fetch(didFetch: @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
if let error = fetchDidFetchThrowableError {
throw error
}
fetchAtDidFetchCallsCount += 1
fetchAtDidFetchReceivedArguments = (range: range, didFetch: didFetch)
if let closure = fetchAtDidFetchClosure {
return try await closure(range, didFetch)
fetchDidFetchCallsCount += 1
fetchDidFetchReceivedDidFetch = didFetch
if let closure = fetchDidFetchClosure {
return try await closure(didFetch)
} else {
return fetchAtDidFetchReturnValue
return fetchDidFetchReturnValue
}
}

View File

@ -66,11 +66,6 @@ class TestCoordinator {
let databases = TemporaryDbBuilder.build()
self.databases = databases
let storage = InternalSyncProgressDiskStorage(storageURL: databases.generalStorageURL, logger: logger)
let internalSyncProgress = InternalSyncProgress(alias: alias, storage: storage, logger: logger)
try await internalSyncProgress.initialize()
try await internalSyncProgress.rewind(to: 0)
let initializer = Initializer(
container: container,
cacheDbURL: nil,

View File

@ -59,22 +59,12 @@ class ZcashTestCase: XCTestCase {
testGeneralStorageDirectory = nil
}
// MARK: - InternalSyncProgress
func resetDefaultInternalSyncProgress(to height: BlockHeight = 0) async throws {
let storage = InternalSyncProgressDiskStorage(storageURL: testGeneralStorageDirectory, logger: logger)
let internalSyncProgress = InternalSyncProgress(alias: .default, storage: storage, logger: logger)
try await internalSyncProgress.initialize()
try await internalSyncProgress.rewind(to: 0)
}
// MARK: - XCTestCase
override func setUp() async throws {
try await super.setUp()
createMockContainer()
try createPaths()
try await resetDefaultInternalSyncProgress()
}
override func setUp() {