Merge pull request #1136 from zcash/feature/700_cbp_as_state_machine
[#700] Rewrite CompactBlockProcessor as state machine
This commit is contained in:
commit
3629861b7a
|
@ -150,7 +150,6 @@ class SyncBlocksViewController: UIViewController {
|
|||
case .unprepared, .error:
|
||||
do {
|
||||
if syncStatus == .unprepared {
|
||||
// swiftlint:disable:next force_try
|
||||
do {
|
||||
_ = try await synchronizer.prepare(
|
||||
with: DemoAppConfig.defaultSeed,
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Action.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
actor ActionContext {
|
||||
var state: CBPState
|
||||
var syncRanges: SyncRanges
|
||||
var totalProgressRange: CompactBlockRange = 0...0
|
||||
|
||||
init(state: CBPState) {
|
||||
self.state = state
|
||||
syncRanges = SyncRanges.empty
|
||||
}
|
||||
|
||||
func update(state: CBPState) async { self.state = state }
|
||||
func update(syncRanges: SyncRanges) async { self.syncRanges = syncRanges }
|
||||
func update(totalProgressRange: CompactBlockRange) async { self.totalProgressRange = totalProgressRange }
|
||||
}
|
||||
|
||||
enum CBPState: CaseIterable {
|
||||
case idle
|
||||
case migrateLegacyCacheDB
|
||||
case validateServer
|
||||
case computeSyncRanges
|
||||
case checksBeforeSync
|
||||
case download
|
||||
case validate
|
||||
case scan
|
||||
case clearAlreadyScannedBlocks
|
||||
case enhance
|
||||
case fetchUTXO
|
||||
case handleSaplingParams
|
||||
case clearCache
|
||||
case finished
|
||||
case failed
|
||||
case stopped
|
||||
}
|
||||
|
||||
protocol Action {
|
||||
/// If this is true and action fails with error then blocks cache is cleared.
|
||||
var removeBlocksCacheWhenFailed: Bool { get }
|
||||
|
||||
// When any action is created it can get `DIContainer` and resolve any depedencies it requires.
|
||||
// Every action uses `context` to get some informartion like download range.
|
||||
//
|
||||
// `didUpdate` is closure that action use to tell CBP that some part of the work is done. For example if download action would like to
|
||||
// update progress on every block downloaded it can use this closure. Also if action doesn't need to update progress on partial work it doesn't
|
||||
// need to use this closure at all.
|
||||
//
|
||||
// Each action updates context accordingly. It should at least set new state. Reason for this is that action can return different states for
|
||||
// different conditions. And action is the thing that knows these conditions.
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext
|
||||
|
||||
// Should be called on each existing action when processor wants to stop. Some actions may do it's own background work.
|
||||
func stop() async
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// 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 { }
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// ClearCacheForLastScannedBatch.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 08.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ClearAlreadyScannedBlocksAction {
|
||||
let storage: CompactBlockRepository
|
||||
let transactionRepository: TransactionRepository
|
||||
init(container: DIContainer) {
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ClearAlreadyScannedBlocksAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
try await storage.clear(upTo: lastScannedHeight)
|
||||
|
||||
await context.update(state: .enhance)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ClearCacheAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ClearCacheAction {
|
||||
let storage: CompactBlockRepository
|
||||
init(container: DIContainer) {
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ClearCacheAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
try await storage.clear()
|
||||
await context.update(state: .finished)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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 { }
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// DownloadAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class DownloadAction {
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let downloader: BlockDownloader
|
||||
let transactionRepository: TransactionRepository
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
downloader = container.resolve(BlockDownloader.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
|
||||
private func update(context: ActionContext) async -> ActionContext {
|
||||
await context.update(state: .validate)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
// 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)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let batchRange = batchRangeStart...batchRangeEnd
|
||||
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.setDownloadLimit(downloadLimit)
|
||||
await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
|
||||
|
||||
try await downloader.waitUntilRequestedBlocksAreDownloaded(in: batchRange)
|
||||
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
func stop() async {
|
||||
await downloader.stopDownload()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// EnhanceAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
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 {
|
||||
await context.update(state: .clearCache)
|
||||
return context
|
||||
}
|
||||
|
||||
if lastScannedHeight >= scanRange.upperBound {
|
||||
await context.update(state: .clearCache)
|
||||
} else {
|
||||
await context.update(state: .download)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
extension EnhanceAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
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
|
||||
// download and scan.
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
|
||||
guard let range = await context.syncRanges.enhanceRange 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)
|
||||
|
||||
// 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
|
||||
|
||||
if forceEnhance || (enhanceRangeStart <= enhanceRangeEnd && lastScannedHeight - lastEnhancedHeight >= config.enhanceBatchSize) {
|
||||
let enhanceRange = enhanceRangeStart...enhanceRangeEnd
|
||||
let transactions = try await blockEnhancer.enhance(
|
||||
at: enhanceRange,
|
||||
didEnhance: { progress in
|
||||
if let foundTx = progress.lastFoundTransaction, progress.newlyMined {
|
||||
await didUpdate(.minedTransaction(foundTx))
|
||||
await didUpdate(.progressPartialUpdate(.enhance(progress)))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if let transactions {
|
||||
await didUpdate(.foundTransactions(transactions, enhanceRange))
|
||||
}
|
||||
}
|
||||
|
||||
return await decideWhatToDoNext(context: context, lastScannedHeight: lastScannedHeight)
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// FetchUTXOsAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class FetchUTXOsAction {
|
||||
let utxoFetcher: UTXOFetcher
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer) {
|
||||
utxoFetcher = container.resolve(UTXOFetcher.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
await context.update(state: .handleSaplingParams)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// MigrateLegacyCacheDB.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 10.05.2023.
|
||||
//
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
private func updateState(_ context: ActionContext) async -> ActionContext {
|
||||
await context.update(state: .validateServer)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
extension MigrateLegacyCacheDBAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
let config = await configProvider.config
|
||||
guard let legacyCacheDbURL = config.cacheDbURL else {
|
||||
return await updateState(context)
|
||||
}
|
||||
|
||||
guard legacyCacheDbURL != config.fsBlockCacheRoot else {
|
||||
throw ZcashError.compactBlockProcessorCacheDbMigrationFsCacheMigrationFailedSameURL
|
||||
}
|
||||
|
||||
// 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.
|
||||
guard config.alias == .default else {
|
||||
return await updateState(context)
|
||||
}
|
||||
|
||||
// if the URL provided is not readable, it means that the client has a reference
|
||||
// to the cacheDb file but it has been deleted in a prior sync cycle. there's
|
||||
// nothing to do here.
|
||||
guard fileManager.isReadableFile(atPath: legacyCacheDbURL.path) else {
|
||||
return await updateState(context)
|
||||
}
|
||||
|
||||
do {
|
||||
// if there's a readable file at the provided URL, delete it.
|
||||
try fileManager.removeItem(at: legacyCacheDbURL)
|
||||
} catch {
|
||||
throw ZcashError.compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb(error)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func stop() { }
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// SaplingParamsAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class SaplingParamsAction {
|
||||
let saplingParametersHandler: SaplingParametersHandler
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer) {
|
||||
saplingParametersHandler = container.resolve(SaplingParametersHandler.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension SaplingParamsAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
logger.debug("Fetching sapling parameters")
|
||||
try await saplingParametersHandler.handleIfNeeded()
|
||||
await context.update(state: .download)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// ScanAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ScanAction {
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let blockScanner: BlockScanner
|
||||
let logger: Logger
|
||||
let transactionRepository: TransactionRepository
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
blockScanner = container.resolve(BlockScanner.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
|
||||
private func update(context: ActionContext) async -> ActionContext {
|
||||
await context.update(state: .clearAlreadyScannedBlocks)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
// 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)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let batchRange = batchRangeStart...batchRangeStart + config.batchSize
|
||||
|
||||
logger.debug("Starting scan blocks with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
|
||||
let totalProgressRange = await context.totalProgressRange
|
||||
try await blockScanner.scanBlocks(at: batchRange, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
|
||||
let progress = BlockProgress(
|
||||
startHeight: totalProgressRange.lowerBound,
|
||||
targetHeight: totalProgressRange.upperBound,
|
||||
progressHeight: lastScannedHeight
|
||||
)
|
||||
self?.logger.debug("progress: \(progress)")
|
||||
await didUpdate(.progressPartialUpdate(.syncing(progress)))
|
||||
}
|
||||
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ValidateAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ValidateAction {
|
||||
let validator: BlockValidator
|
||||
|
||||
init(container: DIContainer) {
|
||||
validator = container.resolve(BlockValidator.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ValidateAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { true }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
try await validator.validate()
|
||||
await context.update(state: .scan)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// ValidateServerAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ValidateServerAction {
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let service: LightWalletService
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
rustBackend = container.resolve(ZcashRustBackendWelding.self)
|
||||
service = container.resolve(LightWalletService.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ValidateServerAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
let config = await configProvider.config
|
||||
let info = try await service.getInfo()
|
||||
let localNetwork = config.network
|
||||
let saplingActivation = config.saplingActivation
|
||||
|
||||
// check network types
|
||||
guard let remoteNetworkType = NetworkType.forChainName(info.chainName) else {
|
||||
throw ZcashError.compactBlockProcessorChainName(info.chainName)
|
||||
}
|
||||
|
||||
guard remoteNetworkType == localNetwork.networkType else {
|
||||
throw ZcashError.compactBlockProcessorNetworkMismatch(localNetwork.networkType, remoteNetworkType)
|
||||
}
|
||||
|
||||
guard saplingActivation == info.saplingActivationHeight else {
|
||||
throw ZcashError.compactBlockProcessorSaplingActivationMismatch(saplingActivation, BlockHeight(info.saplingActivationHeight))
|
||||
}
|
||||
|
||||
// check branch id
|
||||
let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight))
|
||||
|
||||
guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID) else {
|
||||
throw ZcashError.compactBlockProcessorConsensusBranchID
|
||||
}
|
||||
|
||||
guard remoteBranchID == localBranch else {
|
||||
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
|
||||
}
|
||||
|
||||
await context.update(state: .computeSyncRanges)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -250,6 +250,7 @@ extension BlockDownloaderImpl: BlockDownloader {
|
|||
}
|
||||
|
||||
func setSyncRange(_ range: CompactBlockRange, batchSize: Int) async throws {
|
||||
guard range != syncRange else { return }
|
||||
downloadStream = nil
|
||||
self.batchSize = batchSize
|
||||
syncRange = range
|
||||
|
|
|
@ -7,8 +7,52 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct EnhancementProgress: Equatable {
|
||||
/// total transactions that were detected in the `range`
|
||||
public let totalTransactions: Int
|
||||
/// enhanced transactions so far
|
||||
public let enhancedTransactions: Int
|
||||
/// last found transaction
|
||||
public let lastFoundTransaction: ZcashTransaction.Overview?
|
||||
/// block range that's being enhanced
|
||||
public let range: CompactBlockRange
|
||||
/// whether this transaction can be considered `newly mined` and not part of the
|
||||
/// wallet catching up to stale and uneventful blocks.
|
||||
public let newlyMined: Bool
|
||||
|
||||
public init(
|
||||
totalTransactions: Int,
|
||||
enhancedTransactions: Int,
|
||||
lastFoundTransaction: ZcashTransaction.Overview?,
|
||||
range: CompactBlockRange,
|
||||
newlyMined: Bool
|
||||
) {
|
||||
self.totalTransactions = totalTransactions
|
||||
self.enhancedTransactions = enhancedTransactions
|
||||
self.lastFoundTransaction = lastFoundTransaction
|
||||
self.range = range
|
||||
self.newlyMined = newlyMined
|
||||
}
|
||||
|
||||
public var progress: Float {
|
||||
totalTransactions > 0 ? Float(enhancedTransactions) / Float(totalTransactions) : 0
|
||||
}
|
||||
|
||||
public static var zero: EnhancementProgress {
|
||||
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0, newlyMined: false)
|
||||
}
|
||||
|
||||
public static func == (lhs: EnhancementProgress, rhs: EnhancementProgress) -> Bool {
|
||||
return
|
||||
lhs.totalTransactions == rhs.totalTransactions &&
|
||||
lhs.enhancedTransactions == rhs.enhancedTransactions &&
|
||||
lhs.lastFoundTransaction?.id == rhs.lastFoundTransaction?.id &&
|
||||
lhs.range == rhs.range
|
||||
}
|
||||
}
|
||||
|
||||
protocol BlockEnhancer {
|
||||
func enhance(at range: CompactBlockRange, didEnhance: (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview]?
|
||||
func enhance(at range: CompactBlockRange, didEnhance: @escaping (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview]?
|
||||
}
|
||||
|
||||
struct BlockEnhancerImpl {
|
||||
|
@ -38,7 +82,7 @@ struct BlockEnhancerImpl {
|
|||
}
|
||||
|
||||
extension BlockEnhancerImpl: BlockEnhancer {
|
||||
func enhance(at range: CompactBlockRange, didEnhance: (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview]? {
|
||||
func enhance(at range: CompactBlockRange, didEnhance: @escaping (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview]? {
|
||||
try Task.checkCancellation()
|
||||
|
||||
logger.debug("Started Enhancing range: \(range)")
|
||||
|
|
|
@ -19,7 +19,7 @@ struct UTXOFetcherConfig {
|
|||
protocol UTXOFetcher {
|
||||
func fetch(
|
||||
at range: CompactBlockRange,
|
||||
didFetch: (Float) async -> Void
|
||||
didFetch: @escaping (Float) async -> Void
|
||||
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ struct UTXOFetcherImpl {
|
|||
extension UTXOFetcherImpl: UTXOFetcher {
|
||||
func fetch(
|
||||
at range: CompactBlockRange,
|
||||
didFetch: (Float) async -> Void
|
||||
didFetch: @escaping (Float) async -> Void
|
||||
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
|
||||
try Task.checkCancellation()
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// CompactBlockProgress.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 11.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final actor CompactBlockProgress {
|
||||
static let zero = CompactBlockProgress()
|
||||
|
||||
enum Action: Equatable {
|
||||
case enhance
|
||||
case fetch
|
||||
case scan
|
||||
|
||||
func weight() -> Float {
|
||||
switch self {
|
||||
case .enhance: return 0.08
|
||||
case .fetch: return 0.02
|
||||
case .scan: return 0.9
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var actionProgresses: [Action: Float] = [:]
|
||||
|
||||
var progress: Float {
|
||||
var overallProgress = Float(0)
|
||||
actionProgresses.forEach { key, value in
|
||||
overallProgress += value * key.weight()
|
||||
}
|
||||
|
||||
return overallProgress
|
||||
}
|
||||
|
||||
func event(_ event: CompactBlockProcessor.Event) -> Bool {
|
||||
guard case .progressPartialUpdate(let update) = event else {
|
||||
return false
|
||||
}
|
||||
|
||||
switch update {
|
||||
case .syncing(let progress):
|
||||
actionProgresses[.scan] = progress.progress
|
||||
case .enhance(let progress):
|
||||
actionProgresses[.enhance] = progress.progress
|
||||
case .fetch(let progress):
|
||||
actionProgresses[.fetch] = progress
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func reset() {
|
||||
actionProgresses.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
enum CompactBlockProgressUpdate: Equatable {
|
||||
case syncing(_ progress: BlockProgress)
|
||||
case enhance(_ progress: EnhancementProgress)
|
||||
case fetch(_ progress: Float)
|
||||
}
|
|
@ -9,20 +9,35 @@ import Foundation
|
|||
|
||||
struct SyncRanges: Equatable {
|
||||
let latestBlockHeight: BlockHeight
|
||||
/// The sync process can be interrupted in any phase. It may happen that it's interrupted while downloading blocks. In that case in next sync
|
||||
/// process already downloaded blocks needs to be scanned before the sync process starts to download new blocks. And the range of blocks that are
|
||||
/// already downloaded but not scanned is stored in this variable.
|
||||
let downloadedButUnscannedRange: CompactBlockRange?
|
||||
/// Range of blocks that are not yet downloaded and not yet scanned.
|
||||
let downloadAndScanRange: CompactBlockRange?
|
||||
// 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 {
|
||||
|
@ -30,6 +45,7 @@ protocol InternalSyncProgressStorage {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -132,7 +148,7 @@ actor InternalSyncProgress {
|
|||
latestBlockHeight: BlockHeight,
|
||||
latestScannedHeight: BlockHeight,
|
||||
walletBirthday: BlockHeight
|
||||
) async throws -> CompactBlockProcessor.NextState {
|
||||
) async throws -> NextState {
|
||||
let latestDownloadedBlockHeight = try await self.latestDownloadedBlockHeight
|
||||
let latestEnhancedHeight = try await self.latestEnhancedHeight
|
||||
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
|
||||
|
@ -174,15 +190,6 @@ actor InternalSyncProgress {
|
|||
let latestEnhancedHeight = try await self.latestEnhancedHeight
|
||||
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
|
||||
|
||||
// If there is more downloaded then scanned blocks we have to range for these blocks. The sync process will then start with scanning these
|
||||
// blocks instead of downloading new ones.
|
||||
let downloadedButUnscannedRange: CompactBlockRange?
|
||||
if latestScannedHeight < latestDownloadedBlockHeight {
|
||||
downloadedButUnscannedRange = latestScannedHeight + 1...latestDownloadedBlockHeight
|
||||
} else {
|
||||
downloadedButUnscannedRange = nil
|
||||
}
|
||||
|
||||
if latestScannedHeight > latestDownloadedBlockHeight {
|
||||
logger.warn("""
|
||||
InternalSyncProgress found inconsistent state.
|
||||
|
@ -191,24 +198,16 @@ actor InternalSyncProgress {
|
|||
latestScannedHeight: \(latestScannedHeight)
|
||||
latestEnhancedHeight: \(latestEnhancedHeight)
|
||||
latestUTXOFetchedHeight: \(latestUTXOFetchedHeight)
|
||||
|
||||
latest downloaded height
|
||||
""")
|
||||
}
|
||||
|
||||
// compute the range that must be downloaded and scanned based on
|
||||
// birthday, `latestDownloadedBlockHeight`, `latestScannedHeight` and
|
||||
// latest block height fetched from the chain.
|
||||
let downloadAndScanRange = computeRange(
|
||||
latestHeight: max(latestDownloadedBlockHeight, latestScannedHeight),
|
||||
birthday: birthday,
|
||||
latestBlockHeight: latestBlockHeight
|
||||
)
|
||||
let downloadRange = computeRange(latestHeight: latestDownloadedBlockHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
|
||||
let scanRange = computeRange(latestHeight: latestScannedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
|
||||
|
||||
return SyncRanges(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadedButUnscannedRange: downloadedButUnscannedRange,
|
||||
downloadAndScanRange: downloadAndScanRange,
|
||||
downloadRange: downloadRange,
|
||||
scanRange: scanRange,
|
||||
enhanceRange: computeRange(latestHeight: latestEnhancedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
|
||||
fetchUTXORange: computeRange(latestHeight: latestUTXOFetchedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
|
||||
latestScannedHeight: latestScannedHeight,
|
||||
|
|
|
@ -88,17 +88,10 @@ public enum ZcashSDK {
|
|||
// MARK: Defaults
|
||||
|
||||
/// Default size of batches of blocks to request from the compact block service. Which was used both for scanning and downloading.
|
||||
/// consider basing your code assumptions on `DefaultDownloadBatch` and `DefaultScanningBatch` instead.
|
||||
@available(*, deprecated, message: "this value is being deprecated in favor of `DefaultDownloadBatch` and `DefaultScanningBatch`")
|
||||
public static let DefaultBatchSize = 100
|
||||
|
||||
/// Default batch size for downloading blocks for the compact block processor. Be careful with this number. This amount of blocks is held in
|
||||
/// memory at some point of the sync process.
|
||||
/// This values can't be smaller than `DefaultScanningBatch`. Otherwise bad things will happen.
|
||||
public static let DefaultDownloadBatch = 100
|
||||
|
||||
/// Default batch size for scanning blocks for the compact block processor
|
||||
public static let DefaultScanningBatch = 100
|
||||
/// Default batch size for enhancing transactions for the compact block processor
|
||||
public static let DefaultEnhanceBatch = 1000
|
||||
|
||||
/// Default amount of time, in in seconds, to poll for new blocks. Typically, this should be about half the average
|
||||
/// block time.
|
||||
|
|
|
@ -180,6 +180,7 @@ protocol LightWalletService: AnyObject {
|
|||
func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched
|
||||
|
||||
/// - Throws: `serviceFetchUTXOsFailed` when GRPC call fails.
|
||||
// sourcery: mockedName="fetchUTXOsSingle"
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
|
||||
|
||||
/// - Throws: `serviceFetchUTXOsFailed` when GRPC call fails.
|
||||
|
|
|
@ -366,14 +366,8 @@ enum InternalSyncStatus: Equatable {
|
|||
/// taking other maintenance steps that need to occur after an upgrade.
|
||||
case unprepared
|
||||
|
||||
case syncing(_ progress: BlockProgress)
|
||||
|
||||
/// Indicates that this Synchronizer is actively enhancing newly scanned blocks
|
||||
/// with additional transaction details, fetched from the server.
|
||||
case enhancing(_ progress: EnhancementProgress)
|
||||
|
||||
/// fetches the transparent balance and stores it locally
|
||||
case fetching(_ progress: Float)
|
||||
/// Indicates that this Synchronizer is actively processing new blocks (consists of fetch, scan and enhance operations)
|
||||
case syncing(Float)
|
||||
|
||||
/// Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
|
||||
/// When set, a UI element may want to turn green.
|
||||
|
@ -390,7 +384,7 @@ enum InternalSyncStatus: Equatable {
|
|||
|
||||
public var isSyncing: Bool {
|
||||
switch self {
|
||||
case .syncing, .enhancing, .fetching:
|
||||
case .syncing:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -416,8 +410,6 @@ enum InternalSyncStatus: Equatable {
|
|||
switch self {
|
||||
case .unprepared: return "unprepared"
|
||||
case .syncing: return "syncing"
|
||||
case .enhancing: return "enhancing"
|
||||
case .fetching: return "fetching"
|
||||
case .synced: return "synced"
|
||||
case .stopped: return "stopped"
|
||||
case .disconnected: return "disconnected"
|
||||
|
@ -449,8 +441,6 @@ extension InternalSyncStatus {
|
|||
switch (lhs, rhs) {
|
||||
case (.unprepared, .unprepared): return true
|
||||
case let (.syncing(lhsProgress), .syncing(rhsProgress)): return lhsProgress == rhsProgress
|
||||
case let (.enhancing(lhsProgress), .enhancing(rhsProgress)): return lhsProgress == rhsProgress
|
||||
case (.fetching, .fetching): return true
|
||||
case (.synced, .synced): return true
|
||||
case (.stopped, .stopped): return true
|
||||
case (.disconnected, .disconnected): return true
|
||||
|
@ -461,15 +451,8 @@ extension InternalSyncStatus {
|
|||
}
|
||||
|
||||
extension InternalSyncStatus {
|
||||
init(_ blockProcessorProgress: CompactBlockProgress) {
|
||||
switch blockProcessorProgress {
|
||||
case .syncing(let progressReport):
|
||||
self = .syncing(progressReport)
|
||||
case .enhance(let enhancingReport):
|
||||
self = .enhancing(enhancingReport)
|
||||
case .fetch(let fetchingProgress):
|
||||
self = .fetching(fetchingProgress)
|
||||
}
|
||||
init(_ blockProcessorProgress: Float) {
|
||||
self = .syncing(blockProcessorProgress)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,11 +462,7 @@ extension InternalSyncStatus {
|
|||
case .unprepared:
|
||||
return .unprepared
|
||||
case .syncing(let progress):
|
||||
return .syncing(0.9 * progress.progress)
|
||||
case .enhancing(let progress):
|
||||
return .syncing(0.9 + 0.08 * progress.progress)
|
||||
case .fetching(let progress):
|
||||
return .syncing(0.98 + 0.02 * progress)
|
||||
return .syncing(progress)
|
||||
case .synced:
|
||||
return .upToDate
|
||||
case .stopped:
|
||||
|
|
|
@ -94,6 +94,10 @@ enum Dependencies {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
||||
static func setupCompactBlockProcessor(
|
||||
|
@ -140,7 +144,7 @@ enum Dependencies {
|
|||
|
||||
let blockScannerConfig = BlockScannerConfig(
|
||||
networkType: config.network.networkType,
|
||||
scanningBatchSize: config.scanningBatchSize
|
||||
scanningBatchSize: config.batchSize
|
||||
)
|
||||
|
||||
return BlockScannerImpl(
|
||||
|
|
|
@ -160,14 +160,14 @@ public class SDKSynchronizer: Synchronizer {
|
|||
case .unprepared:
|
||||
throw ZcashError.synchronizerNotPrepared
|
||||
|
||||
case .syncing, .enhancing, .fetching:
|
||||
case .syncing:
|
||||
logger.warn("warning: Synchronizer started when already running. Next sync process will be started when the current one stops.")
|
||||
/// This may look strange but `CompactBlockProcessor` has mechanisms which can handle this situation. So we are fine with calling
|
||||
/// it's start here.
|
||||
await blockProcessor.start(retry: retry)
|
||||
|
||||
case .stopped, .synced, .disconnected, .error:
|
||||
await updateStatus(.syncing(.nullProgress))
|
||||
await updateStatus(.syncing(0))
|
||||
syncStartDate = Date()
|
||||
await blockProcessor.start(retry: retry)
|
||||
}
|
||||
|
@ -200,15 +200,14 @@ public class SDKSynchronizer: Synchronizer {
|
|||
|
||||
// MARK: Handle CompactBlockProcessor.Flow
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func subscribeToProcessorEvents(_ processor: CompactBlockProcessor) async {
|
||||
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
||||
switch event {
|
||||
case let .failed(error):
|
||||
await self?.failed(error: error)
|
||||
|
||||
case let .finished(height, foundBlocks):
|
||||
await self?.finished(lastScannedHeight: height, foundBlocks: foundBlocks)
|
||||
case let .finished(height):
|
||||
await self?.finished(lastScannedHeight: height)
|
||||
|
||||
case let .foundTransactions(transactions, range):
|
||||
self?.foundTransactions(transactions: transactions, in: range)
|
||||
|
@ -220,17 +219,14 @@ public class SDKSynchronizer: Synchronizer {
|
|||
case let .progressUpdated(progress):
|
||||
await self?.progressUpdated(progress: progress)
|
||||
|
||||
case .progressPartialUpdate:
|
||||
break
|
||||
|
||||
case let .storedUTXOs(utxos):
|
||||
self?.storedUTXOs(utxos: utxos)
|
||||
|
||||
case .startedEnhancing:
|
||||
await self?.updateStatus(.enhancing(.zero))
|
||||
|
||||
case .startedFetching:
|
||||
await self?.updateStatus(.fetching(0))
|
||||
|
||||
case .startedSyncing:
|
||||
await self?.updateStatus(.syncing(.nullProgress))
|
||||
case .startedEnhancing, .startedFetching, .startedSyncing:
|
||||
break
|
||||
|
||||
case .stopped:
|
||||
await self?.updateStatus(.stopped)
|
||||
|
@ -247,7 +243,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
await updateStatus(.error(error))
|
||||
}
|
||||
|
||||
private func finished(lastScannedHeight: BlockHeight, foundBlocks: Bool) async {
|
||||
private func finished(lastScannedHeight: BlockHeight) async {
|
||||
await latestBlocksDataProvider.updateScannedData()
|
||||
|
||||
await updateStatus(.synced)
|
||||
|
@ -266,7 +262,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
}
|
||||
}
|
||||
|
||||
private func progressUpdated(progress: CompactBlockProgress) async {
|
||||
private func progressUpdated(progress: Float) async {
|
||||
let newStatus = InternalSyncStatus(progress)
|
||||
await updateStatus(newStatus)
|
||||
}
|
||||
|
@ -407,7 +403,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
}
|
||||
|
||||
public func latestHeight() async throws -> BlockHeight {
|
||||
try await blockProcessor.blockDownloaderService.latestBlockHeight()
|
||||
try await blockProcessor.latestHeight()
|
||||
}
|
||||
|
||||
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
|
||||
|
@ -640,8 +636,6 @@ extension InternalSyncStatus {
|
|||
switch (self, otherStatus) {
|
||||
case (.unprepared, .unprepared): return false
|
||||
case (.syncing, .syncing): return false
|
||||
case (.enhancing, .enhancing): return false
|
||||
case (.fetching, .fetching): return false
|
||||
case (.synced, .synced): return false
|
||||
case (.stopped, .stopped): return false
|
||||
case (.disconnected, .disconnected): return false
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// ZcashFileManager.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 23.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ZcashFileManager {
|
||||
func isReadableFile(atPath path: String) -> Bool
|
||||
func removeItem(at URL: URL) throws
|
||||
func isDeletableFile(atPath path: String) -> Bool
|
||||
}
|
||||
|
||||
extension FileManager: ZcashFileManager { }
|
|
@ -59,7 +59,7 @@ class SDKSynchronizerAliasDarksideTests: ZcashTestCase {
|
|||
endpoint: endpoint
|
||||
)
|
||||
|
||||
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
|
||||
try await coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
|
||||
|
||||
coordinators.append(coordinator)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class AdvancedReOrgTests: ZcashTestCase {
|
|||
walletBirthday: birthday + 50,
|
||||
network: network
|
||||
)
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
@ -453,7 +453,7 @@ class AdvancedReOrgTests: ZcashTestCase {
|
|||
await hookToReOrgNotification()
|
||||
self.expectedReorgHeight = 663196
|
||||
self.expectedRewindHeight = 663175
|
||||
try coordinator.reset(saplingActivation: birthday, branchID: "2bb40e60", chainName: "main")
|
||||
try await coordinator.reset(saplingActivation: birthday, branchID: "2bb40e60", chainName: "main")
|
||||
try coordinator.resetBlocks(dataset: .predefined(dataset: .txIndexChangeBefore))
|
||||
try coordinator.applyStaged(blockheight: 663195)
|
||||
sleep(1)
|
||||
|
@ -1031,7 +1031,7 @@ class AdvancedReOrgTests: ZcashTestCase {
|
|||
/// 8. sync to latest height
|
||||
/// 9. verify that the balance is equal to the one before the reorg
|
||||
func testReOrgChangesInboundMinedHeight() async throws {
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName)
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName)
|
||||
sleep(2)
|
||||
try coordinator.resetBlocks(dataset: .predefined(dataset: .txHeightReOrgBefore))
|
||||
sleep(2)
|
||||
|
@ -1096,7 +1096,7 @@ class AdvancedReOrgTests: ZcashTestCase {
|
|||
// FIXME [#644]: Test works with lightwalletd v0.4.13 but is broken when using newer lightwalletd. More info is in #644.
|
||||
func testReOrgRemovesIncomingTxForever() async throws {
|
||||
await hookToReOrgNotification()
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName)
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName)
|
||||
|
||||
try coordinator.resetBlocks(dataset: .predefined(dataset: .txReOrgRemovesInboundTxBefore))
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class BalanceTests: ZcashTestCase {
|
|||
walletBirthday: birthday,
|
||||
network: network
|
||||
)
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
|
|
@ -33,7 +33,7 @@ class DarksideSanityCheckTests: ZcashTestCase {
|
|||
network: network
|
||||
)
|
||||
|
||||
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
try self.coordinator.resetBlocks(dataset: .default)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
//
|
||||
// InternalStateConsistencyTests.swift
|
||||
// DarksideTests
|
||||
//
|
||||
// Created by Francisco Gindre on 1/26/23.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class InternalStateConsistencyTests: ZcashTestCase {
|
||||
let sendAmount = Zatoshi(1000)
|
||||
var birthday: BlockHeight = 663150
|
||||
let defaultLatestHeight: BlockHeight = 663175
|
||||
var coordinator: TestCoordinator!
|
||||
var firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
var expectedReorgHeight: BlockHeight = 665188
|
||||
var expectedRewindHeight: BlockHeight = 665188
|
||||
var reorgExpectation = XCTestExpectation(description: "reorg")
|
||||
let branchID = "2bb40e60"
|
||||
let chainName = "main"
|
||||
let network = DarksideWalletDNetwork()
|
||||
var sdkSynchronizerInternalSyncStatusHandler: SDKSynchronizerInternalSyncStatusHandler! = SDKSynchronizerInternalSyncStatusHandler()
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
// don't use an exact birthday, users never do.
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday + 50,
|
||||
network: network
|
||||
)
|
||||
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
try await super.tearDown()
|
||||
let coordinator = self.coordinator!
|
||||
self.coordinator = nil
|
||||
sdkSynchronizerInternalSyncStatusHandler = nil
|
||||
|
||||
try await coordinator.stop()
|
||||
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
|
||||
try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
|
||||
}
|
||||
|
||||
func testInternalStateIsConsistentWhenMigrating() async throws {
|
||||
sdkSynchronizerInternalSyncStatusHandler.subscribe(
|
||||
to: coordinator.synchronizer.stateStream,
|
||||
expectations: [.stopped: firstSyncExpectation]
|
||||
)
|
||||
|
||||
let fullSyncLength = 10000
|
||||
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: fullSyncLength)
|
||||
|
||||
sleep(1)
|
||||
|
||||
// apply the height
|
||||
try coordinator.applyStaged(blockheight: 664150)
|
||||
|
||||
sleep(1)
|
||||
|
||||
try await coordinator.sync(
|
||||
completion: { _ in
|
||||
XCTFail("shouldn't have completed")
|
||||
},
|
||||
error: handleError
|
||||
)
|
||||
|
||||
let coordinator = self.coordinator!
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
|
||||
Task(priority: .userInitiated) {
|
||||
coordinator.synchronizer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
await fulfillment(of: [firstSyncExpectation], timeout: 2)
|
||||
|
||||
let isSyncing = await coordinator.synchronizer.status.isSyncing
|
||||
let status = await coordinator.synchronizer.status
|
||||
XCTAssertFalse(isSyncing, "SDKSynchronizer shouldn't be syncing")
|
||||
XCTAssertEqual(status, .stopped)
|
||||
|
||||
let internalSyncState = coordinator.synchronizer.internalSyncProgress
|
||||
|
||||
let latestDownloadHeight = try await internalSyncState.latestDownloadedBlockHeight
|
||||
let latestScanHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
|
||||
let dbHandle = TestDbHandle(originalDb: TestDbBuilder.prePopulatedDarksideCacheDb()!)
|
||||
try dbHandle.setUp()
|
||||
|
||||
if latestDownloadHeight > latestScanHeight {
|
||||
try await coordinator.synchronizer.blockProcessor.migrateCacheDb(dbHandle.readWriteDb)
|
||||
|
||||
let afterMigrationDownloadedHeight = try await internalSyncState.latestDownloadedBlockHeight
|
||||
|
||||
XCTAssertNotEqual(latestDownloadHeight, afterMigrationDownloadedHeight)
|
||||
XCTAssertEqual(latestScanHeight, afterMigrationDownloadedHeight)
|
||||
} else {
|
||||
try await coordinator.synchronizer.blockProcessor.migrateCacheDb(dbHandle.readWriteDb)
|
||||
|
||||
let afterMigrationDownloadedHeight = try await internalSyncState.latestDownloadedBlockHeight
|
||||
|
||||
XCTAssertEqual(latestDownloadHeight, afterMigrationDownloadedHeight)
|
||||
XCTAssertEqual(latestScanHeight, afterMigrationDownloadedHeight)
|
||||
}
|
||||
|
||||
XCTAssertFalse(FileManager.default.isReadableFile(atPath: dbHandle.readWriteDb.path))
|
||||
|
||||
// clear to simulate a clean slate from the FsBlockDb
|
||||
try await coordinator.synchronizer.blockProcessor.storage.clear()
|
||||
|
||||
// Now let's resume scanning and see how it goes.
|
||||
let secondSyncAttemptExpectation = XCTestExpectation(description: "second sync attempt")
|
||||
|
||||
do {
|
||||
try await coordinator.sync(
|
||||
completion: { _ in
|
||||
XCTAssertTrue(true)
|
||||
secondSyncAttemptExpectation.fulfill()
|
||||
},
|
||||
error: { [weak self] error in
|
||||
secondSyncAttemptExpectation.fulfill()
|
||||
self?.handleError(error)
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
handleError(error)
|
||||
}
|
||||
|
||||
await fulfillment(of: [secondSyncAttemptExpectation], timeout: 10)
|
||||
}
|
||||
|
||||
func handleError(_ error: Error?) {
|
||||
guard let testError = error else {
|
||||
XCTFail("failed with nil error")
|
||||
return
|
||||
}
|
||||
XCTFail("Failed with error: \(testError)")
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ class PendingTransactionUpdatesTest: ZcashTestCase {
|
|||
walletBirthday: birthday,
|
||||
network: network
|
||||
)
|
||||
try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
|
|
@ -50,7 +50,7 @@ class ReOrgTests: ZcashTestCase {
|
|||
network: self.network
|
||||
)
|
||||
|
||||
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
|
||||
try self.coordinator.resetBlocks(dataset: .default)
|
||||
|
||||
|
@ -128,7 +128,7 @@ class ReOrgTests: ZcashTestCase {
|
|||
targetHeight: BlockHeight
|
||||
) async throws {
|
||||
do {
|
||||
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
|
||||
try await coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
|
||||
try coordinator.resetBlocks(dataset: .predefined(dataset: .beforeReOrg))
|
||||
try coordinator.applyStaged(blockheight: firstLatestHeight)
|
||||
sleep(1)
|
||||
|
|
|
@ -35,7 +35,7 @@ class RewindRescanTests: ZcashTestCase {
|
|||
walletBirthday: birthday,
|
||||
network: network
|
||||
)
|
||||
try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
|
|
@ -30,7 +30,7 @@ class ShieldFundsTests: ZcashTestCase {
|
|||
walletBirthday: birthday,
|
||||
network: network
|
||||
)
|
||||
try coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName)
|
||||
try coordinator.service.clearAddedUTXOs()
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
network: network
|
||||
)
|
||||
|
||||
try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
@ -195,7 +195,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[0],
|
||||
shieldedBalance: .zero,
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
|
||||
internalSyncStatus: .syncing(0),
|
||||
latestScannedHeight: 663150,
|
||||
latestBlockHeight: 0,
|
||||
latestScannedTime: 1576821833
|
||||
|
@ -204,7 +204,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
|
||||
internalSyncStatus: .syncing(0.9),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
|
@ -213,84 +213,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0, newlyMined: false)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 2,
|
||||
enhancedTransactions: 1,
|
||||
lastFoundTransaction: ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663206,
|
||||
fee: Zatoshi(0),
|
||||
id: 2,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663188,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
),
|
||||
range: 663150...663189,
|
||||
newlyMined: true
|
||||
)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 2,
|
||||
enhancedTransactions: 2,
|
||||
lastFoundTransaction: ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663192,
|
||||
fee: Zatoshi(0),
|
||||
id: 1,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663174,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
),
|
||||
range: 663150...663189,
|
||||
newlyMined: true
|
||||
)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .fetching(0),
|
||||
internalSyncStatus: .syncing(1.0),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
|
@ -359,7 +282,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[0],
|
||||
shieldedBalance: .zero,
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
|
||||
internalSyncStatus: .syncing(0),
|
||||
latestScannedHeight: 663150,
|
||||
latestBlockHeight: 0,
|
||||
latestScannedTime: 1576821833.0
|
||||
|
@ -368,7 +291,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
|
||||
internalSyncStatus: .syncing(0.9),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
|
@ -376,85 +299,8 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0, newlyMined: false)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 2,
|
||||
enhancedTransactions: 1,
|
||||
lastFoundTransaction: ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663206,
|
||||
fee: nil,
|
||||
id: 2,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663188,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
),
|
||||
range: 663150...663189,
|
||||
newlyMined: true
|
||||
)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 2,
|
||||
enhancedTransactions: 2,
|
||||
lastFoundTransaction: ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663192,
|
||||
fee: nil,
|
||||
id: 1,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663174,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
),
|
||||
range: 663150...663189,
|
||||
newlyMined: true
|
||||
)
|
||||
),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[0],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .fetching(0),
|
||||
transparentBalance: .zero,
|
||||
internalSyncStatus: .syncing(1.0),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1
|
||||
|
@ -499,7 +345,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[1],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
|
||||
internalSyncStatus: .syncing(0),
|
||||
latestScannedHeight: 663189,
|
||||
latestBlockHeight: 663189,
|
||||
latestScannedTime: 1.0
|
||||
|
@ -508,7 +354,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[1],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)),
|
||||
internalSyncStatus: .syncing(0.9),
|
||||
latestScannedHeight: 663200,
|
||||
latestBlockHeight: 663200,
|
||||
latestScannedTime: 1
|
||||
|
@ -517,18 +363,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
syncSessionID: uuids[1],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .enhancing(
|
||||
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0, newlyMined: true)
|
||||
),
|
||||
latestScannedHeight: 663200,
|
||||
latestBlockHeight: 663200,
|
||||
latestScannedTime: 1
|
||||
),
|
||||
SynchronizerState(
|
||||
syncSessionID: uuids[1],
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
|
||||
internalSyncStatus: .fetching(0),
|
||||
internalSyncStatus: .syncing(1.0),
|
||||
latestScannedHeight: 663200,
|
||||
latestBlockHeight: 663200,
|
||||
latestScannedTime: 1
|
||||
|
|
|
@ -33,7 +33,7 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
walletBirthday: birthday + 50,
|
||||
network: network
|
||||
)
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
|
||||
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
||||
switch event {
|
||||
|
@ -101,8 +101,6 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
|
||||
let status = await coordinator.synchronizer.status
|
||||
XCTAssertEqual(status, .stopped)
|
||||
let state = await coordinator.synchronizer.blockProcessor.state
|
||||
XCTAssertEqual(state, .stopped)
|
||||
}
|
||||
|
||||
// MARK: Wipe tests
|
||||
|
@ -188,8 +186,13 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
try await Task.sleep(nanoseconds: 2_000_000_000)
|
||||
|
||||
// Just to be sure that blockProcessor is still syncing and that this test does what it should.
|
||||
let blockProcessorState = await coordinator.synchronizer.blockProcessor.state
|
||||
XCTAssertEqual(blockProcessorState, .syncing)
|
||||
let synchronizerState = coordinator.synchronizer.latestState.syncStatus
|
||||
switch synchronizerState {
|
||||
case .syncing:
|
||||
break
|
||||
default:
|
||||
XCTFail("Synchornizer should be in syncing state.")
|
||||
}
|
||||
|
||||
let wipeFinished = XCTestExpectation(description: "SynchronizerWipeFinished Expectation")
|
||||
/*
|
||||
|
@ -223,7 +226,6 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
private func checkThatWipeWorked() async throws {
|
||||
let storage = await self.coordinator.synchronizer.blockProcessor.storage as! FSCompactBlockRepository
|
||||
let fm = FileManager.default
|
||||
print(coordinator.synchronizer.initializer.dataDbURL.path)
|
||||
|
||||
XCTAssertFalse(fm.fileExists(atPath: coordinator.synchronizer.initializer.dataDbURL.path), "Data DB should be deleted.")
|
||||
XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist")
|
||||
|
@ -239,9 +241,6 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
XCTAssertEqual(latestEnhancedHeight, 0, "internalSyncProgress latestEnhancedHeight should be 0")
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 0, "internalSyncProgress latestUTXOFetchedHeight should be 0")
|
||||
|
||||
let blockProcessorState = await coordinator.synchronizer.blockProcessor.state
|
||||
XCTAssertEqual(blockProcessorState, .stopped, "CompactBlockProcessor state should be stopped")
|
||||
|
||||
let status = await coordinator.synchronizer.status
|
||||
XCTAssertEqual(status, .unprepared, "SDKSynchronizer state should be unprepared")
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class Z2TReceiveTests: ZcashTestCase {
|
|||
walletBirthday: birthday,
|
||||
network: network
|
||||
)
|
||||
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
//
|
||||
// BlockScanTests.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/17/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import XCTest
|
||||
import SQLite
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class BlockScanTests: ZcashTestCase {
|
||||
var cancelables: [AnyCancellable] = []
|
||||
|
||||
var dataDbURL: URL!
|
||||
var spendParamsURL: URL!
|
||||
var outputParamsURL: URL!
|
||||
// swiftlint:disable:next line_length
|
||||
var saplingExtendedKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u")
|
||||
|
||||
var walletBirthDay = Checkpoint.birthday(
|
||||
with: 1386000,
|
||||
network: ZcashNetworkBuilder.network(for: .testnet)
|
||||
)
|
||||
|
||||
var rustBackend: ZcashRustBackendWelding!
|
||||
|
||||
var network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
var blockRepository: BlockRepository!
|
||||
|
||||
let testFileManager = FileManager()
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
logger = OSLogger(logLevel: .debug)
|
||||
dataDbURL = try! __dataDbURL()
|
||||
spendParamsURL = try! __spendParamsURL()
|
||||
outputParamsURL = try! __outputParamsURL()
|
||||
|
||||
rustBackend = ZcashRustBackend.makeForTests(
|
||||
dbData: dataDbURL,
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
networkType: network.networkType
|
||||
)
|
||||
|
||||
deleteDBs()
|
||||
|
||||
Dependencies.setup(
|
||||
in: mockContainer,
|
||||
urls: Initializer.URLs(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
dataDbURL: dataDbURL,
|
||||
generalStorageURL: testGeneralStorageDirectory,
|
||||
spendParamsURL: spendParamsURL,
|
||||
outputParamsURL: outputParamsURL
|
||||
),
|
||||
alias: .default,
|
||||
networkType: .testnet,
|
||||
endpoint: LightWalletEndpointBuilder.default,
|
||||
loggingPolicy: .default(.debug)
|
||||
)
|
||||
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in LatestBlocksDataProviderMock() }
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackend }
|
||||
}
|
||||
|
||||
private func deleteDBs() {
|
||||
try? FileManager.default.removeItem(at: dataDbURL)
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
try super.tearDownWithError()
|
||||
try? testFileManager.removeItem(at: dataDbURL)
|
||||
try? testFileManager.removeItem(at: spendParamsURL)
|
||||
try? testFileManager.removeItem(at: outputParamsURL)
|
||||
cancelables = []
|
||||
blockRepository = nil
|
||||
testTempDirectory = nil
|
||||
}
|
||||
|
||||
func testSingleDownloadAndScan() async throws {
|
||||
_ = try await rustBackend.initDataDb(seed: nil)
|
||||
|
||||
let endpoint = LightWalletEndpoint(address: "lightwalletd.testnet.electriccoin.co", port: 9067)
|
||||
let blockCount = 100
|
||||
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: dataDbURL,
|
||||
spendParamsURL: spendParamsURL,
|
||||
outputParamsURL: outputParamsURL,
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
walletBirthdayProvider: { [weak self] in self?.walletBirthDay.height ?? .zero },
|
||||
network: network
|
||||
)
|
||||
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in
|
||||
LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
}
|
||||
try await mockContainer.resolve(CompactBlockRepository.self).create()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(container: mockContainer, config: processorConfig)
|
||||
|
||||
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
|
||||
var latestScannedheight = BlockHeight.empty()
|
||||
|
||||
try await compactBlockProcessor.blockDownloaderService.downloadBlockRange(range)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
try await compactBlockProcessor.blockScanner.scanBlocks(at: range, totalProgressRange: range, didScan: { _ in })
|
||||
|
||||
latestScannedheight = repository.lastScannedBlockHeight()
|
||||
XCTAssertEqual(latestScannedheight, range.upperBound)
|
||||
|
||||
await compactBlockProcessor.stop()
|
||||
}
|
||||
|
||||
func observeBenchmark(_ metrics: SDKMetrics) {
|
||||
let reports = metrics.popAllBlockReports(flush: true)
|
||||
|
||||
reports.forEach {
|
||||
print("observed benchmark: \($0)")
|
||||
}
|
||||
}
|
||||
|
||||
func testScanValidateDownload() async throws {
|
||||
let seed = "testreferencealicetestreferencealice"
|
||||
|
||||
let metrics = SDKMetrics()
|
||||
metrics.enableMetrics()
|
||||
|
||||
guard try await rustBackend.initDataDb(seed: nil) == .success else {
|
||||
XCTFail("Seed should not be required for this test")
|
||||
return
|
||||
}
|
||||
|
||||
let derivationTool = DerivationTool(networkType: .testnet)
|
||||
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: Array(seed.utf8), accountIndex: 0)
|
||||
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
|
||||
|
||||
do {
|
||||
try await rustBackend.initAccountsTable(ufvks: [viewingKey])
|
||||
} catch {
|
||||
XCTFail("failed to init account table. error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
try await rustBackend.initBlocksTable(
|
||||
height: Int32(walletBirthDay.height),
|
||||
hash: walletBirthDay.hash,
|
||||
time: walletBirthDay.time,
|
||||
saplingTree: walletBirthDay.saplingTree
|
||||
)
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: dataDbURL,
|
||||
spendParamsURL: spendParamsURL,
|
||||
outputParamsURL: outputParamsURL,
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 1000,
|
||||
scanningBatchSize: 1000,
|
||||
walletBirthdayProvider: { [weak self] in self?.network.constants.saplingActivationHeight ?? .zero },
|
||||
network: network
|
||||
)
|
||||
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in
|
||||
LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
|
||||
}
|
||||
try await mockContainer.resolve(CompactBlockRepository.self).create()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(container: mockContainer, config: processorConfig)
|
||||
|
||||
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
||||
switch event {
|
||||
case .progressUpdated: self?.observeBenchmark(metrics)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
await compactBlockProcessor.updateEventClosure(identifier: "tests", closure: eventClosure)
|
||||
|
||||
let range = CompactBlockRange(
|
||||
uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)
|
||||
)
|
||||
|
||||
do {
|
||||
let blockDownloader = await compactBlockProcessor.blockDownloader
|
||||
await blockDownloader.setDownloadLimit(range.upperBound)
|
||||
try await blockDownloader.setSyncRange(range, batchSize: 100)
|
||||
await blockDownloader.startDownload(maxBlockBufferSize: 10)
|
||||
try await blockDownloader.waitUntilRequestedBlocksAreDownloaded(in: range)
|
||||
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
|
||||
try await compactBlockProcessor.blockValidator.validate()
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
|
||||
try await compactBlockProcessor.blockScanner.scanBlocks(at: range, totalProgressRange: range, didScan: { _ in })
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
if let lwdError = error as? ZcashError {
|
||||
switch lwdError {
|
||||
case .serviceBlockStreamFailed:
|
||||
XCTAssert(true)
|
||||
default:
|
||||
XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)")
|
||||
}
|
||||
} else {
|
||||
XCTFail("Error should have been a timeLimit reached Error - \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
await compactBlockProcessor.stop()
|
||||
metrics.disableMetrics()
|
||||
}
|
||||
}
|
|
@ -12,6 +12,13 @@ import XCTest
|
|||
class BlockStreamingTest: ZcashTestCase {
|
||||
let testFileManager = FileManager()
|
||||
var rustBackend: ZcashRustBackendWelding!
|
||||
var endpoint: LightWalletEndpoint!
|
||||
var service: LightWalletService!
|
||||
var storage: FSCompactBlockRepository!
|
||||
var internalSyncProgress: InternalSyncProgress!
|
||||
var processorConfig: CompactBlockProcessor.Configuration!
|
||||
var latestBlockHeight: BlockHeight!
|
||||
var startHeight: BlockHeight!
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
@ -37,45 +44,7 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in LatestBlocksDataProviderMock() }
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackend }
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
try super.tearDownWithError()
|
||||
rustBackend = nil
|
||||
try? FileManager.default.removeItem(at: __dataDbURL())
|
||||
testTempDirectory = nil
|
||||
}
|
||||
|
||||
func testStream() async throws {
|
||||
let endpoint = LightWalletEndpoint(
|
||||
address: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
secure: true,
|
||||
singleCallTimeoutInMillis: 1000,
|
||||
streamingCallTimeoutInMillis: 100000
|
||||
)
|
||||
let service = LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
|
||||
let latestHeight = try await service.latestBlockHeight()
|
||||
|
||||
let startHeight = latestHeight - 100_000
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
let stream = service.blockStream(startHeight: startHeight, endHeight: latestHeight)
|
||||
|
||||
do {
|
||||
for try await compactBlock in stream {
|
||||
print("received block \(compactBlock.height)")
|
||||
blocks.append(compactBlock)
|
||||
print("progressHeight: \(compactBlock.height)")
|
||||
print("startHeight: \(startHeight)")
|
||||
print("targetHeight: \(latestHeight)")
|
||||
}
|
||||
} catch {
|
||||
XCTFail("failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testStreamCancellation() async throws {
|
||||
let endpoint = LightWalletEndpoint(
|
||||
address: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -85,51 +54,49 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
)
|
||||
let service = LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
|
||||
let latestBlockHeight = try await service.latestBlockHeight()
|
||||
let startHeight = latestBlockHeight - 100_000
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in
|
||||
LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
}
|
||||
try await mockContainer.resolve(CompactBlockRepository.self).create()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(container: mockContainer, config: processorConfig)
|
||||
|
||||
let cancelableTask = Task {
|
||||
do {
|
||||
let blockDownloader = await compactBlockProcessor.blockDownloader
|
||||
await blockDownloader.setDownloadLimit(latestBlockHeight)
|
||||
try await blockDownloader.setSyncRange(startHeight...latestBlockHeight, batchSize: 100)
|
||||
await blockDownloader.startDownload(maxBlockBufferSize: 10)
|
||||
try await blockDownloader.waitUntilRequestedBlocksAreDownloaded(in: startHeight...latestBlockHeight)
|
||||
} catch {
|
||||
XCTAssertTrue(Task.isCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
cancelableTask.cancel()
|
||||
await compactBlockProcessor.stop()
|
||||
latestBlockHeight = try await service.latestBlockHeight()
|
||||
startHeight = latestBlockHeight - 10_000
|
||||
}
|
||||
|
||||
func testStreamTimeout() async throws {
|
||||
override func tearDownWithError() throws {
|
||||
try super.tearDownWithError()
|
||||
rustBackend = nil
|
||||
try? FileManager.default.removeItem(at: __dataDbURL())
|
||||
endpoint = nil
|
||||
service = nil
|
||||
storage = nil
|
||||
internalSyncProgress = nil
|
||||
processorConfig = nil
|
||||
}
|
||||
|
||||
private func makeDependencies(timeout: Int64) async throws {
|
||||
let endpoint = LightWalletEndpoint(
|
||||
address: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
secure: true,
|
||||
singleCallTimeoutInMillis: 1000,
|
||||
streamingCallTimeoutInMillis: 1000
|
||||
singleCallTimeoutInMillis: timeout,
|
||||
streamingCallTimeoutInMillis: timeout
|
||||
)
|
||||
let service = LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
self.endpoint = endpoint
|
||||
service = LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
try await storage.create()
|
||||
|
||||
let latestBlockHeight = try await service.latestBlockHeight()
|
||||
let internalSyncProgressStorage = InternalSyncProgressMemoryStorage()
|
||||
try await internalSyncProgressStorage.set(startHeight, for: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue)
|
||||
internalSyncProgress = InternalSyncProgress(alias: .default, storage: internalSyncProgressStorage, logger: logger)
|
||||
|
||||
let startHeight = latestBlockHeight - 100_000
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
@ -137,18 +104,99 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in
|
||||
LightWalletServiceFactory(endpoint: endpoint).make()
|
||||
}
|
||||
try await mockContainer.resolve(CompactBlockRepository.self).create()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(container: mockContainer, config: processorConfig)
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = startHeight
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
|
||||
let blockDownloader = BlockDownloaderImpl(
|
||||
service: service,
|
||||
downloaderService: BlockDownloaderServiceImpl(service: service, storage: storage),
|
||||
storage: storage,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
metrics: SDKMetrics(),
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: BlockDownloader.self, isSingleton: true) { _ in blockDownloader }
|
||||
}
|
||||
|
||||
func testStream() async throws {
|
||||
try await makeDependencies(timeout: 10000)
|
||||
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
let stream = service.blockStream(startHeight: startHeight, endHeight: latestBlockHeight)
|
||||
|
||||
do {
|
||||
for try await compactBlock in stream {
|
||||
blocks.append(compactBlock)
|
||||
}
|
||||
} catch {
|
||||
XCTFail("failed with error: \(error)")
|
||||
}
|
||||
|
||||
XCTAssertEqual(blocks.count, latestBlockHeight - startHeight + 1)
|
||||
}
|
||||
|
||||
func testStreamCancellation() async throws {
|
||||
try await makeDependencies(timeout: 10000)
|
||||
|
||||
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadRange: startHeight...latestBlockHeight,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: startHeight,
|
||||
latestDownloadedBlockHeight: startHeight
|
||||
)
|
||||
let context = ActionContext(state: .download)
|
||||
await context.update(syncRanges: syncRanges)
|
||||
|
||||
let expectation = XCTestExpectation()
|
||||
|
||||
let cancelableTask = Task {
|
||||
do {
|
||||
_ = try await action.run(with: context, didUpdate: { _ in })
|
||||
let lastDownloadedHeight = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
// Just to be sure that download was interrupted before download was finished.
|
||||
XCTAssertLessThan(lastDownloadedHeight, latestBlockHeight)
|
||||
expectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Downloading failed with error: \(error)")
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||
cancelableTask.cancel()
|
||||
}
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 5)
|
||||
await action.stop()
|
||||
}
|
||||
|
||||
func testStreamTimeout() async throws {
|
||||
try await makeDependencies(timeout: 100)
|
||||
|
||||
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadRange: startHeight...latestBlockHeight,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: startHeight,
|
||||
latestDownloadedBlockHeight: startHeight
|
||||
)
|
||||
let context = ActionContext(state: .download)
|
||||
await context.update(syncRanges: syncRanges)
|
||||
|
||||
let date = Date()
|
||||
|
||||
do {
|
||||
let blockDownloader = await compactBlockProcessor.blockDownloader
|
||||
await blockDownloader.setDownloadLimit(latestBlockHeight)
|
||||
try await blockDownloader.setSyncRange(startHeight...latestBlockHeight, batchSize: 100)
|
||||
await blockDownloader.startDownload(maxBlockBufferSize: 10)
|
||||
try await blockDownloader.waitUntilRequestedBlocksAreDownloaded(in: startHeight...latestBlockHeight)
|
||||
_ = try await action.run(with: context, didUpdate: { _ in })
|
||||
XCTFail("It is expected that this downloading fails.")
|
||||
} catch {
|
||||
if let lwdError = error as? ZcashError {
|
||||
switch lwdError {
|
||||
|
@ -167,6 +215,6 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
let elapsed = now.timeIntervalSince(date)
|
||||
print("took \(elapsed) seconds")
|
||||
|
||||
await compactBlockProcessor.stop()
|
||||
await action.stop()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ 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,
|
||||
|
@ -170,7 +174,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
let expectedUpdates = expectedBatches(
|
||||
currentHeight: processorConfig.walletBirthday,
|
||||
targetHeight: mockLatestHeight,
|
||||
batchSize: processorConfig.downloadBatchSize
|
||||
batchSize: processorConfig.batchSize
|
||||
)
|
||||
updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates
|
||||
|
||||
|
@ -189,8 +193,8 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
|
||||
var expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadedButUnscannedRange: 1...latestDownloadedHeight,
|
||||
downloadAndScanRange: latestDownloadedHeight...latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight...latestBlockchainHeight,
|
||||
scanRange: latestDownloadedHeight...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
|
@ -217,13 +221,13 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
)
|
||||
|
||||
// Test mid-range
|
||||
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultDownloadBatch)
|
||||
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultBatchSize)
|
||||
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
||||
|
||||
expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadedButUnscannedRange: 1...latestDownloadedHeight,
|
||||
downloadAndScanRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
|
@ -256,8 +260,8 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
|
||||
expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadedButUnscannedRange: 1...latestDownloadedHeight,
|
||||
downloadAndScanRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
|
@ -284,72 +288,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
)
|
||||
}
|
||||
|
||||
func testShouldClearBlockCacheReturnsNilWhenScannedHeightEqualsDownloadedHeight() {
|
||||
/*
|
||||
downloaded but not scanned: -1...-1
|
||||
download and scan: 1493120...2255953
|
||||
enhance range: 1410000...2255953
|
||||
fetchUTXO range: 1410000...2255953
|
||||
total progress range: 1493120...2255953
|
||||
*/
|
||||
|
||||
let range = SyncRanges(
|
||||
latestBlockHeight: 2255953,
|
||||
downloadedButUnscannedRange: -1 ... -1,
|
||||
downloadAndScanRange: 1493120...2255953,
|
||||
enhanceRange: 1410000...2255953,
|
||||
fetchUTXORange: 1410000...2255953,
|
||||
latestScannedHeight: 1493119,
|
||||
latestDownloadedBlockHeight: 1493119
|
||||
)
|
||||
|
||||
XCTAssertNil(range.shouldClearBlockCacheAndUpdateInternalState())
|
||||
}
|
||||
|
||||
func testShouldClearBlockCacheReturnsAHeightWhenScannedIsGreaterThanDownloaded() {
|
||||
/*
|
||||
downloaded but not scanned: -1...-1
|
||||
download and scan: 1493120...2255953
|
||||
enhance range: 1410000...2255953
|
||||
fetchUTXO range: 1410000...2255953
|
||||
total progress range: 1493120...2255953
|
||||
*/
|
||||
|
||||
let range = SyncRanges(
|
||||
latestBlockHeight: 2255953,
|
||||
downloadedButUnscannedRange: -1 ... -1,
|
||||
downloadAndScanRange: 1493120...2255953,
|
||||
enhanceRange: 1410000...2255953,
|
||||
fetchUTXORange: 1410000...2255953,
|
||||
latestScannedHeight: 1493129,
|
||||
latestDownloadedBlockHeight: 1493119
|
||||
)
|
||||
|
||||
XCTAssertEqual(range.shouldClearBlockCacheAndUpdateInternalState(), BlockHeight(1493129))
|
||||
}
|
||||
|
||||
func testShouldClearBlockCacheReturnsNilWhenScannedIsGreaterThanDownloaded() {
|
||||
/*
|
||||
downloaded but not scanned: 1493120...1494120
|
||||
download and scan: 1494121...2255953
|
||||
enhance range: 1410000...2255953
|
||||
fetchUTXO range: 1410000...2255953
|
||||
total progress range: 1493120...2255953
|
||||
*/
|
||||
|
||||
let range = SyncRanges(
|
||||
latestBlockHeight: 2255953,
|
||||
downloadedButUnscannedRange: 1493120...1494120,
|
||||
downloadAndScanRange: 1494121...2255953,
|
||||
enhanceRange: 1410000...2255953,
|
||||
fetchUTXORange: 1410000...2255953,
|
||||
latestScannedHeight: 1493119,
|
||||
latestDownloadedBlockHeight: 1494120
|
||||
)
|
||||
|
||||
XCTAssertNil(range.shouldClearBlockCacheAndUpdateInternalState())
|
||||
}
|
||||
|
||||
func testDetermineLowerBoundPastBirthday() async {
|
||||
let errorHeight = 781_906
|
||||
|
||||
|
|
|
@ -1,607 +0,0 @@
|
|||
//
|
||||
// BlockBatchValidationTests.swift
|
||||
// ZcashLightClientKit-Unit-Tests
|
||||
//
|
||||
// Created by Francisco Gindre on 6/17/21.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class BlockBatchValidationTests: ZcashTestCase {
|
||||
let testFileManager = FileManager()
|
||||
var rustBackend: ZcashRustBackendWelding!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
Dependencies.setup(
|
||||
in: mockContainer,
|
||||
urls: Initializer.URLs(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
dataDbURL: try! __dataDbURL(),
|
||||
generalStorageURL: testGeneralStorageDirectory,
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL()
|
||||
),
|
||||
alias: .default,
|
||||
networkType: .testnet,
|
||||
endpoint: LightWalletEndpointBuilder.default,
|
||||
loggingPolicy: .default(.debug)
|
||||
)
|
||||
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in LatestBlocksDataProviderMock() }
|
||||
|
||||
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
try super.tearDownWithError()
|
||||
rustBackend = nil
|
||||
testTempDirectory = nil
|
||||
}
|
||||
|
||||
func testBranchIdFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
||||
|
||||
let storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in storage }
|
||||
|
||||
try await storage.create()
|
||||
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
return BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
}
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { 1210000 },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = 130000
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "main"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db33f"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: Int32(0xd34d))
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in mockBackend.rustBackendMock }
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
container: mockContainer,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloaderService: mockContainer.resolve(BlockDownloaderService.self))
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case ZcashError.compactBlockProcessorWrongConsensusBranchId:
|
||||
break
|
||||
default:
|
||||
XCTFail("Expected ZcashError.compactBlockProcessorWrongConsensusBranchId but found \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBranchNetworkMismatchFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
||||
|
||||
let storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in storage }
|
||||
|
||||
try await storage.create()
|
||||
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
return BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
}
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { 1210000 },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = 130000
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "test"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in mockBackend.rustBackendMock }
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
container: mockContainer,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloaderService: mockContainer.resolve(BlockDownloaderService.self))
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case ZcashError.compactBlockProcessorNetworkMismatch(.mainnet, .testnet):
|
||||
break
|
||||
default:
|
||||
XCTFail("Expected ZcashError.compactBlockProcessorNetworkMismatch but found \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBranchNetworkTypeWrongFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
||||
|
||||
let storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in storage }
|
||||
|
||||
try await storage.create()
|
||||
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
return BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
}
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { 1210000 },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = 130000
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "another"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in mockBackend.rustBackendMock }
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
container: mockContainer,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloaderService: mockContainer.resolve(BlockDownloaderService.self))
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case ZcashError.compactBlockProcessorChainName:
|
||||
break
|
||||
default:
|
||||
XCTFail("Expected ZcashError.compactBlockProcessorChainName but found \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSaplingActivationHeightMismatch() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
||||
|
||||
let storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in storage }
|
||||
|
||||
try await storage.create()
|
||||
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
return BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
}
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { 1210000 },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = 130000
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "main"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(3434343)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in mockBackend.rustBackendMock }
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
container: mockContainer,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloaderService: mockContainer.resolve(BlockDownloaderService.self))
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case ZcashError.compactBlockProcessorSaplingActivationMismatch(
|
||||
network.constants.saplingActivationHeight,
|
||||
BlockHeight(info.saplingActivationHeight)
|
||||
):
|
||||
break
|
||||
default:
|
||||
XCTFail("Expected ZcashError.compactBlockProcessorSaplingActivationMismatch but found \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testResultIsWait() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
|
||||
let expectedLatestHeight = BlockHeight(1210000)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: expectedLatestHeight,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
let expectedStoredLatestHeight = BlockHeight(1220000)
|
||||
let expectedResult = CompactBlockProcessor.NextState.wait(
|
||||
latestHeight: expectedLatestHeight,
|
||||
latestDownloadHeight: expectedStoredLatestHeight
|
||||
)
|
||||
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoredLatestHeight)
|
||||
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { 1210000 },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = UInt64(expectedLatestHeight)
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "main"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
|
||||
service: service,
|
||||
downloaderService: downloaderService,
|
||||
latestBlocksDataProvider: LatestBlocksDataProviderMock(
|
||||
latestScannedHeight: expectedStoredLatestHeight,
|
||||
latestBlockHeight: expectedLatestHeight
|
||||
),
|
||||
config: config,
|
||||
rustBackend: mockBackend.rustBackendMock,
|
||||
internalSyncProgress: InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
),
|
||||
alias: .default
|
||||
)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
guard nextBatch != nil else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch (nextBatch, expectedResult) {
|
||||
case let (.wait(latestHeight, latestDownloadHeight), .wait(expectedLatestHeight, exectedLatestDownloadHeight)):
|
||||
return latestHeight == expectedLatestHeight && latestDownloadHeight == exectedLatestDownloadHeight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
|
||||
func testResultProcessNew() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let expectedLatestHeight = BlockHeight(1230000)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: expectedLatestHeight,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
let expectedStoredLatestHeight = BlockHeight(1220000)
|
||||
let walletBirthday = BlockHeight(1210000)
|
||||
|
||||
let ranges = SyncRanges(
|
||||
latestBlockHeight: expectedLatestHeight,
|
||||
downloadedButUnscannedRange: nil,
|
||||
downloadAndScanRange: expectedStoredLatestHeight + 1...expectedLatestHeight,
|
||||
enhanceRange: walletBirthday...expectedLatestHeight,
|
||||
fetchUTXORange: walletBirthday...expectedLatestHeight,
|
||||
latestScannedHeight: expectedStoredLatestHeight,
|
||||
latestDownloadedBlockHeight: expectedStoredLatestHeight
|
||||
)
|
||||
let expectedResult = CompactBlockProcessor.NextState.processNewBlocks(ranges: ranges)
|
||||
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoredLatestHeight)
|
||||
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { walletBirthday },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = UInt64(expectedLatestHeight)
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "main"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
|
||||
service: service,
|
||||
downloaderService: downloaderService,
|
||||
latestBlocksDataProvider: LatestBlocksDataProviderMock(
|
||||
latestScannedHeight: expectedStoredLatestHeight,
|
||||
latestBlockHeight: expectedLatestHeight
|
||||
),
|
||||
config: config,
|
||||
rustBackend: mockBackend.rustBackendMock,
|
||||
internalSyncProgress: InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
),
|
||||
alias: .default
|
||||
)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
guard nextBatch != nil else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch (nextBatch, expectedResult) {
|
||||
case let (.processNewBlocks(ranges), .processNewBlocks(expectedRanges)):
|
||||
return ranges == expectedRanges
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
|
||||
func testResultProcessorFinished() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let expectedLatestHeight = BlockHeight(1230000)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: expectedLatestHeight,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
)
|
||||
let expectedStoredLatestHeight = BlockHeight(1230000)
|
||||
let walletBirthday = BlockHeight(1210000)
|
||||
let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoredLatestHeight)
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoredLatestHeight)
|
||||
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
fsBlockCacheRoot: testTempDirectory,
|
||||
dataDb: try! __dataDbURL(),
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL(),
|
||||
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
|
||||
downloadBatchSize: 100,
|
||||
retries: 5,
|
||||
maxBackoffInterval: 10,
|
||||
rewindDistance: 100,
|
||||
walletBirthdayProvider: { walletBirthday },
|
||||
saplingActivation: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
|
||||
let internalSyncProgress = InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
)
|
||||
try await internalSyncProgress.set(expectedStoredLatestHeight, .latestEnhancedHeight)
|
||||
try await internalSyncProgress.set(expectedStoredLatestHeight, .latestUTXOFetchedHeight)
|
||||
|
||||
var info = LightdInfo()
|
||||
info.blockHeight = UInt64(expectedLatestHeight)
|
||||
info.branch = "d34db33f"
|
||||
info.chainName = "main"
|
||||
info.buildUser = "test user"
|
||||
info.consensusBranchID = "d34db4d"
|
||||
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
|
||||
|
||||
service.mockLightDInfo = info
|
||||
|
||||
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
|
||||
service: service,
|
||||
downloaderService: downloaderService,
|
||||
latestBlocksDataProvider: LatestBlocksDataProviderMock(
|
||||
latestScannedHeight: expectedStoredLatestHeight,
|
||||
latestBlockHeight: expectedLatestHeight
|
||||
),
|
||||
config: config,
|
||||
rustBackend: mockBackend.rustBackendMock,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
alias: .default
|
||||
)
|
||||
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
guard nextBatch != nil else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch (nextBatch, expectedResult) {
|
||||
case let (.finishProcessing(height), .finishProcessing(expectedHeight)):
|
||||
return height == expectedHeight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// ClearAlreadyScannedBlocksActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 22.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ClearAlreadyScannedBlocksActionTests: ZcashTestCase {
|
||||
func testClearAlreadyScannedBlocksAction_NextAction() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
|
||||
compactBlockRepositoryMock.clearUpToClosure = { _ in }
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
|
||||
let clearAlreadyScannedBlocksAction = ClearAlreadyScannedBlocksAction(
|
||||
container: mockContainer
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await clearAlreadyScannedBlocksAction.run(with: .init(state: .clearAlreadyScannedBlocks)) { _ in }
|
||||
XCTAssertTrue(compactBlockRepositoryMock.clearUpToCalled, "storage.clear(upTo:) is expected to be called.")
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .enhance,
|
||||
"nextContext after .clearAlreadyScannedBlocks is expected to be .enhance but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testClearAlreadyScannedBlocksAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// ClearCacheActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 22.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ClearCacheActionTests: ZcashTestCase {
|
||||
func testClearCacheAction_NextAction() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
|
||||
compactBlockRepositoryMock.clearClosure = { }
|
||||
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
|
||||
|
||||
let clearCacheAction = ClearCacheAction(
|
||||
container: mockContainer
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await clearCacheAction.run(with: .init(state: .clearCache)) { _ in }
|
||||
XCTAssertTrue(compactBlockRepositoryMock.clearCalled, "storage.clear() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .finished,
|
||||
"nextContext after .clearCache is expected to be .finished but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testClearCacheAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
//
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
//
|
||||
// DownloadActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 21.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class DownloadActionTests: ZcashTestCase {
|
||||
var underlyingDownloadRange: CompactBlockRange?
|
||||
var underlyingScanRange: CompactBlockRange?
|
||||
|
||||
func testDownloadAction_NextAction() async throws {
|
||||
let blockDownloaderMock = BlockDownloaderMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
blockDownloaderMock.setSyncRangeBatchSizeClosure = { _, _ in }
|
||||
blockDownloaderMock.setDownloadLimitClosure = { _ in }
|
||||
blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in }
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in }
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock,
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is expected to be called.")
|
||||
XCTAssertTrue(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is expected to be called.")
|
||||
XCTAssertTrue(
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled,
|
||||
"downloader.waitUntilRequestedBlocksAreDownloaded() is expected to be called."
|
||||
)
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .validate,
|
||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testDownloadAction_NoDownloadAndScanRange() async throws {
|
||||
let blockDownloaderMock = BlockDownloaderMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock,
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled,
|
||||
"downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called."
|
||||
)
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .validate,
|
||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testDownloadAction_NothingMoreToDownload() async throws {
|
||||
let blockDownloaderMock = BlockDownloaderMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 2001
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock,
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.")
|
||||
XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled,
|
||||
"downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called."
|
||||
)
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .validate,
|
||||
"nextContext after .download is expected to be .validate but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testDownloadAction_DownloadStops() async throws {
|
||||
let blockDownloaderMock = BlockDownloaderMock()
|
||||
|
||||
blockDownloaderMock.stopDownloadClosure = { }
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock
|
||||
)
|
||||
|
||||
await downloadAction.stop()
|
||||
|
||||
XCTAssertTrue(blockDownloaderMock.stopDownloadCalled, "downloader.stopDownload() is expected to be called.")
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ blockDownloaderMock: BlockDownloaderMock = BlockDownloaderMock(),
|
||||
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> DownloadAction {
|
||||
mockContainer.mock(type: BlockDownloader.self, isSingleton: true) { _ in blockDownloaderMock }
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
||||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
return DownloadAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
//
|
||||
// EnhanceActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 19.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class EnhanceActionTests: ZcashTestCase {
|
||||
var underlyingDownloadRange: CompactBlockRange?
|
||||
var underlyingScanRange: CompactBlockRange?
|
||||
var underlyingEnhanceRange: CompactBlockRange?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingDownloadRange = nil
|
||||
underlyingScanRange = nil
|
||||
underlyingEnhanceRange = nil
|
||||
}
|
||||
|
||||
func testEnhanceAction_decideWhatToDoNext_NoDownloadAndScanRange() async throws {
|
||||
let enhanceAction = setupAction()
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
let nextContext = await enhanceAction.decideWhatToDoNext(context: syncContext, lastScannedHeight: 1)
|
||||
let nextState = await nextContext.state
|
||||
|
||||
XCTAssertTrue(
|
||||
nextState == .clearCache,
|
||||
"testEnhanceAction_decideWhatToDoNext_NoDownloadAndScanRange is expected to be .clearCache but received \(nextState)"
|
||||
)
|
||||
}
|
||||
|
||||
func testEnhanceAction_decideWhatToDoNext_NothingToDownloadAndScanLeft() async throws {
|
||||
let enhanceAction = setupAction()
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
let nextContext = await enhanceAction.decideWhatToDoNext(context: syncContext, lastScannedHeight: 2000)
|
||||
let nextState = await nextContext.state
|
||||
|
||||
XCTAssertTrue(
|
||||
nextState == .clearCache,
|
||||
"testEnhanceAction_decideWhatToDoNext_NothingToDownloadAndScanLeft is expected to be .clearCache but received \(nextState)"
|
||||
)
|
||||
}
|
||||
|
||||
func testEnhanceAction_decideWhatToDoNext_DownloadExpected() async throws {
|
||||
let enhanceAction = setupAction()
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
let nextContext = await enhanceAction.decideWhatToDoNext(context: syncContext, lastScannedHeight: 1500)
|
||||
let nextState = await nextContext.state
|
||||
|
||||
XCTAssertTrue(
|
||||
nextState == .download,
|
||||
"testEnhanceAction_decideWhatToDoNext_DownloadExpected is expected to be .download but received \(nextState)"
|
||||
)
|
||||
}
|
||||
|
||||
func testEnhanceAction_NoEnhanceRange() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
_ = 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)")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663206,
|
||||
fee: Zatoshi(0),
|
||||
id: 2,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663188,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
)
|
||||
|
||||
blockEnhancerMock.enhanceAtDidEnhanceClosure = { _, didEnhance in
|
||||
await didEnhance(EnhancementProgress.zero)
|
||||
return [transaction]
|
||||
}
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { event in
|
||||
guard case let .foundTransactions(transactions, _) = event else {
|
||||
XCTFail("Event is expected to be .foundTransactions but received \(event)")
|
||||
return
|
||||
}
|
||||
XCTAssertTrue(transactions.count == 1)
|
||||
guard let receivedTransaction = transactions.first else {
|
||||
XCTFail("Transaction.first is expected to pass.")
|
||||
return
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663206,
|
||||
fee: Zatoshi(0),
|
||||
id: 2,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663188,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
)
|
||||
|
||||
blockEnhancerMock.enhanceAtDidEnhanceClosure = { _, didEnhance in
|
||||
await didEnhance(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 0,
|
||||
enhancedTransactions: 0,
|
||||
lastFoundTransaction: transaction,
|
||||
range: 0...0,
|
||||
newlyMined: true
|
||||
)
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { event in
|
||||
if case .progressPartialUpdate = event { return }
|
||||
|
||||
guard case .minedTransaction(let minedTransaction) = event else {
|
||||
XCTFail("Event is expected to be .minedTransaction but received \(event)")
|
||||
return
|
||||
}
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
func testEnhanceAction_EnhancementOfBlocksCalled_usingSmallRange_minedTransaction() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 200
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
|
||||
let transaction = ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
blockTime: 1.0,
|
||||
expiryHeight: 663206,
|
||||
fee: Zatoshi(0),
|
||||
id: 2,
|
||||
index: 1,
|
||||
hasChange: false,
|
||||
memoCount: 1,
|
||||
minedHeight: 663188,
|
||||
raw: Data(),
|
||||
rawID: Data(),
|
||||
receivedNoteCount: 1,
|
||||
sentNoteCount: 0,
|
||||
value: Zatoshi(100000),
|
||||
isExpiredUmined: false
|
||||
)
|
||||
|
||||
blockEnhancerMock.enhanceAtDidEnhanceClosure = { _, didEnhance in
|
||||
await didEnhance(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 0,
|
||||
enhancedTransactions: 0,
|
||||
lastFoundTransaction: transaction,
|
||||
range: 0...0,
|
||||
newlyMined: true
|
||||
)
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (100, 200))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { event in
|
||||
if case .progressPartialUpdate = event { return }
|
||||
|
||||
guard case .minedTransaction(let minedTransaction) = event else {
|
||||
XCTFail("Event is expected to be .minedTransaction but received \(event)")
|
||||
return
|
||||
}
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
return EnhanceAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// FetchUTXOsActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 18.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class FetchUTXOsActionTests: ZcashTestCase {
|
||||
func testFetchUTXOsAction_NextAction() async throws {
|
||||
let loggerMock = LoggerMock()
|
||||
let uTXOFetcherMock = UTXOFetcherMock()
|
||||
|
||||
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])
|
||||
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
mockContainer.mock(type: UTXOFetcher.self, isSingleton: true) { _ in uTXOFetcherMock }
|
||||
|
||||
let fetchUTXOsAction = FetchUTXOsAction(container: mockContainer)
|
||||
|
||||
let syncContext: ActionContext = .init(state: .fetchUTXO)
|
||||
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: nil,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
|
||||
do {
|
||||
let nextContext = try await fetchUTXOsAction.run(with: syncContext) { event in
|
||||
guard case .storedUTXOs(let result) = event else {
|
||||
XCTFail("testFetchUTXOsAction_NextAction event expected to be .storedUTXOs but received \(event)")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(result.inserted as! [UnspentTransactionOutputEntityMock], [insertedEntity])
|
||||
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.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .handleSaplingParams,
|
||||
"nextContext after .fetchUTXO is expected to be .handleSaplingParams but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testFetchUTXOsAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
//
|
||||
// MigrateLegacyCacheDBActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 23.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
||||
var underlyingAlias: ZcashSynchronizerAlias?
|
||||
var underlyingCacheDbURL: URL?
|
||||
var underlyingFsBlockCacheRoot: URL?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingAlias = nil
|
||||
underlyingCacheDbURL = nil
|
||||
underlyingFsBlockCacheRoot = nil
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_noCacheDbURL() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
|
||||
XCTAssertFalse(compactBlockRepositoryMock.createCalled, "storage.create() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
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(
|
||||
nextState == .validateServer,
|
||||
"nextContext after .migrateLegacyCacheDB is expected to be .validateServer but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testMigrateLegacyCacheDBAction_noCacheDbURL is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_noFsBlockCacheRoot() async throws {
|
||||
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
|
||||
)
|
||||
|
||||
do {
|
||||
_ = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
XCTFail("testMigrateLegacyCacheDBAction_noFsBlockCacheRoot is expected to fail.")
|
||||
} catch ZcashError.compactBlockProcessorCacheDbMigrationFsCacheMigrationFailedSameURL {
|
||||
XCTAssertFalse(compactBlockRepositoryMock.createCalled, "storage.create() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
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 \
|
||||
ZcashError.compactBlockProcessorCacheDbMigrationFsCacheMigrationFailedSameURL but received \(error)
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_aliasDoesntMatchDefault() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
underlyingFsBlockCacheRoot = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).dataDbURL
|
||||
|
||||
underlyingAlias = .custom("any")
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
|
||||
XCTAssertFalse(compactBlockRepositoryMock.createCalled, "storage.create() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
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(
|
||||
nextState == .validateServer,
|
||||
"nextContext after .migrateLegacyCacheDB is expected to be .validateServer but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testMigrateLegacyCacheDBAction_aliasDoesntMatchDefault is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_isNotReadableFile() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
underlyingFsBlockCacheRoot = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).dataDbURL
|
||||
|
||||
zcashFileManagerMock.isReadableFileAtPathReturnValue = false
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
|
||||
XCTAssertFalse(compactBlockRepositoryMock.createCalled, "storage.create() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
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(
|
||||
nextState == .validateServer,
|
||||
"nextContext after .migrateLegacyCacheDB is expected to be .validateServer but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testMigrateLegacyCacheDBAction_isNotReadableFile is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_removeItemFailed() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
underlyingFsBlockCacheRoot = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).dataDbURL
|
||||
|
||||
zcashFileManagerMock.isReadableFileAtPathReturnValue = true
|
||||
zcashFileManagerMock.removeItemAtClosure = { _ in throw "remove failed" }
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
do {
|
||||
_ = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
} catch ZcashError.compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb {
|
||||
XCTAssertFalse(compactBlockRepositoryMock.createCalled, "storage.create() is not expected to be called.")
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
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 \
|
||||
ZcashError.compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb but received \(error)
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testMigrateLegacyCacheDBAction_nextAction() async throws {
|
||||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
underlyingFsBlockCacheRoot = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).dataDbURL
|
||||
|
||||
zcashFileManagerMock.isReadableFileAtPathReturnValue = true
|
||||
zcashFileManagerMock.removeItemAtClosure = { _ in }
|
||||
compactBlockRepositoryMock.createClosure = { }
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
internalSyncProgressStorageMock.setForClosure = { _, _ in }
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
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(
|
||||
nextState == .validateServer,
|
||||
"nextContext after .migrateLegacyCacheDB is expected to be .validateServer but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testMigrateLegacyCacheDBAction_nextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ 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 }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
||||
return MigrateLegacyCacheDBAction(
|
||||
container: mockContainer,
|
||||
configProvider: setupConfig()
|
||||
)
|
||||
}
|
||||
|
||||
private func setupConfig() -> CompactBlockProcessor.ConfigProvider {
|
||||
let defaultConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
alias: underlyingAlias ?? defaultConfig.alias,
|
||||
cacheDbURL: underlyingCacheDbURL ?? defaultConfig.cacheDbURL,
|
||||
fsBlockCacheRoot: underlyingFsBlockCacheRoot ?? defaultConfig.fsBlockCacheRoot,
|
||||
dataDb: defaultConfig.dataDb,
|
||||
spendParamsURL: defaultConfig.spendParamsURL,
|
||||
outputParamsURL: defaultConfig.outputParamsURL,
|
||||
saplingParamsSourceURL: defaultConfig.saplingParamsSourceURL,
|
||||
walletBirthdayProvider: defaultConfig.walletBirthdayProvider,
|
||||
saplingActivation: defaultConfig.saplingActivation,
|
||||
network: defaultConfig.network
|
||||
)
|
||||
|
||||
return CompactBlockProcessor.ConfigProvider(config: config)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// SaplingParamsActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 18.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class SaplingParamsActionTests: ZcashTestCase {
|
||||
func testSaplingParamsAction_NextAction() async throws {
|
||||
let loggerMock = LoggerMock()
|
||||
let saplingParametersHandlerMock = SaplingParametersHandlerMock()
|
||||
|
||||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
saplingParametersHandlerMock.handleIfNeededClosure = { }
|
||||
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
mockContainer.mock(type: SaplingParametersHandler.self, isSingleton: true) { _ in saplingParametersHandlerMock }
|
||||
|
||||
let saplingParamsActionAction = SaplingParamsAction(container: mockContainer)
|
||||
|
||||
do {
|
||||
let nextContext = try await saplingParamsActionAction.run(with: .init(state: .handleSaplingParams)) { _ in }
|
||||
XCTAssertTrue(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is expected to be called.")
|
||||
XCTAssertTrue(saplingParametersHandlerMock.handleIfNeededCalled, "saplingParametersHandler.handleIfNeeded() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .download,
|
||||
"nextContext after .handleSaplingParams is expected to be .download but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testSaplingParamsAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// ScanActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 18.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ScanActionTests: ZcashTestCase {
|
||||
func testScanAction_NextAction() async throws {
|
||||
let blockScannerMock = BlockScannerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1500
|
||||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
blockScannerMock.scanBlocksAtTotalProgressRangeDidScanClosure = { _, _, _ in 2 }
|
||||
|
||||
let scanAction = setupAction(blockScannerMock, transactionRepositoryMock, loggerMock)
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await scanAction.run(with: syncContext) { event in
|
||||
guard case .progressPartialUpdate(.syncing(let progress)) = event else {
|
||||
XCTFail("event is expected to be .progressPartialUpdate(.syncing()) but received \(event)")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(progress.startHeight, BlockHeight(1000))
|
||||
XCTAssertEqual(progress.targetHeight, BlockHeight(2000))
|
||||
XCTAssertEqual(progress.progressHeight, BlockHeight(1500))
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is expected to be called.")
|
||||
XCTAssertTrue(blockScannerMock.scanBlocksAtTotalProgressRangeDidScanCalled, "blockScanner.scanBlocks(...) is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .clearAlreadyScannedBlocks,
|
||||
"nextContext after .scan is expected to be .clearAlreadyScannedBlocks but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testScanAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testScanAction_EarlyOutForNoDownloadAndScanRangeSet() async throws {
|
||||
let blockScannerMock = BlockScannerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let scanAction = setupAction(blockScannerMock, transactionRepositoryMock, loggerMock)
|
||||
let syncContext: ActionContext = .init(state: .scan)
|
||||
|
||||
do {
|
||||
_ = try await scanAction.run(with: syncContext) { _ in }
|
||||
XCTAssertFalse(
|
||||
transactionRepositoryMock.lastScannedHeightCalled,
|
||||
"transactionRepository.lastScannedHeight() is not expected to be called."
|
||||
)
|
||||
XCTAssertFalse(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is not expected to be called.")
|
||||
XCTAssertFalse(blockScannerMock.scanBlocksAtTotalProgressRangeDidScanCalled, "blockScanner.scanBlocks(...) is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testScanAction_EarlyOutForNoDownloadAndScanRangeSet is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testScanAction_StartRangeHigherThanEndRange() async throws {
|
||||
let blockScannerMock = BlockScannerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 2001
|
||||
|
||||
let scanAction = setupAction(blockScannerMock, transactionRepositoryMock, loggerMock)
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
_ = try await scanAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is not expected to be called.")
|
||||
XCTAssertFalse(blockScannerMock.scanBlocksAtTotalProgressRangeDidScanCalled, "blockScanner.scanBlocks(...) is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testScanAction_StartRangeHigherThanEndRange is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ blockScannerMock: BlockScannerMock,
|
||||
_ transactionRepositoryMock: TransactionRepositoryMock,
|
||||
_ loggerMock: LoggerMock
|
||||
) -> ScanAction {
|
||||
mockContainer.mock(type: BlockScanner.self, isSingleton: true) { _ in blockScannerMock }
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
return ScanAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// ValidateActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 17.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ValidateActionTests: ZcashTestCase {
|
||||
func testValidateAction_NextAction() async throws {
|
||||
let blockValidatorMock = BlockValidatorMock()
|
||||
|
||||
blockValidatorMock.validateClosure = { }
|
||||
|
||||
mockContainer.mock(type: BlockValidator.self, isSingleton: true) { _ in blockValidatorMock }
|
||||
|
||||
let validateAction = ValidateAction(
|
||||
container: mockContainer
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await validateAction.run(with: .init(state: .validate)) { _ in }
|
||||
XCTAssertTrue(blockValidatorMock.validateCalled, "validator.validate() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .scan,
|
||||
"nextContext after .validate is expected to be .scan but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testValidateAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// ValidateServerActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 16.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ValidateServerActionTests: ZcashTestCase {
|
||||
var underlyingChainName = ""
|
||||
var underlyingNetworkType = NetworkType.testnet
|
||||
var underlyingSaplingActivationHeight: BlockHeight?
|
||||
var underlyingConsensusBranchID = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingChainName = "test"
|
||||
underlyingNetworkType = .testnet
|
||||
underlyingSaplingActivationHeight = nil
|
||||
underlyingConsensusBranchID = "c2d6d0b4"
|
||||
}
|
||||
|
||||
func testValidateServerAction_NextAction() async throws {
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
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)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testValidateServerAction_NextAction is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateServerAction_ChainNameError() async throws {
|
||||
underlyingChainName = "invalid"
|
||||
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
_ = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
XCTFail("testValidateServerAction_ChainNameError is expected to fail.")
|
||||
} catch ZcashError.compactBlockProcessorChainName(let chainName) {
|
||||
XCTAssertEqual(chainName, "invalid")
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testValidateServerAction_ChainNameError is expected to fail but error \(error) doesn't match \
|
||||
ZcashError.compactBlockProcessorChainName
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateServerAction_NetworkMatchError() async throws {
|
||||
underlyingNetworkType = .mainnet
|
||||
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
_ = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
XCTFail("testValidateServerAction_NetworkMatchError is expected to fail.")
|
||||
} catch let ZcashError.compactBlockProcessorNetworkMismatch(expected, found) {
|
||||
XCTAssertEqual(expected, .mainnet)
|
||||
XCTAssertEqual(found, .testnet)
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testValidateServerAction_NetworkMatchError is expected to fail but error \(error) doesn't match \
|
||||
ZcashError.compactBlockProcessorNetworkMismatch
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateServerAction_SaplingActivationError() async throws {
|
||||
underlyingSaplingActivationHeight = 1
|
||||
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
_ = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
XCTFail("testValidateServerAction_SaplingActivationError is expected to fail.")
|
||||
} catch let ZcashError.compactBlockProcessorSaplingActivationMismatch(expected, found) {
|
||||
XCTAssertEqual(expected, 280_000)
|
||||
XCTAssertEqual(found, 1)
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testValidateServerAction_SaplingActivationError is expected to fail but error \(error) doesn't match \
|
||||
ZcashError.compactBlockProcessorSaplingActivationMismatch
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateServerAction_ConsensusBranchIDError_InvalidRemoteBranch() async throws {
|
||||
underlyingConsensusBranchID = "1 1"
|
||||
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
_ = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
XCTFail("testValidateServerAction_ConsensusBranchIDError_InvalidRemoteBranch is expected to fail.")
|
||||
} catch ZcashError.compactBlockProcessorConsensusBranchID {
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testValidateServerAction_ConsensusBranchIDError_InvalidRemoteBranch is expected to fail but error \(error) doesn't match \
|
||||
ZcashError.compactBlockProcessorConsensusBranchID
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
func testValidateServerAction_ConsensusBranchIDError_ValidRemoteBranch() async throws {
|
||||
underlyingConsensusBranchID = "1"
|
||||
|
||||
let validateServerAction = setupAction()
|
||||
|
||||
do {
|
||||
_ = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
XCTFail("testValidateServerAction_ConsensusBranchIDError_ValidRemoteBranch is expected to fail.")
|
||||
} catch let ZcashError.compactBlockProcessorWrongConsensusBranchId(expected, found) {
|
||||
XCTAssertEqual(expected, -1026109260)
|
||||
XCTAssertEqual(found, 1)
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testValidateServerAction_ConsensusBranchIDError_ValidRemoteBranch is expected to fail but error \(error) doesn't match \
|
||||
ZcashError.compactBlockProcessorWrongConsensusBranchId
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAction() -> ValidateServerAction {
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: underlyingNetworkType), walletBirthday: 0
|
||||
)
|
||||
|
||||
let rustBackendMock = ZcashRustBackendWeldingMock(
|
||||
consensusBranchIdForHeightClosure: { height in
|
||||
XCTAssertEqual(height, 2, "")
|
||||
return -1026109260
|
||||
}
|
||||
)
|
||||
|
||||
let lightWalletdInfoMock = LightWalletdInfoMock()
|
||||
lightWalletdInfoMock.underlyingConsensusBranchID = underlyingConsensusBranchID
|
||||
lightWalletdInfoMock.underlyingSaplingActivationHeight = UInt64(underlyingSaplingActivationHeight ?? config.saplingActivation)
|
||||
lightWalletdInfoMock.underlyingBlockHeight = 2
|
||||
lightWalletdInfoMock.underlyingChainName = underlyingChainName
|
||||
|
||||
let serviceMock = LightWalletServiceMock()
|
||||
serviceMock.getInfoReturnValue = lightWalletdInfoMock
|
||||
|
||||
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in rustBackendMock }
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in serviceMock }
|
||||
|
||||
return ValidateServerAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
//
|
||||
// CompactBlockProcessorOfflineTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 15.12.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class CompactBlockProcessorOfflineTests: ZcashTestCase {
|
||||
let testFileManager = FileManager()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
|
||||
Dependencies.setup(
|
||||
in: mockContainer,
|
||||
urls: Initializer.URLs(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
dataDbURL: try! __dataDbURL(),
|
||||
generalStorageURL: testGeneralStorageDirectory,
|
||||
spendParamsURL: try! __spendParamsURL(),
|
||||
outputParamsURL: try! __outputParamsURL()
|
||||
),
|
||||
alias: .default,
|
||||
networkType: .testnet,
|
||||
endpoint: LightWalletEndpointBuilder.default,
|
||||
loggingPolicy: .default(.debug)
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
try super.tearDownWithError()
|
||||
}
|
||||
|
||||
func testComputeProcessingRangeForSingleLoop() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: network,
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 690000,
|
||||
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
|
||||
)
|
||||
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
||||
|
||||
let storage = FSCompactBlockRepository(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
metadataStore: FSMetadataStore.live(
|
||||
fsBlockDbRoot: testTempDirectory,
|
||||
rustBackend: rustBackend,
|
||||
logger: logger
|
||||
),
|
||||
blockDescriptor: .live,
|
||||
contentProvider: DirectoryListingProviders.defaultSorted,
|
||||
logger: logger
|
||||
)
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in storage }
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in LatestBlocksDataProviderMock() }
|
||||
|
||||
let processor = CompactBlockProcessor(
|
||||
container: mockContainer,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
let fullRange = 0...1000
|
||||
|
||||
var range = await processor.computeSingleLoopDownloadRange(fullRange: fullRange, loopCounter: 0, batchSize: 100)
|
||||
XCTAssertEqual(range, 0...99)
|
||||
|
||||
range = await processor.computeSingleLoopDownloadRange(fullRange: fullRange, loopCounter: 5, batchSize: 100)
|
||||
XCTAssertEqual(range, 500...599)
|
||||
|
||||
range = await processor.computeSingleLoopDownloadRange(fullRange: fullRange, loopCounter: 10, batchSize: 100)
|
||||
XCTAssertEqual(range, 1000...1000)
|
||||
}
|
||||
}
|
|
@ -66,8 +66,8 @@ class InternalSyncProgressTests: ZcashTestCase {
|
|||
|
||||
switch nextState {
|
||||
case let .processNewBlocks(ranges):
|
||||
XCTAssertEqual(ranges.downloadedButUnscannedRange, 620001...630000)
|
||||
XCTAssertEqual(ranges.downloadAndScanRange, 630001...640000)
|
||||
XCTAssertEqual(ranges.downloadRange, 630001...640000)
|
||||
XCTAssertEqual(ranges.scanRange, 620001...640000)
|
||||
XCTAssertEqual(ranges.enhanceRange, 630001...640000)
|
||||
XCTAssertEqual(ranges.fetchUTXORange, 630001...640000)
|
||||
|
||||
|
|
|
@ -368,7 +368,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testIsNewSessionOnUnpreparedToValidTransition() {
|
||||
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(.nullProgress)))
|
||||
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0)))
|
||||
}
|
||||
|
||||
func testIsNotNewSessionOnUnpreparedToStateThatWontSync() {
|
||||
|
@ -378,18 +378,16 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
|
||||
func testIsNotNewSessionOnUnpreparedToInvalidOrUnexpectedTransitions() {
|
||||
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .synced))
|
||||
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .fetching(0)))
|
||||
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .enhancing(.zero)))
|
||||
}
|
||||
|
||||
func testIsNotNewSyncSessionOnSameSession() {
|
||||
XCTAssertFalse(
|
||||
SessionTicker.live.isNewSyncSession(
|
||||
.syncing(
|
||||
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 3)
|
||||
0.5
|
||||
),
|
||||
.syncing(
|
||||
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
|
||||
0.6
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -400,7 +398,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
SessionTicker.live.isNewSyncSession(
|
||||
.synced,
|
||||
.syncing(
|
||||
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
|
||||
0.6
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -411,7 +409,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
SessionTicker.live.isNewSyncSession(
|
||||
.disconnected,
|
||||
.syncing(
|
||||
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
|
||||
0.6
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -422,7 +420,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
SessionTicker.live.isNewSyncSession(
|
||||
.stopped,
|
||||
.syncing(
|
||||
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
|
||||
0.6
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -430,17 +428,16 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
|
||||
func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
|
||||
XCTAssertFalse(InternalSyncStatus.disconnected.isDifferent(from: .disconnected))
|
||||
XCTAssertFalse(InternalSyncStatus.fetching(0).isDifferent(from: .fetching(0)))
|
||||
XCTAssertFalse(InternalSyncStatus.syncing(0).isDifferent(from: .syncing(0)))
|
||||
XCTAssertFalse(InternalSyncStatus.stopped.isDifferent(from: .stopped))
|
||||
XCTAssertFalse(InternalSyncStatus.synced.isDifferent(from: .synced))
|
||||
XCTAssertFalse(InternalSyncStatus.syncing(.nullProgress).isDifferent(from: .syncing(.nullProgress)))
|
||||
XCTAssertFalse(InternalSyncStatus.unprepared.isDifferent(from: .unprepared))
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_SyncingLowerBound() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 0))
|
||||
InternalSyncStatus.syncing(0)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.0, data) {
|
||||
|
@ -451,7 +448,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
func testInternalSyncStatusMap_SyncingInTheMiddle() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 50))
|
||||
InternalSyncStatus.syncing(0.45)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.45, data) {
|
||||
|
@ -462,7 +459,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
func testInternalSyncStatusMap_SyncingUpperBound() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 100))
|
||||
InternalSyncStatus.syncing(0.9)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.9, data) {
|
||||
|
@ -470,81 +467,8 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_EnhancingLowerBound() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 100,
|
||||
enhancedTransactions: 0,
|
||||
lastFoundTransaction: nil,
|
||||
range: CompactBlockRange(uncheckedBounds: (0, 100)),
|
||||
newlyMined: false
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.9, data) {
|
||||
XCTFail("Syncing is expected to be 90% (0.9) but received \(data).")
|
||||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_EnhancingInTheMiddle() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 100,
|
||||
enhancedTransactions: 50,
|
||||
lastFoundTransaction: nil,
|
||||
range: CompactBlockRange(uncheckedBounds: (0, 100)),
|
||||
newlyMined: false
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.94, data) {
|
||||
XCTFail("Syncing is expected to be 94% (0.94) but received \(data).")
|
||||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_EnhancingUpperBound() {
|
||||
let synchronizerState = synchronizerState(
|
||||
for:
|
||||
InternalSyncStatus.enhancing(
|
||||
EnhancementProgress(
|
||||
totalTransactions: 100,
|
||||
enhancedTransactions: 100,
|
||||
lastFoundTransaction: nil,
|
||||
range: CompactBlockRange(uncheckedBounds: (0, 100)),
|
||||
newlyMined: false
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.98, data) {
|
||||
XCTFail("Syncing is expected to be 98% (0.98) but received \(data).")
|
||||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_FetchingLowerBound() {
|
||||
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(0))
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.98, data) {
|
||||
XCTFail("Syncing is expected to be 98% (0.98) but received \(data).")
|
||||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_FetchingInTheMiddle() {
|
||||
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(0.5))
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.99, data) {
|
||||
XCTFail("Syncing is expected to be 99% (0.99) but received \(data).")
|
||||
}
|
||||
}
|
||||
|
||||
func testInternalSyncStatusMap_FetchingUpperBound() {
|
||||
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(1))
|
||||
let synchronizerState = synchronizerState(for: InternalSyncStatus.syncing(1))
|
||||
|
||||
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(1.0, data) {
|
||||
XCTFail("Syncing is expected to be 100% (1.0) but received \(data).")
|
||||
|
|
|
@ -18,6 +18,7 @@ class CompactBlockProcessorEventHandler {
|
|||
case minedTransaction
|
||||
case handleReorg
|
||||
case progressUpdated
|
||||
case progressPartialUpdate
|
||||
case storedUTXOs
|
||||
case startedEnhancing
|
||||
case startedFetching
|
||||
|
@ -63,6 +64,8 @@ extension CompactBlockProcessor.Event {
|
|||
return .stopped
|
||||
case .minedTransaction:
|
||||
return .minedTransaction
|
||||
case .progressPartialUpdate:
|
||||
return .progressPartialUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// LatestBlocksDataProviderMock.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 12.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
actor LatestBlocksDataProviderMock: LatestBlocksDataProvider {
|
||||
private(set) var latestScannedHeight: BlockHeight = .zero
|
||||
private(set) var latestScannedTime: TimeInterval = 0.0
|
||||
private(set) var latestBlockHeight: BlockHeight = .zero
|
||||
private(set) var walletBirthday: BlockHeight = .zero
|
||||
|
||||
init(
|
||||
latestScannedHeight: BlockHeight = .zero,
|
||||
latestScannedTime: TimeInterval = 0,
|
||||
latestBlockHeight: BlockHeight = .zero,
|
||||
walletBirthday: BlockHeight = .zero
|
||||
) {
|
||||
self.latestScannedHeight = latestScannedHeight
|
||||
self.latestScannedTime = latestScannedTime
|
||||
self.latestBlockHeight = latestBlockHeight
|
||||
self.walletBirthday = walletBirthday
|
||||
}
|
||||
|
||||
func updateScannedData() async { }
|
||||
|
||||
func updateBlockData() async { }
|
||||
|
||||
func updateWalletBirthday(_ walletBirthday: BlockHeight) async { }
|
||||
|
||||
func updateLatestScannedHeight(_ latestScannedHeight: BlockHeight) async { }
|
||||
|
||||
func updateLatestScannedTime(_ latestScannedTime: TimeInterval) async { }
|
||||
}
|
|
@ -39,8 +39,6 @@ extension InternalSyncStatus {
|
|||
switch self {
|
||||
case .unprepared: return .unprepared
|
||||
case .syncing: return .syncing
|
||||
case .enhancing: return .enhancing
|
||||
case .fetching: return .fetching
|
||||
case .synced: return .synced
|
||||
case .stopped: return .stopped
|
||||
case .disconnected: return .disconnected
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Combine
|
||||
@testable import ZcashLightClientKit
|
||||
import Foundation
|
||||
|
||||
{% macro methodName method%}{%if method|annotated:"mockedName" %}{{ method.annotations.mockedName }}{% else %}{% call swiftifyMethodName method.selectorName %}{% endif %}{% endmacro %}
|
||||
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
|
||||
|
@ -83,7 +84,7 @@ import Combine
|
|||
{% if method.isStatic %}Self.{% endif %}{% call methodName method %}CallsCount += 1
|
||||
{% call methodReceivedParameters method %}
|
||||
{% if method.returnTypeName.isVoid %}
|
||||
{% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
|
||||
{% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}{% call methodClosureName method %}!({% call methodClosureCallParameters method %})
|
||||
{% else %}
|
||||
if let closure = {% if method.isStatic %}Self.{% endif %}{% call methodClosureName method %} {
|
||||
return {% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}closure({% call methodClosureCallParameters method %})
|
||||
|
|
|
@ -12,7 +12,22 @@
|
|||
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
extension ZcashRustBackendWelding { }
|
||||
extension BlockDownloader { }
|
||||
extension BlockDownloaderService { }
|
||||
extension BlockEnhancer { }
|
||||
extension BlockScanner { }
|
||||
extension BlockValidator { }
|
||||
extension CompactBlockRepository { }
|
||||
extension InternalSyncProgressStorage { }
|
||||
extension LatestBlocksDataProvider { }
|
||||
extension LightWalletdInfo { }
|
||||
extension LightWalletService { }
|
||||
extension Logger { }
|
||||
extension SaplingParametersHandler { }
|
||||
extension Synchronizer { }
|
||||
extension TransactionRepository { }
|
||||
extension UTXOFetcher { }
|
||||
extension ZcashFileManager { }
|
||||
extension ZcashRustBackendWelding { }
|
||||
|
||||
// sourcery:end:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -209,7 +209,7 @@ extension SynchronizerState {
|
|||
syncSessionID: .nullID,
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
|
||||
internalSyncStatus: .fetching(0),
|
||||
internalSyncStatus: .syncing(0),
|
||||
latestScannedHeight: 111111,
|
||||
latestBlockHeight: 222222,
|
||||
latestScannedTime: 12345678
|
||||
|
|
|
@ -213,29 +213,26 @@ extension TestCoordinator {
|
|||
try await service.latestBlockHeight()
|
||||
}
|
||||
|
||||
func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) throws {
|
||||
Task {
|
||||
await self.synchronizer.blockProcessor.stop()
|
||||
let config = await self.synchronizer.blockProcessor.config
|
||||
func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) async throws {
|
||||
await self.synchronizer.blockProcessor.stop()
|
||||
|
||||
let newConfig = CompactBlockProcessor.Configuration(
|
||||
alias: config.alias,
|
||||
fsBlockCacheRoot: config.fsBlockCacheRoot,
|
||||
dataDb: config.dataDb,
|
||||
spendParamsURL: config.spendParamsURL,
|
||||
outputParamsURL: config.outputParamsURL,
|
||||
saplingParamsSourceURL: config.saplingParamsSourceURL,
|
||||
downloadBatchSize: config.downloadBatchSize,
|
||||
retries: config.retries,
|
||||
maxBackoffInterval: config.maxBackoffInterval,
|
||||
rewindDistance: config.rewindDistance,
|
||||
walletBirthdayProvider: config.walletBirthdayProvider,
|
||||
saplingActivation: saplingActivation,
|
||||
network: config.network
|
||||
)
|
||||
let config = await self.synchronizer.blockProcessor.config
|
||||
let newConfig = CompactBlockProcessor.Configuration(
|
||||
alias: config.alias,
|
||||
fsBlockCacheRoot: config.fsBlockCacheRoot,
|
||||
dataDb: config.dataDb,
|
||||
spendParamsURL: config.spendParamsURL,
|
||||
outputParamsURL: config.outputParamsURL,
|
||||
saplingParamsSourceURL: config.saplingParamsSourceURL,
|
||||
retries: config.retries,
|
||||
maxBackoffInterval: config.maxBackoffInterval,
|
||||
rewindDistance: config.rewindDistance,
|
||||
walletBirthdayProvider: config.walletBirthdayProvider,
|
||||
saplingActivation: saplingActivation,
|
||||
network: config.network
|
||||
)
|
||||
|
||||
await self.synchronizer.blockProcessor.update(config: newConfig)
|
||||
}
|
||||
await self.synchronizer.blockProcessor.update(config: newConfig)
|
||||
|
||||
try service.reset(saplingActivation: saplingActivation, branchID: branchID, chainName: chainName)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
@startuml
|
||||
hide empty description
|
||||
|
||||
note as Lines
|
||||
Green lines are happy paths.
|
||||
Red lines are error paths.
|
||||
Blue lines are stop paths.
|
||||
|
||||
In general any action for any state can produce error.
|
||||
And the sync process can be stopped during any action.
|
||||
end note
|
||||
|
||||
|
||||
[*] -> idle
|
||||
|
||||
idle -[#green,bold]-> migrateLegacyCacheDB
|
||||
|
||||
migrateLegacyCacheDB : MigrateLegacyCacheDBAction
|
||||
migrateLegacyCacheDB -[#green,bold]-> validateServer
|
||||
migrateLegacyCacheDB -[#red]-> failed : Error occured.
|
||||
migrateLegacyCacheDB -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
validateServer : ValidateServerAction
|
||||
validateServer -[#green,bold]-> computeSyncRanges
|
||||
validateServer -[#red]-> failed : Error occured.
|
||||
validateServer -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
computeSyncRanges : ComputeSyncRangesAction
|
||||
computeSyncRanges -[#green,bold]-> checksBeforeSync
|
||||
computeSyncRanges -[#red]-> failed : Error occured.
|
||||
computeSyncRanges -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
checksBeforeSync : ChecksBeforeSyncAction
|
||||
checksBeforeSync -[#green,bold]-> fetchUTXO
|
||||
checksBeforeSync -[#red]-> failed : Error occured.
|
||||
checksBeforeSync -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
fetchUTXO : FetchUTXOAction
|
||||
fetchUTXO -[#green,bold]-> handleSaplingParams
|
||||
fetchUTXO -[#red]-> failed : Error occured.
|
||||
fetchUTXO -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
handleSaplingParams : SaplingParamsAction
|
||||
handleSaplingParams -[#green,bold]-> download
|
||||
handleSaplingParams -[#red]-> failed : Error occured.
|
||||
handleSaplingParams -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
download : DownloadAction
|
||||
download -[#green,bold]-> validate
|
||||
download -[#red]-> failed : Error occured.
|
||||
download -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
validate : ValidateAction
|
||||
validate -[#green,bold]-> scan
|
||||
validate -[#red]-> failed : Error occured.
|
||||
validate -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
scan : ScanAction
|
||||
scan -[#green,bold]-> clearAlreadyScannedBlocks
|
||||
scan -[#red]-> failed : Error occured.
|
||||
scan -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
clearAlreadyScannedBlocks : ClearAlreadyScannedBlocksAction
|
||||
clearAlreadyScannedBlocks -[#green,bold]-> enhance
|
||||
clearAlreadyScannedBlocks -[#red]-> failed : Error occured.
|
||||
clearAlreadyScannedBlocks -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
enhance : EnhanceAction
|
||||
enhance -[#green,bold]-> download : Not all blocks in the\nsync range are downloaded\nand scanned yet.
|
||||
enhance -[#green,bold]-> clearCache : All the blocks in\nthe sync range are downloaded\nand scanned.
|
||||
enhance -[#red]-> failed : Error occured.
|
||||
enhance -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
note right of enhance
|
||||
Enhance transactions in batches of 1000
|
||||
blocks. Dont't do it for each scan batch
|
||||
which is usualy 100 blocks.
|
||||
end note
|
||||
|
||||
clearCache : ClearCacheAction
|
||||
clearCache --> finished
|
||||
clearCache -[#red]-> failed : Error occured.
|
||||
clearCache -[#blue]-> stopped : Sync was stopped.
|
||||
|
||||
finished --> [*]
|
||||
failed --> [*]
|
||||
stopped --> [*]
|
||||
|
||||
@enduml
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 253 KiB |
Loading…
Reference in New Issue