Merge pull request #1148 from LukasKorba/1140-ClearCache-action-before-anything-starts

ClearCache action called right after the idle action, clearing out metadata so the sync process can be fully restored from the DB and live blockchain values only.
InternalSyncProgress removed
InternalSyncProgressStorage removed
Sync process control logic updated, controlled by latestScannedHeight and firstUnenhancedHeight only
ChecksBeforeSyncAction removed
Offline tests fixed
fixed injection of a wallet birthday, the sync range must start with wallet BD instead of lower bound
Network tests fixed
rewind actions extension in compact block processor added
DarkSideTests fixed
SyncRanges modified to be even less dependent on ranges, now it holds just 3 values (latest block height, latest scanned height if any, first unenhanced height if any), the rest is computed on the fly
SyncRanges struct not anymore, refactored to SyncControlData, holding just 3 mentioned values
code cleanup
This commit is contained in:
Lukas Korba 2023-06-28 14:28:16 +02:00 committed by GitHub
commit d930517348
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 489 additions and 1664 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

@ -10,28 +10,31 @@ import Foundation
actor ActionContext {
var state: CBPState
var prevState: CBPState?
var syncRanges: SyncRanges
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 {
prevState = self.state
self.state = state
}
func update(syncRanges: SyncRanges) async { self.syncRanges = syncRanges }
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,63 +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

@ -9,7 +9,7 @@ import Foundation
final class ClearCacheAction {
let storage: CompactBlockRepository
init(container: DIContainer) {
storage = container.resolve(CompactBlockRepository.self)
}
@ -20,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,25 +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)
@ -44,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
@ -53,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,
@ -80,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

@ -509,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
@ -527,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 {
@ -682,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."
}
}
@ -841,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

@ -301,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.
@ -313,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

@ -589,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.
@ -610,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

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

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

@ -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
@ -2027,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() {