Merge pull request #1149 from zcash/feature/pre-dag
The feature branch consists of 3 main parts of the new synchronization management - the State Machine being aware of previous state - the new firstUnenhancedHeight API - ClearCache being called at the beginning of the State Machine
This commit is contained in:
commit
605af0417f
|
@ -20,6 +20,7 @@ enum DemoAppConfig {
|
|||
|
||||
static let host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "lightwalletd.testnet.electriccoin.co"
|
||||
static let port: Int = 9067
|
||||
|
||||
static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
|
||||
static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
|
||||
live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor
|
||||
|
|
|
@ -29,6 +29,6 @@ class GetBalanceViewController: UIViewController {
|
|||
|
||||
extension Zatoshi {
|
||||
var formattedString: String? {
|
||||
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self.amount))
|
||||
decimalString(formatter: NumberFormatter.zcashNumberFormatter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,25 +9,32 @@ import Foundation
|
|||
|
||||
actor ActionContext {
|
||||
var state: CBPState
|
||||
var syncRanges: SyncRanges
|
||||
var prevState: CBPState?
|
||||
var syncControlData: SyncControlData
|
||||
var totalProgressRange: CompactBlockRange = 0...0
|
||||
var lastDownloadedHeight: BlockHeight?
|
||||
var lastEnhancedHeight: BlockHeight?
|
||||
|
||||
init(state: CBPState) {
|
||||
self.state = state
|
||||
syncRanges = SyncRanges.empty
|
||||
syncControlData = SyncControlData.empty
|
||||
}
|
||||
|
||||
func update(state: CBPState) async { self.state = state }
|
||||
func update(syncRanges: SyncRanges) async { self.syncRanges = syncRanges }
|
||||
func update(state: CBPState) async {
|
||||
prevState = self.state
|
||||
self.state = state
|
||||
}
|
||||
func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
|
||||
func update(totalProgressRange: CompactBlockRange) async { self.totalProgressRange = totalProgressRange }
|
||||
func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
|
||||
func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
|
||||
}
|
||||
|
||||
enum CBPState: CaseIterable {
|
||||
case idle
|
||||
case migrateLegacyCacheDB
|
||||
case validateServer
|
||||
case computeSyncRanges
|
||||
case checksBeforeSync
|
||||
case computeSyncControlData
|
||||
case download
|
||||
case validate
|
||||
case scan
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// ChecksBeforeSyncAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ChecksBeforeSyncAction {
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let storage: CompactBlockRepository
|
||||
init(container: DIContainer) {
|
||||
internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
}
|
||||
|
||||
/// Tells whether the state represented by these sync ranges evidence some sort of
|
||||
/// outdated state on the cache or the internal state of the compact block processor.
|
||||
///
|
||||
/// - Note: this can mean that the processor has synced over the height that the internal
|
||||
/// state knows of because the sync process was interrupted before it could reflect
|
||||
/// it in the internal state storage. This could happen because of many factors, the
|
||||
/// most feasible being OS shutting down a background process or the user abruptly
|
||||
/// exiting the app.
|
||||
/// - Returns: an ``Optional<BlockHeight>`` where Some represents what's the
|
||||
/// new state the internal state should reflect and indicating that the cache should be cleared
|
||||
/// as well. `nil` means that no action is required.
|
||||
func shouldClearBlockCacheAndUpdateInternalState(syncRange: SyncRanges) -> BlockHeight? {
|
||||
guard syncRange.downloadRange != nil, syncRange.scanRange != nil else { return nil }
|
||||
|
||||
guard
|
||||
let latestScannedHeight = syncRange.latestScannedHeight,
|
||||
let latestDownloadedHeight = syncRange.latestDownloadedBlockHeight,
|
||||
latestScannedHeight > latestDownloadedHeight
|
||||
else { return nil }
|
||||
|
||||
return latestScannedHeight
|
||||
}
|
||||
}
|
||||
|
||||
extension ChecksBeforeSyncAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
// clear any present cached state if needed.
|
||||
// this checks if there was a sync in progress that was
|
||||
// interrupted abruptly and cache was not able to be cleared
|
||||
// properly and internal state set to the appropriate value
|
||||
if let newLatestDownloadedHeight = shouldClearBlockCacheAndUpdateInternalState(syncRange: await context.syncRanges) {
|
||||
try await storage.clear()
|
||||
try await internalSyncProgress.set(newLatestDownloadedHeight, .latestDownloadedBlockHeight)
|
||||
} else {
|
||||
try await storage.create()
|
||||
}
|
||||
|
||||
await context.update(state: .fetchUTXO)
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -10,6 +10,7 @@ import Foundation
|
|||
final class ClearAlreadyScannedBlocksAction {
|
||||
let storage: CompactBlockRepository
|
||||
let transactionRepository: TransactionRepository
|
||||
|
||||
init(container: DIContainer) {
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
|
||||
final class ClearCacheAction {
|
||||
let storage: CompactBlockRepository
|
||||
|
||||
init(container: DIContainer) {
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
}
|
||||
|
@ -19,7 +20,11 @@ extension ClearCacheAction: Action {
|
|||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
try await storage.clear()
|
||||
await context.update(state: .finished)
|
||||
if await context.prevState == .idle {
|
||||
await context.update(state: .migrateLegacyCacheDB)
|
||||
} else {
|
||||
await context.update(state: .finished)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// ComputeSyncControlDataAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ComputeSyncControlDataAction {
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let downloaderService: BlockDownloaderService
|
||||
let latestBlocksDataProvider: LatestBlocksDataProvider
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
downloaderService = container.resolve(BlockDownloaderService.self)
|
||||
latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComputeSyncControlDataAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
let config = await configProvider.config
|
||||
|
||||
await latestBlocksDataProvider.updateScannedData()
|
||||
await latestBlocksDataProvider.updateBlockData()
|
||||
|
||||
// Here we know:
|
||||
// - latest scanned height from the DB, if none the wallet's birthday is automatically used
|
||||
// - first unenhanced height from the DB, could be nil = up to latestScannedHeight nothing to enhance
|
||||
// - latest downloaded height reported by downloaderService
|
||||
// - latest block height on the blockchain
|
||||
// - wallet birthday for the initial scan
|
||||
|
||||
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
|
||||
let latestScannedHeightDB = await latestBlocksDataProvider.latestScannedHeight
|
||||
let latestScannedHeight = latestScannedHeightDB < config.walletBirthday ? config.walletBirthday : latestScannedHeightDB
|
||||
let firstUnenhancedHeight = await latestBlocksDataProvider.firstUnenhancedHeight
|
||||
let enhanceStart: BlockHeight
|
||||
if let firstUnenhancedHeight {
|
||||
enhanceStart = min(firstUnenhancedHeight, latestScannedHeight)
|
||||
} else {
|
||||
enhanceStart = latestScannedHeight
|
||||
}
|
||||
|
||||
logger.debug("""
|
||||
Init numbers:
|
||||
latestBlockHeight [BC]: \(latestBlockHeight)
|
||||
latestScannedHeight [DB]: \(latestScannedHeight)
|
||||
firstUnenhancedHeight [DB]: \(firstUnenhancedHeight ?? -1)
|
||||
enhanceStart: \(enhanceStart)
|
||||
walletBirthday: \(config.walletBirthday)
|
||||
""")
|
||||
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
latestScannedHeight: latestScannedHeight,
|
||||
firstUnenhancedHeight: enhanceStart
|
||||
)
|
||||
|
||||
await context.update(lastDownloadedHeight: latestScannedHeight)
|
||||
await context.update(syncControlData: syncControlData)
|
||||
await context.update(totalProgressRange: latestScannedHeight...latestBlockHeight)
|
||||
|
||||
// if there is nothing sync just switch to finished state
|
||||
if latestBlockHeight < latestScannedHeight || latestBlockHeight == latestScannedHeight {
|
||||
await context.update(state: .finished)
|
||||
} else {
|
||||
await context.update(state: .fetchUTXO)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
//
|
||||
// ComputeSyncRangesAction.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 05.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class ComputeSyncRangesAction {
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let downloaderService: BlockDownloaderService
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let latestBlocksDataProvider: LatestBlocksDataProvider
|
||||
let logger: Logger
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
downloaderService = container.resolve(BlockDownloaderService.self)
|
||||
internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
||||
latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
}
|
||||
|
||||
/// This method analyses what must be done and computes range that should be used to compute reported progress.
|
||||
func computeTotalProgressRange(from syncRanges: SyncRanges) -> CompactBlockRange {
|
||||
guard syncRanges.downloadRange != nil || syncRanges.scanRange != nil else {
|
||||
// In this case we are sure that no downloading or scanning happens so this returned range won't be even used. And it's easier to return
|
||||
// this "fake" range than to handle nil.
|
||||
return 0...0
|
||||
}
|
||||
|
||||
// Thanks to guard above we can be sure that one of these two ranges is not nil.
|
||||
let lowerBound = syncRanges.scanRange?.lowerBound ?? syncRanges.downloadRange?.lowerBound ?? 0
|
||||
let upperBound = syncRanges.scanRange?.upperBound ?? syncRanges.downloadRange?.upperBound ?? 0
|
||||
|
||||
return lowerBound...upperBound
|
||||
}
|
||||
}
|
||||
|
||||
extension ComputeSyncRangesAction: Action {
|
||||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
// call internalSyncProgress and compute sync ranges and store them in context
|
||||
// if there is nothing sync just switch to finished state
|
||||
|
||||
let config = await configProvider.config
|
||||
let latestDownloadHeight = try await downloaderService.lastDownloadedBlockHeight()
|
||||
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadHeight, alias: config.alias)
|
||||
|
||||
await latestBlocksDataProvider.updateScannedData()
|
||||
await latestBlocksDataProvider.updateBlockData()
|
||||
|
||||
let nextState = try await internalSyncProgress.computeNextState(
|
||||
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
|
||||
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
|
||||
walletBirthday: config.walletBirthday
|
||||
)
|
||||
|
||||
switch nextState {
|
||||
case .finishProcessing:
|
||||
await context.update(state: .finished)
|
||||
case .processNewBlocks(let ranges):
|
||||
let totalProgressRange = computeTotalProgressRange(from: ranges)
|
||||
await context.update(totalProgressRange: totalProgressRange)
|
||||
await context.update(syncRanges: ranges)
|
||||
await context.update(state: .checksBeforeSync)
|
||||
|
||||
logger.debug("""
|
||||
Syncing with ranges:
|
||||
download: \(ranges.downloadRange?.lowerBound ?? -1)...\(ranges.downloadRange?.upperBound ?? -1)
|
||||
scan: \(ranges.scanRange?.lowerBound ?? -1)...\(ranges.scanRange?.upperBound ?? -1)
|
||||
enhance range: \(ranges.enhanceRange?.lowerBound ?? -1)...\(ranges.enhanceRange?.upperBound ?? -1)
|
||||
fetchUTXO range: \(ranges.fetchUTXORange?.lowerBound ?? -1)...\(ranges.fetchUTXORange?.upperBound ?? -1)
|
||||
total progress range: \(totalProgressRange.lowerBound)...\(totalProgressRange.upperBound)
|
||||
""")
|
||||
|
||||
case let .wait(latestHeight, latestDownloadHeight):
|
||||
// Lightwalletd might be syncing
|
||||
logger.info(
|
||||
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight) " +
|
||||
"while latest blockheight is reported at: \(latestHeight)"
|
||||
)
|
||||
await context.update(state: .finished)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func stop() async { }
|
||||
}
|
|
@ -30,16 +30,17 @@ extension DownloadAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { true }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
guard let downloadRange = await context.syncRanges.downloadRange else {
|
||||
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
|
||||
// So the right range for this batch must be computed.
|
||||
let batchRangeStart = max(downloadRange.lowerBound, lastScannedHeight)
|
||||
let batchRangeEnd = min(downloadRange.upperBound, batchRangeStart + config.batchSize)
|
||||
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
|
||||
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
return await update(context: context)
|
||||
|
@ -49,7 +50,8 @@ extension DownloadAction: Action {
|
|||
let downloadLimit = batchRange.upperBound + (2 * config.batchSize)
|
||||
|
||||
logger.debug("Starting download with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
|
||||
try await downloader.setSyncRange(downloadRange, batchSize: config.batchSize)
|
||||
await downloader.update(latestDownloadedBlockHeight: batchRange.lowerBound)
|
||||
try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
|
||||
await downloader.setDownloadLimit(downloadLimit)
|
||||
await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
|
||||
|
||||
|
|
|
@ -10,24 +10,24 @@ import Foundation
|
|||
final class EnhanceAction {
|
||||
let blockEnhancer: BlockEnhancer
|
||||
let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let logger: Logger
|
||||
let transactionRepository: TransactionRepository
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
blockEnhancer = container.resolve(BlockEnhancer.self)
|
||||
self.configProvider = configProvider
|
||||
internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
||||
logger = container.resolve(Logger.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
}
|
||||
|
||||
func decideWhatToDoNext(context: ActionContext, lastScannedHeight: BlockHeight) async -> ActionContext {
|
||||
guard let scanRange = await context.syncRanges.scanRange else {
|
||||
guard await context.syncControlData.latestScannedHeight != nil else {
|
||||
await context.update(state: .clearCache)
|
||||
return context
|
||||
}
|
||||
|
||||
if lastScannedHeight >= scanRange.upperBound {
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
if lastScannedHeight >= latestBlockHeight {
|
||||
await context.update(state: .clearCache)
|
||||
} else {
|
||||
await context.update(state: .download)
|
||||
|
@ -43,7 +43,6 @@ extension EnhanceAction: Action {
|
|||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
// Use `BlockEnhancer` to enhance blocks.
|
||||
// This action is executed on each downloaded and scanned batch (typically each 100 blocks). But we want to run enhancement each 1000 blocks.
|
||||
// This action can use `InternalSyncProgress` and last scanned height to compute when it should do work.
|
||||
|
||||
// if latestScannedHeight >= context.scanRanges.scanRange.upperBound then everything is processed and sync process should continue to end.
|
||||
// If latestScannedHeight < context.scanRanges.scanRange.upperBound then set state to `download` because there are blocks to
|
||||
|
@ -52,22 +51,28 @@ extension EnhanceAction: Action {
|
|||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
|
||||
guard let range = await context.syncRanges.enhanceRange else {
|
||||
guard let firstUnenhancedHeight = await context.syncControlData.firstUnenhancedHeight else {
|
||||
return await decideWhatToDoNext(context: context, lastScannedHeight: lastScannedHeight)
|
||||
}
|
||||
|
||||
let lastEnhancedHeight = try await internalSyncProgress.load(.latestEnhancedHeight)
|
||||
let enhanceRangeStart = max(range.lowerBound, lastEnhancedHeight)
|
||||
let enhanceRangeEnd = min(range.upperBound, lastScannedHeight)
|
||||
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
let lastEnhancedHeight: BlockHeight
|
||||
if let lastEnhancedHeightInContext = await context.lastEnhancedHeight {
|
||||
lastEnhancedHeight = lastEnhancedHeightInContext
|
||||
} else {
|
||||
lastEnhancedHeight = -1
|
||||
}
|
||||
let enhanceRangeStart = max(firstUnenhancedHeight, lastEnhancedHeight + 1)
|
||||
let enhanceRangeEnd = min(latestBlockHeight, lastScannedHeight)
|
||||
|
||||
// This may happen:
|
||||
// For example whole enhance range is 0...2100 Without this force enhance is done for ranges: 0...1000, 1001...2000. And that's it.
|
||||
// Last 100 blocks isn't enhanced.
|
||||
//
|
||||
// This force makes sure that all the blocks are enhanced even when last enhance happened < 1000 blocks ago.
|
||||
let forceEnhance = enhanceRangeEnd == range.upperBound && enhanceRangeEnd - enhanceRangeStart <= config.enhanceBatchSize
|
||||
let forceEnhance = enhanceRangeEnd == latestBlockHeight && enhanceRangeEnd - enhanceRangeStart <= config.enhanceBatchSize
|
||||
|
||||
if forceEnhance || (enhanceRangeStart <= enhanceRangeEnd && lastScannedHeight - lastEnhancedHeight >= config.enhanceBatchSize) {
|
||||
if enhanceRangeStart <= enhanceRangeEnd && (forceEnhance || (lastScannedHeight - lastEnhancedHeight >= config.enhanceBatchSize)) {
|
||||
let enhanceRange = enhanceRangeStart...enhanceRangeEnd
|
||||
let transactions = try await blockEnhancer.enhance(
|
||||
at: enhanceRange,
|
||||
|
@ -79,6 +84,8 @@ extension EnhanceAction: Action {
|
|||
}
|
||||
)
|
||||
|
||||
await context.update(lastEnhancedHeight: enhanceRange.upperBound)
|
||||
|
||||
if let transactions {
|
||||
await didUpdate(.foundTransactions(transactions, enhanceRange))
|
||||
}
|
||||
|
|
|
@ -21,14 +21,12 @@ extension FetchUTXOsAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { false }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
if let range = await context.syncRanges.fetchUTXORange {
|
||||
logger.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
|
||||
let result = try await utxoFetcher.fetch(at: range) { fetchProgress in
|
||||
await didUpdate(.progressPartialUpdate(.fetch(fetchProgress)))
|
||||
}
|
||||
await didUpdate(.storedUTXOs(result))
|
||||
logger.debug("Fetching UTXOs")
|
||||
let result = try await utxoFetcher.fetch() { fetchProgress in
|
||||
await didUpdate(.progressPartialUpdate(.fetch(fetchProgress)))
|
||||
}
|
||||
|
||||
await didUpdate(.storedUTXOs(result))
|
||||
|
||||
await context.update(state: .handleSaplingParams)
|
||||
return context
|
||||
}
|
||||
|
|
|
@ -9,14 +9,12 @@ import Foundation
|
|||
|
||||
final class MigrateLegacyCacheDBAction {
|
||||
private let configProvider: CompactBlockProcessor.ConfigProvider
|
||||
private let internalSyncProgress: InternalSyncProgress
|
||||
private let storage: CompactBlockRepository
|
||||
private let transactionRepository: TransactionRepository
|
||||
private let fileManager: ZcashFileManager
|
||||
|
||||
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
|
||||
self.configProvider = configProvider
|
||||
internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
||||
storage = container.resolve(CompactBlockRepository.self)
|
||||
transactionRepository = container.resolve(TransactionRepository.self)
|
||||
fileManager = container.resolve(ZcashFileManager.self)
|
||||
|
@ -43,7 +41,7 @@ extension MigrateLegacyCacheDBAction: Action {
|
|||
|
||||
// Instance with alias `default` is same as instance before the Alias was introduced. So it makes sense that only this instance handles
|
||||
// legacy cache DB. Any instance with different than `default` alias was created after the Alias was introduced and at this point legacy
|
||||
// cache DB is't anymore. So there is nothing to migrate for instances with not default Alias.
|
||||
// cache DB doesn't exist anymore. So there is nothing to migrate for instances with not default Alias.
|
||||
guard config.alias == .default else {
|
||||
return await updateState(context)
|
||||
}
|
||||
|
@ -65,13 +63,6 @@ extension MigrateLegacyCacheDBAction: Action {
|
|||
// create the storage
|
||||
try await self.storage.create()
|
||||
|
||||
// The database has been deleted, so we have adjust the internal state of the
|
||||
// `CompactBlockProcessor` so that it doesn't rely on download heights set
|
||||
// by a previous processing cycle.
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
|
||||
try await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
|
||||
|
||||
return await updateState(context)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,16 +30,17 @@ extension ScanAction: Action {
|
|||
var removeBlocksCacheWhenFailed: Bool { true }
|
||||
|
||||
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
|
||||
guard let scanRange = await context.syncRanges.scanRange else {
|
||||
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
|
||||
return await update(context: context)
|
||||
}
|
||||
|
||||
let config = await configProvider.config
|
||||
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
||||
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
|
||||
let latestBlockHeight = await context.syncControlData.latestBlockHeight
|
||||
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `scanRange` are scanned.
|
||||
// So the right range for this batch must be computed.
|
||||
let batchRangeStart = max(scanRange.lowerBound, lastScannedHeight)
|
||||
let batchRangeEnd = min(scanRange.upperBound, batchRangeStart + config.batchSize)
|
||||
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
|
||||
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
|
||||
|
||||
guard batchRangeStart <= batchRangeEnd else {
|
||||
return await update(context: context)
|
||||
|
|
|
@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
|
|||
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
|
||||
}
|
||||
|
||||
await context.update(state: .computeSyncRanges)
|
||||
await context.update(state: .computeSyncControlData)
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ actor CompactBlockProcessor {
|
|||
|
||||
private let accountRepository: AccountRepository
|
||||
let blockDownloaderService: BlockDownloaderService
|
||||
private let internalSyncProgress: InternalSyncProgress
|
||||
private let latestBlocksDataProvider: LatestBlocksDataProvider
|
||||
private let logger: Logger
|
||||
private let metrics: SDKMetrics
|
||||
|
@ -190,7 +189,6 @@ actor CompactBlockProcessor {
|
|||
self.metrics = container.resolve(SDKMetrics.self)
|
||||
self.logger = container.resolve(Logger.self)
|
||||
self.latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
|
||||
self.internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
||||
self.blockDownloaderService = container.resolve(BlockDownloaderService.self)
|
||||
self.service = container.resolve(LightWalletService.self)
|
||||
self.rustBackend = container.resolve(ZcashRustBackendWelding.self)
|
||||
|
@ -216,10 +214,8 @@ actor CompactBlockProcessor {
|
|||
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
|
||||
case .validateServer:
|
||||
action = ValidateServerAction(container: container, configProvider: configProvider)
|
||||
case .computeSyncRanges:
|
||||
action = ComputeSyncRangesAction(container: container, configProvider: configProvider)
|
||||
case .checksBeforeSync:
|
||||
action = ChecksBeforeSyncAction(container: container)
|
||||
case .computeSyncControlData:
|
||||
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
|
||||
case .download:
|
||||
action = DownloadAction(container: container, configProvider: configProvider)
|
||||
case .validate:
|
||||
|
@ -313,7 +309,7 @@ extension CompactBlockProcessor {
|
|||
|
||||
private func doRewind(context: AfterSyncHooksManager.RewindContext) async throws {
|
||||
logger.debug("Executing rewind.")
|
||||
let lastDownloaded = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
let lastDownloaded = await latestBlocksDataProvider.latestScannedHeight
|
||||
let height = Int32(context.height ?? lastDownloaded)
|
||||
|
||||
let nearestHeight: Int32
|
||||
|
@ -328,6 +324,7 @@ extension CompactBlockProcessor {
|
|||
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
|
||||
|
||||
do {
|
||||
try await rewindDownloadBlockAction(to: BlockHeight(rewindHeight))
|
||||
try await rustBackend.rewindToHeight(height: rewindHeight)
|
||||
} catch {
|
||||
await failure(error)
|
||||
|
@ -342,12 +339,22 @@ extension CompactBlockProcessor {
|
|||
return await context.completion(.failure(error))
|
||||
}
|
||||
|
||||
try await internalSyncProgress.rewind(to: rewindBlockHeight)
|
||||
|
||||
await context.completion(.success(rewindBlockHeight))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
private extension CompactBlockProcessor {
|
||||
func rewindDownloadBlockAction(to rewindHeight: BlockHeight?) async throws {
|
||||
if let downloadAction = actions[.download] as? DownloadAction {
|
||||
await downloadAction.downloader.rewind(latestDownloadedBlockHeight: rewindHeight)
|
||||
} else {
|
||||
throw ZcashError.compactBlockProcessorDownloadBlockActionRewind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Wipe
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
|
@ -369,7 +376,6 @@ extension CompactBlockProcessor {
|
|||
|
||||
do {
|
||||
try await self.storage.clear()
|
||||
try await internalSyncProgress.rewind(to: 0)
|
||||
|
||||
wipeLegacyCacheDbIfNeeded()
|
||||
|
||||
|
@ -378,6 +384,8 @@ extension CompactBlockProcessor {
|
|||
try fileManager.removeItem(at: config.dataDb)
|
||||
}
|
||||
|
||||
try await rewindDownloadBlockAction(to: nil)
|
||||
|
||||
await context.completion(nil)
|
||||
} catch {
|
||||
await context.completion(error)
|
||||
|
@ -468,7 +476,7 @@ extension CompactBlockProcessor {
|
|||
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
||||
await stopAllActions()
|
||||
// Update state to the first state in state machine that can be handled by action.
|
||||
await context.update(state: .migrateLegacyCacheDB)
|
||||
await context.update(state: .clearCache)
|
||||
await syncStarted()
|
||||
|
||||
if backoffTimer == nil {
|
||||
|
@ -579,9 +587,7 @@ extension CompactBlockProcessor {
|
|||
break
|
||||
case .validateServer:
|
||||
break
|
||||
case .computeSyncRanges:
|
||||
break
|
||||
case .checksBeforeSync:
|
||||
case .computeSyncControlData:
|
||||
break
|
||||
case .download:
|
||||
break
|
||||
|
@ -609,7 +615,9 @@ extension CompactBlockProcessor {
|
|||
}
|
||||
|
||||
private func resetContext() async {
|
||||
let lastEnhancedheight = await context.lastEnhancedHeight
|
||||
context = ActionContext(state: .idle)
|
||||
await context.update(lastEnhancedHeight: lastEnhancedheight)
|
||||
await compactBlockProgress.reset()
|
||||
}
|
||||
|
||||
|
@ -621,7 +629,7 @@ extension CompactBlockProcessor {
|
|||
|
||||
private func syncFinished() async -> Bool {
|
||||
logger.debug("Sync finished")
|
||||
let latestBlockHeightWhenSyncing = await context.syncRanges.latestBlockHeight
|
||||
let latestBlockHeightWhenSyncing = await context.syncControlData.latestBlockHeight
|
||||
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
|
||||
// If `latestBlockHeightWhenSyncing` is 0 then it means that there was nothing to sync in last sync process.
|
||||
let newerBlocksWereMinedDuringSync =
|
||||
|
@ -659,7 +667,8 @@ extension CompactBlockProcessor {
|
|||
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
|
||||
|
||||
try await blockDownloaderService.rewind(to: rewindHeight)
|
||||
try await internalSyncProgress.rewind(to: rewindHeight)
|
||||
|
||||
try await rewindDownloadBlockAction(to: rewindHeight)
|
||||
|
||||
await send(event: .handledReorg(height, rewindHeight))
|
||||
}
|
||||
|
@ -758,20 +767,7 @@ extension CompactBlockProcessor {
|
|||
|
||||
private func ifTaskIsNotCanceledClearCompactBlockCache() async {
|
||||
guard !Task.isCancelled else { return }
|
||||
let lastScannedHeight = await latestBlocksDataProvider.latestScannedHeight
|
||||
do {
|
||||
// Blocks download work in parallel with scanning. So imagine this scenario:
|
||||
//
|
||||
// Scanning is done until height 10300. Blocks are downloaded until height 10400.
|
||||
// And now validation fails and this method is called. And `.latestDownloadedBlockHeight` in `internalSyncProgress` is set to 10400. And
|
||||
// all the downloaded blocks are removed here.
|
||||
//
|
||||
// If this line doesn't happen then when sync starts next time it thinks that all the blocks are downloaded until 10400. But all were
|
||||
// removed. So blocks between 10300 and 10400 wouldn't ever be scanned.
|
||||
//
|
||||
// Scanning is done until 10300 so the SDK can be sure that blocks with height below 10300 are not required. So it makes sense to set
|
||||
// `.latestDownloadedBlockHeight` to `lastScannedHeight`. And sync will work fine in next run.
|
||||
try await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
|
||||
try await clearCompactBlockCache()
|
||||
} catch {
|
||||
logger.error("`clearCompactBlockCache` failed after error: \(error)")
|
||||
|
|
|
@ -52,6 +52,14 @@ protocol BlockDownloader {
|
|||
/// called before then nothing is downloaded.
|
||||
/// - Parameter range: Wait until blocks from `range` are downloaded.
|
||||
func waitUntilRequestedBlocksAreDownloaded(in range: CompactBlockRange) async throws
|
||||
|
||||
/// Updates the internal in memory value of latest downloaded block height. This way the `BlockDownloader` works with the current latest height and can
|
||||
/// continue on parallel downloading of next batch.
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async
|
||||
/// Provides the value of latest downloaded height.
|
||||
func latestDownloadedBlockHeight() async -> BlockHeight
|
||||
/// In case rewind is needed, the latestDownloadedBlockHeight is rewritten forcefully.
|
||||
func rewind(latestDownloadedBlockHeight: BlockHeight?) async
|
||||
}
|
||||
|
||||
actor BlockDownloaderImpl {
|
||||
|
@ -62,9 +70,9 @@ actor BlockDownloaderImpl {
|
|||
let service: LightWalletService
|
||||
let downloaderService: BlockDownloaderService
|
||||
let storage: CompactBlockRepository
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let metrics: SDKMetrics
|
||||
let logger: Logger
|
||||
var latestDownloadedBlockHeight: BlockHeight = -1
|
||||
|
||||
private var downloadStreamCreatedAtRange: CompactBlockRange = 0...0
|
||||
private var downloadStream: BlockDownloaderStream?
|
||||
|
@ -80,14 +88,12 @@ actor BlockDownloaderImpl {
|
|||
service: LightWalletService,
|
||||
downloaderService: BlockDownloaderService,
|
||||
storage: CompactBlockRepository,
|
||||
internalSyncProgress: InternalSyncProgress,
|
||||
metrics: SDKMetrics,
|
||||
logger: Logger
|
||||
) {
|
||||
self.service = service
|
||||
self.downloaderService = downloaderService
|
||||
self.storage = storage
|
||||
self.internalSyncProgress = internalSyncProgress
|
||||
self.metrics = metrics
|
||||
self.logger = logger
|
||||
}
|
||||
|
@ -100,8 +106,6 @@ actor BlockDownloaderImpl {
|
|||
throw ZcashError.blockDownloadSyncRangeNotSet
|
||||
}
|
||||
|
||||
let latestDownloadedBlockHeight = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
|
||||
let downloadFrom = max(syncRange.lowerBound, latestDownloadedBlockHeight + 1)
|
||||
let downloadTo = min(downloadToHeight, syncRange.upperBound)
|
||||
|
||||
|
@ -240,11 +244,25 @@ actor BlockDownloaderImpl {
|
|||
|
||||
private func blocksBufferWritten(_ buffer: [ZcashCompactBlock]) async throws {
|
||||
guard let lastBlock = buffer.last else { return }
|
||||
try await internalSyncProgress.set(lastBlock.height, .latestDownloadedBlockHeight)
|
||||
latestDownloadedBlockHeight = lastBlock.height
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockDownloaderImpl: BlockDownloader {
|
||||
func rewind(latestDownloadedBlockHeight: BlockHeight?) async {
|
||||
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight ?? -1
|
||||
}
|
||||
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async {
|
||||
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight {
|
||||
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight
|
||||
}
|
||||
}
|
||||
|
||||
func latestDownloadedBlockHeight() async -> BlockHeight {
|
||||
latestDownloadedBlockHeight
|
||||
}
|
||||
|
||||
func setDownloadLimit(_ limit: BlockHeight) async {
|
||||
downloadToHeight = limit
|
||||
}
|
||||
|
@ -283,13 +301,13 @@ extension BlockDownloaderImpl: BlockDownloader {
|
|||
|
||||
func waitUntilRequestedBlocksAreDownloaded(in range: CompactBlockRange) async throws {
|
||||
logger.debug("Waiting until requested blocks are downloaded at \(range)")
|
||||
var latestDownloadedBlock = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
var latestDownloadedBlock = latestDownloadedBlockHeight
|
||||
while latestDownloadedBlock < range.upperBound {
|
||||
if let error = lastError {
|
||||
throw error
|
||||
}
|
||||
try await Task.sleep(milliseconds: 10)
|
||||
latestDownloadedBlock = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
latestDownloadedBlock = latestDownloadedBlockHeight
|
||||
}
|
||||
logger.debug("Waiting done. Blocks are downloaded at \(range)")
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ protocol BlockEnhancer {
|
|||
|
||||
struct BlockEnhancerImpl {
|
||||
let blockDownloaderService: BlockDownloaderService
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let transactionRepository: TransactionRepository
|
||||
let metrics: SDKMetrics
|
||||
|
@ -96,7 +95,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
|
|||
let transactions = try await transactionRepository.find(in: range, limit: Int.max, kind: .all)
|
||||
|
||||
guard !transactions.isEmpty else {
|
||||
try await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
|
||||
logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
|
||||
return nil
|
||||
}
|
||||
|
@ -126,10 +124,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
|
|||
)
|
||||
|
||||
await didEnhance(progress)
|
||||
|
||||
if let minedHeight = confirmedTx.minedHeight {
|
||||
try await internalSyncProgress.set(minedHeight, .latestEnhancedHeight)
|
||||
}
|
||||
} catch {
|
||||
retries += 1
|
||||
logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
|
||||
|
@ -155,8 +149,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
|
|||
logger.error("error enhancing transactions! \(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
try await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
|
||||
|
||||
if Task.isCancelled {
|
||||
logger.debug("Warning: compactBlockEnhancement on range \(range) cancelled")
|
||||
|
|
|
@ -18,7 +18,6 @@ struct UTXOFetcherConfig {
|
|||
|
||||
protocol UTXOFetcher {
|
||||
func fetch(
|
||||
at range: CompactBlockRange,
|
||||
didFetch: @escaping (Float) async -> Void
|
||||
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
|
||||
}
|
||||
|
@ -27,7 +26,6 @@ struct UTXOFetcherImpl {
|
|||
let accountRepository: AccountRepository
|
||||
let blockDownloaderService: BlockDownloaderService
|
||||
let config: UTXOFetcherConfig
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let metrics: SDKMetrics
|
||||
let logger: Logger
|
||||
|
@ -35,7 +33,6 @@ struct UTXOFetcherImpl {
|
|||
|
||||
extension UTXOFetcherImpl: UTXOFetcher {
|
||||
func fetch(
|
||||
at range: CompactBlockRange,
|
||||
didFetch: @escaping (Float) async -> Void
|
||||
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
|
||||
try Task.checkCancellation()
|
||||
|
@ -82,7 +79,6 @@ extension UTXOFetcherImpl: UTXOFetcher {
|
|||
|
||||
counter += 1
|
||||
await didFetch(counter / all)
|
||||
try await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight)
|
||||
} catch {
|
||||
logger.error("failed to put utxo - error: \(error)")
|
||||
skipped.append(utxo)
|
||||
|
@ -91,22 +87,20 @@ extension UTXOFetcherImpl: UTXOFetcher {
|
|||
|
||||
metrics.pushProgressReport(
|
||||
progress: BlockProgress(
|
||||
startHeight: range.lowerBound,
|
||||
targetHeight: range.upperBound,
|
||||
progressHeight: range.upperBound
|
||||
startHeight: 0,
|
||||
targetHeight: 1,
|
||||
progressHeight: 1
|
||||
),
|
||||
start: startTime,
|
||||
end: Date(),
|
||||
batchSize: range.count,
|
||||
batchSize: 1,
|
||||
operation: .fetchUTXOs
|
||||
)
|
||||
|
||||
let result = (inserted: refreshed, skipped: skipped)
|
||||
|
||||
try await internalSyncProgress.set(range.upperBound, .latestUTXOFetchedHeight)
|
||||
|
||||
if Task.isCancelled {
|
||||
logger.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
|
||||
logger.debug("Warning: fetchUnspentTxOutputs cancelled")
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
//
|
||||
// InternalSyncProgress.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 23.11.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SyncRanges: Equatable {
|
||||
let latestBlockHeight: BlockHeight
|
||||
// Range of blocks that are not yet downloaded
|
||||
let downloadRange: CompactBlockRange?
|
||||
/// Range of blocks that are not yet scanned.
|
||||
let scanRange: CompactBlockRange?
|
||||
/// Range of blocks that are not enhanced yet.
|
||||
let enhanceRange: CompactBlockRange?
|
||||
/// Range of blocks for which no UTXOs are fetched yet.
|
||||
let fetchUTXORange: CompactBlockRange?
|
||||
|
||||
let latestScannedHeight: BlockHeight?
|
||||
let latestDownloadedBlockHeight: BlockHeight?
|
||||
|
||||
static var empty: SyncRanges {
|
||||
SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: nil,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum NextState: Equatable {
|
||||
case finishProcessing(height: BlockHeight)
|
||||
case processNewBlocks(ranges: SyncRanges)
|
||||
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
|
||||
}
|
||||
|
||||
protocol InternalSyncProgressStorage {
|
||||
func initialize() async throws
|
||||
func bool(for key: String) async throws -> Bool
|
||||
func integer(for key: String) async throws -> Int
|
||||
func set(_ value: Int, for key: String) async throws
|
||||
// sourcery: mockedName="setBool"
|
||||
func set(_ value: Bool, for key: String) async throws
|
||||
}
|
||||
|
||||
actor InternalSyncProgress {
|
||||
enum Key: String, CaseIterable {
|
||||
case latestDownloadedBlockHeight
|
||||
case latestEnhancedHeight
|
||||
case latestUTXOFetchedHeight
|
||||
|
||||
func with(_ alias: ZcashSynchronizerAlias) -> String {
|
||||
switch alias {
|
||||
case .`default`:
|
||||
return self.rawValue
|
||||
case let .custom(rawAlias):
|
||||
return "\(self.rawValue)_\(rawAlias)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let alias: ZcashSynchronizerAlias
|
||||
private let storage: InternalSyncProgressStorage
|
||||
let logger: Logger
|
||||
|
||||
var latestDownloadedBlockHeight: BlockHeight {
|
||||
get async throws { try await load(.latestDownloadedBlockHeight) }
|
||||
}
|
||||
var latestEnhancedHeight: BlockHeight {
|
||||
get async throws { try await load(.latestEnhancedHeight) }
|
||||
}
|
||||
var latestUTXOFetchedHeight: BlockHeight {
|
||||
get async throws { try await load(.latestUTXOFetchedHeight) }
|
||||
}
|
||||
|
||||
init(alias: ZcashSynchronizerAlias, storage: InternalSyncProgressStorage, logger: Logger) {
|
||||
self.alias = alias
|
||||
self.storage = storage
|
||||
self.logger = logger
|
||||
}
|
||||
|
||||
func initialize() async throws {
|
||||
try await storage.initialize()
|
||||
}
|
||||
|
||||
func load(_ key: Key) async throws -> BlockHeight {
|
||||
return try await storage.integer(for: key.with(alias))
|
||||
}
|
||||
|
||||
func set(_ value: BlockHeight, _ key: Key) async throws {
|
||||
try await storage.set(value, for: key.with(alias))
|
||||
}
|
||||
|
||||
func rewind(to: BlockHeight) async throws {
|
||||
for key in Key.allCases {
|
||||
let finalRewindHeight = min(try await load(key), to)
|
||||
try await self.set(finalRewindHeight, key)
|
||||
}
|
||||
}
|
||||
|
||||
/// `InternalSyncProgress` is from now on used to track which block were already downloaded. Previous versions of the SDK were using cache DB to
|
||||
/// track this. Because of this we have to migrate height of latest downloaded block from cache DB to here.
|
||||
///
|
||||
/// - Parameter latestDownloadedBlockHeight: Height of latest downloaded block from cache DB.
|
||||
func migrateIfNeeded(
|
||||
latestDownloadedBlockHeightFromCacheDB latestDownloadedBlockHeight: BlockHeight,
|
||||
alias: ZcashSynchronizerAlias
|
||||
) async throws {
|
||||
// If no latest downloaded height is stored in storage store there latest downloaded height from blocks storage. If there are no blocks
|
||||
// downloaded then it will be 0 anyway. If there are blocks downloaded real height is stored.
|
||||
if try await storage.integer(for: Key.latestDownloadedBlockHeight.with(alias)) == 0 {
|
||||
try await set(latestDownloadedBlockHeight, .latestDownloadedBlockHeight)
|
||||
}
|
||||
|
||||
for key in Key.allCases {
|
||||
let finalKey = key.with(alias)
|
||||
let value = UserDefaults.standard.integer(forKey: finalKey)
|
||||
if value > 0 {
|
||||
try await storage.set(value, for: finalKey)
|
||||
}
|
||||
UserDefaults.standard.set(0, forKey: finalKey)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the next state for the sync process. Thanks to this it's possible to interrupt the sync process at any phase and then it can be safely
|
||||
/// resumed.
|
||||
///
|
||||
/// The sync process has 4 phases (download, scan, enhance, fetch UTXO). `InternalSyncProgress` tracks independently which blocks were already
|
||||
/// processed in each phase. To compute the next state these 4 numbers are compared with `latestBlockHeight`.
|
||||
///
|
||||
/// - If any of these numbers are larger than `latestBlockHeight` then `wait` is used as the next state. We have locally higher block heights than
|
||||
/// are currently available at LightWalletd.
|
||||
/// - If any of these numbers are lower than `latestBlockHeight` then `processNewBlocks` is used as the next state. The sync process should run.
|
||||
/// - Otherwise `finishProcessing` is used as the next state. It means that local data are synced with what is available at LightWalletd.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - latestBlockHeight: Latest height fetched from LightWalletd API.
|
||||
/// - latestScannedHeight: Latest height of latest block scanned.
|
||||
/// - walletBirthday: Wallet birthday.
|
||||
/// - Returns: Computed state.
|
||||
func computeNextState(
|
||||
latestBlockHeight: BlockHeight,
|
||||
latestScannedHeight: BlockHeight,
|
||||
walletBirthday: BlockHeight
|
||||
) async throws -> NextState {
|
||||
let latestDownloadedBlockHeight = try await self.latestDownloadedBlockHeight
|
||||
let latestEnhancedHeight = try await self.latestEnhancedHeight
|
||||
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
|
||||
logger.debug("""
|
||||
Init numbers:
|
||||
latestBlockHeight: \(latestBlockHeight)
|
||||
latestDownloadedHeight: \(latestDownloadedBlockHeight)
|
||||
latestScannedHeight: \(latestScannedHeight)
|
||||
latestEnhancedHeight: \(latestEnhancedHeight)
|
||||
latestUTXOFetchedHeight: \(latestUTXOFetchedHeight)
|
||||
""")
|
||||
|
||||
if latestDownloadedBlockHeight > latestBlockHeight ||
|
||||
latestScannedHeight > latestBlockHeight ||
|
||||
latestEnhancedHeight > latestBlockHeight ||
|
||||
latestUTXOFetchedHeight > latestBlockHeight {
|
||||
return .wait(latestHeight: latestBlockHeight, latestDownloadHeight: latestDownloadedBlockHeight)
|
||||
} else if latestDownloadedBlockHeight < latestBlockHeight ||
|
||||
latestScannedHeight < latestBlockHeight ||
|
||||
latestEnhancedHeight < latestEnhancedHeight ||
|
||||
latestUTXOFetchedHeight < latestBlockHeight {
|
||||
let ranges = try await computeSyncRanges(
|
||||
birthday: walletBirthday,
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
latestScannedHeight: latestScannedHeight
|
||||
)
|
||||
return .processNewBlocks(ranges: ranges)
|
||||
} else {
|
||||
return .finishProcessing(height: latestBlockHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func computeSyncRanges(
|
||||
birthday: BlockHeight,
|
||||
latestBlockHeight: BlockHeight,
|
||||
latestScannedHeight: BlockHeight
|
||||
) async throws -> SyncRanges {
|
||||
let latestDownloadedBlockHeight = try await self.latestDownloadedBlockHeight
|
||||
let latestEnhancedHeight = try await self.latestEnhancedHeight
|
||||
let latestUTXOFetchedHeight = try await self.latestUTXOFetchedHeight
|
||||
|
||||
if latestScannedHeight > latestDownloadedBlockHeight {
|
||||
logger.warn("""
|
||||
InternalSyncProgress found inconsistent state.
|
||||
latestBlockHeight: \(latestBlockHeight)
|
||||
--> latestDownloadedHeight: \(latestDownloadedBlockHeight)
|
||||
latestScannedHeight: \(latestScannedHeight)
|
||||
latestEnhancedHeight: \(latestEnhancedHeight)
|
||||
latestUTXOFetchedHeight: \(latestUTXOFetchedHeight)
|
||||
""")
|
||||
}
|
||||
|
||||
let downloadRange = computeRange(latestHeight: latestDownloadedBlockHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
|
||||
let scanRange = computeRange(latestHeight: latestScannedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight)
|
||||
|
||||
return SyncRanges(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadRange: downloadRange,
|
||||
scanRange: scanRange,
|
||||
enhanceRange: computeRange(latestHeight: latestEnhancedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
|
||||
fetchUTXORange: computeRange(latestHeight: latestUTXOFetchedHeight, birthday: birthday, latestBlockHeight: latestBlockHeight),
|
||||
latestScannedHeight: latestScannedHeight,
|
||||
latestDownloadedBlockHeight: latestDownloadedBlockHeight
|
||||
)
|
||||
}
|
||||
|
||||
private func computeRange(latestHeight: BlockHeight, birthday: BlockHeight, latestBlockHeight: BlockHeight) -> CompactBlockRange? {
|
||||
guard latestHeight < latestBlockHeight else { return nil }
|
||||
let lowerBound = latestHeight <= birthday ? birthday : latestHeight + 1
|
||||
return lowerBound...latestBlockHeight
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
//
|
||||
// InternalSyncProgressDiskStorage.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 21.05.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
actor InternalSyncProgressDiskStorage {
|
||||
private let storageURL: URL
|
||||
private let logger: Logger
|
||||
init(storageURL: URL, logger: Logger) {
|
||||
self.storageURL = storageURL
|
||||
self.logger = logger
|
||||
}
|
||||
|
||||
private func fileURL(for key: String) -> URL {
|
||||
return storageURL.appendingPathComponent(key)
|
||||
}
|
||||
}
|
||||
|
||||
extension InternalSyncProgressDiskStorage: InternalSyncProgressStorage {
|
||||
/// - If object on the file system at `generalStorageURL` exists and it is directory then do nothing.
|
||||
/// - If object on the file system at `generalStorageURL` exists and it is file then throw error. Because `generalStorageURL` should be directory.
|
||||
/// - If object on the file system at `generalStorageURL` doesn't exists then create directory at this URL. And set `isExcludedFromBackup` URL
|
||||
/// flag to prevent backup of the progress information to system backup.
|
||||
func initialize() async throws {
|
||||
let fileManager = FileManager.default
|
||||
var isDirectory: ObjCBool = false
|
||||
let exists = fileManager.fileExists(atPath: storageURL.pathExtension, isDirectory: &isDirectory)
|
||||
|
||||
if exists && !isDirectory.boolValue {
|
||||
throw ZcashError.initializerGeneralStorageExistsButIsFile(storageURL)
|
||||
} else if !exists {
|
||||
do {
|
||||
try fileManager.createDirectory(at: storageURL, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
throw ZcashError.initializerGeneralStorageCantCreate(storageURL, error)
|
||||
}
|
||||
|
||||
do {
|
||||
// Prevent from backing up progress information to system backup.
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
var generalStorageURL = storageURL
|
||||
try generalStorageURL.setResourceValues(resourceValues)
|
||||
} catch {
|
||||
throw ZcashError.initializerCantSetNoBackupFlagToGeneralStorageURL(storageURL, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bool(for key: String) async throws -> Bool {
|
||||
let fileURL = self.fileURL(for: key)
|
||||
do {
|
||||
return try Data(contentsOf: fileURL).toBool()
|
||||
} catch {
|
||||
if !FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
return false
|
||||
} else {
|
||||
throw ZcashError.ispStorageCantLoad(fileURL, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func integer(for key: String) async throws -> Int {
|
||||
let fileURL = self.fileURL(for: key)
|
||||
do {
|
||||
return try Data(contentsOf: fileURL).toInt()
|
||||
} catch {
|
||||
if !FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
return 0
|
||||
} else {
|
||||
throw ZcashError.ispStorageCantLoad(fileURL, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ value: Int, for key: String) async throws {
|
||||
let fileURL = self.fileURL(for: key)
|
||||
do {
|
||||
try value.toData().write(to: fileURL, options: [.atomic])
|
||||
} catch {
|
||||
throw ZcashError.ispStorageCantWrite(fileURL, error)
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ value: Bool, for key: String) async throws {
|
||||
let fileURL = self.fileURL(for: key)
|
||||
do {
|
||||
try value.toData().write(to: fileURL, options: [.atomic])
|
||||
} catch {
|
||||
throw ZcashError.ispStorageCantWrite(fileURL, error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// SyncControlData.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 23.11.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SyncControlData: Equatable {
|
||||
/// The tip of the blockchain
|
||||
let latestBlockHeight: BlockHeight
|
||||
/// The last height that has been scanned
|
||||
let latestScannedHeight: BlockHeight?
|
||||
/// The height from the enhancement must start
|
||||
let firstUnenhancedHeight: BlockHeight?
|
||||
|
||||
static var empty: SyncControlData {
|
||||
SyncControlData(
|
||||
latestBlockHeight: 0,
|
||||
latestScannedHeight: nil,
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
}
|
||||
}
|
|
@ -34,15 +34,74 @@ struct Block: Codable {
|
|||
let hash: Data
|
||||
let time: Int
|
||||
let saplingTree: Data
|
||||
|
||||
|
||||
static let table = Table("blocks")
|
||||
}
|
||||
|
||||
struct VTransaction: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case accountId = "account_id"
|
||||
case idTx = "id_tx"
|
||||
case minedHeight = "mined_height"
|
||||
case txIndex = "tx_index"
|
||||
case txId = "txid"
|
||||
case expiryHeight = "expiry_height"
|
||||
case raw = "raw"
|
||||
case accountBalanceDelta = "account_balance_delta"
|
||||
case feePaid = "fee_paid"
|
||||
case expiredUnmined = "expired_unmined"
|
||||
case hasChange = "has_change"
|
||||
case sentNoteCount = "sent_note_count"
|
||||
case recievedNoteCount = "received_note_count"
|
||||
case memoCount = "memo_count"
|
||||
case blockTime = "block_time"
|
||||
}
|
||||
|
||||
enum TableStructure {
|
||||
static let accountId = Expression<Int>(VTransaction.CodingKeys.accountId.rawValue)
|
||||
static let idTx = Expression<Int>(VTransaction.CodingKeys.idTx.rawValue)
|
||||
static let minedHeight = Expression<Int>(VTransaction.CodingKeys.minedHeight.rawValue)
|
||||
static let txIndex = Expression<Int>(VTransaction.CodingKeys.txIndex.rawValue)
|
||||
static let txId = Expression<Data>(VTransaction.CodingKeys.txId.rawValue)
|
||||
static let expiryHeight = Expression<Int?>(VTransaction.CodingKeys.expiryHeight.rawValue)
|
||||
static let raw = Expression<Data?>(VTransaction.CodingKeys.raw.rawValue)
|
||||
static let accountBalanceDelta = Expression<Int>(VTransaction.CodingKeys.accountBalanceDelta.rawValue)
|
||||
static let feePaid = Expression<Int?>(VTransaction.CodingKeys.feePaid.rawValue)
|
||||
static let expiredUnmined = Expression<Int>(VTransaction.CodingKeys.expiredUnmined.rawValue)
|
||||
static let hasChange = Expression<Bool>(VTransaction.CodingKeys.hasChange.rawValue)
|
||||
static let sentNoteCount = Expression<Int>(VTransaction.CodingKeys.sentNoteCount.rawValue)
|
||||
static let recievedNoteCount = Expression<Int>(VTransaction.CodingKeys.recievedNoteCount.rawValue)
|
||||
static let memoCount = Expression<Int>(VTransaction.CodingKeys.memoCount.rawValue)
|
||||
static let blockTime = Expression<Int>(VTransaction.CodingKeys.blockTime.rawValue)
|
||||
}
|
||||
|
||||
let accountId: Int
|
||||
let idTx: Int
|
||||
let minedHeight: Int
|
||||
let txIndex: Int
|
||||
let txId: Data
|
||||
let expiryHeight: Int?
|
||||
let raw: Data?
|
||||
let accountBalanceDelta: Int
|
||||
let feePaid: Int?
|
||||
let expiredUnmined: Int
|
||||
let hasChange: Bool
|
||||
let sentNoteCount: Int
|
||||
let recievedNoteCount: Int
|
||||
let memoCount: Int
|
||||
let blockTime: Int
|
||||
|
||||
static let table = Table("v_transactions")
|
||||
}
|
||||
|
||||
class BlockSQLDAO: BlockDao {
|
||||
let dbProvider: ConnectionProvider
|
||||
let table: Table
|
||||
let height = Expression<Int>("height")
|
||||
|
||||
let minedHeight = Expression<Int>("mined_height")
|
||||
let raw = Expression<Data?>("raw")
|
||||
|
||||
init(dbProvider: ConnectionProvider) {
|
||||
self.dbProvider = dbProvider
|
||||
self.table = Table("Blocks")
|
||||
|
@ -103,6 +162,30 @@ class BlockSQLDAO: BlockDao {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func firstUnenhancedHeight(in range: CompactBlockRange? = nil) throws -> BlockHeight? {
|
||||
do {
|
||||
return try dbProvider
|
||||
.connection()
|
||||
.prepare(
|
||||
VTransaction.table
|
||||
.order(minedHeight.asc)
|
||||
.filter(raw == nil)
|
||||
.limit(1)
|
||||
)
|
||||
.map {
|
||||
do {
|
||||
let vTransaction: VTransaction = try $0.decode()
|
||||
return vTransaction.minedHeight
|
||||
} catch {
|
||||
throw ZcashError.blockDAOFirstUnenhancedCantDecode(error)
|
||||
}
|
||||
}
|
||||
.first
|
||||
} catch {
|
||||
throw ZcashError.blockDAOFirstUnenhancedHeight(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockSQLDAO: BlockRepository {
|
||||
|
|
|
@ -49,6 +49,10 @@ class TransactionSQLDAO: TransactionRepository {
|
|||
try blockDao.latestBlock()
|
||||
}
|
||||
|
||||
func firstUnenhancedHeight() throws -> BlockHeight? {
|
||||
try blockDao.firstUnenhancedHeight()
|
||||
}
|
||||
|
||||
func isInitialized() async throws -> Bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -94,10 +94,18 @@ public enum ZcashError: Equatable, Error {
|
|||
/// - `sqliteError` is error produced by SQLite library.
|
||||
/// ZBDAO0004
|
||||
case blockDAOLatestBlock(_ sqliteError: Error)
|
||||
/// Fetched latesxt block information from DB but can't decode them.
|
||||
/// Fetched latest block information from DB but can't decode them.
|
||||
/// - `error` is decoding error.
|
||||
/// ZBDAO0005
|
||||
case blockDAOLatestBlockCantDecode(_ error: Error)
|
||||
/// SQLite query failed when fetching the first unenhanced block from the database.
|
||||
/// - `sqliteError` is error produced by SQLite library.
|
||||
/// ZBDAO0006
|
||||
case blockDAOFirstUnenhancedHeight(_ sqliteError: Error)
|
||||
/// Fetched unenhanced block information from DB but can't decode them.
|
||||
/// - `error` is decoding error.
|
||||
/// ZBDAO0007
|
||||
case blockDAOFirstUnenhancedCantDecode(_ error: Error)
|
||||
/// Error from rust layer when calling ZcashRustBackend.createAccount
|
||||
/// - `rustError` contains error generated by the rust layer.
|
||||
/// ZRUST0001
|
||||
|
@ -501,6 +509,9 @@ public enum ZcashError: Equatable, Error {
|
|||
/// Consensus BranchIDs don't match this is probably an API or programming error.
|
||||
/// ZCBPEO0017
|
||||
case compactBlockProcessorConsensusBranchID
|
||||
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
|
||||
/// ZCBPEO0018
|
||||
case compactBlockProcessorDownloadBlockActionRewind
|
||||
/// The synchronizer is unprepared.
|
||||
/// ZSYNCO0001
|
||||
case synchronizerNotPrepared
|
||||
|
@ -519,12 +530,6 @@ public enum ZcashError: Equatable, Error {
|
|||
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
|
||||
/// ZSYNCO0006
|
||||
case synchronizerDisconnected
|
||||
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
|
||||
/// ZISPDS0001
|
||||
case ispStorageCantLoad(_ fileURL: URL, _ error: Error)
|
||||
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
|
||||
/// ZISPDS0002
|
||||
case ispStorageCantWrite(_ fileURL: URL, _ error: Error)
|
||||
|
||||
public var message: String {
|
||||
switch self {
|
||||
|
@ -552,7 +557,9 @@ public enum ZcashError: Equatable, Error {
|
|||
case .blockDAOCantDecode: return "Fetched block information from DB but can't decode them."
|
||||
case .blockDAOLatestBlockHeight: return "SQLite query failed when fetching height of the latest block from the database."
|
||||
case .blockDAOLatestBlock: return "SQLite query failed when fetching the latest block from the database."
|
||||
case .blockDAOLatestBlockCantDecode: return "Fetched latesxt block information from DB but can't decode them."
|
||||
case .blockDAOLatestBlockCantDecode: return "Fetched latest block information from DB but can't decode them."
|
||||
case .blockDAOFirstUnenhancedHeight: return "SQLite query failed when fetching the first unenhanced block from the database."
|
||||
case .blockDAOFirstUnenhancedCantDecode: return "Fetched unenhanced block information from DB but can't decode them."
|
||||
case .rustCreateAccount: return "Error from rust layer when calling ZcashRustBackend.createAccount"
|
||||
case .rustCreateToAddress: return "Error from rust layer when calling ZcashRustBackend.createToAddress"
|
||||
case .rustDecryptAndStoreTransaction: return "Error from rust layer when calling ZcashRustBackend.decryptAndStoreTransaction"
|
||||
|
@ -672,14 +679,13 @@ public enum ZcashError: Equatable, Error {
|
|||
case .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb: return "Deletion of readable file at the provided URL failed."
|
||||
case .compactBlockProcessorChainName: return "Chain name does not match. Expected either 'test' or 'main'. This is probably an API or programming error."
|
||||
case .compactBlockProcessorConsensusBranchID: return "Consensus BranchIDs don't match this is probably an API or programming error."
|
||||
case .compactBlockProcessorDownloadBlockActionRewind: return "Rewind of DownloadBlockAction failed as no action is possible to unwrapp."
|
||||
case .synchronizerNotPrepared: return "The synchronizer is unprepared."
|
||||
case .synchronizerSendMemoToTransparentAddress: return "Memos can't be sent to transparent addresses."
|
||||
case .synchronizerShieldFundsInsuficientTransparentFunds: return "There is not enough transparent funds to cover fee for the shielding."
|
||||
case .synchronizerLatestUTXOsInvalidTAddress: return "LatestUTXOs for the address failed, invalid t-address."
|
||||
case .synchronizerRewindUnknownArchorHeight: return "Rewind failed, unknown archor height"
|
||||
case .synchronizerDisconnected: return "Indicates that this Synchronizer is disconnected from its lightwalletd server."
|
||||
case .ispStorageCantLoad: return "`InternalSyncProgressDiskStorage` can't read data from specific file."
|
||||
case .ispStorageCantWrite: return "`InternalSyncProgressDiskStorage` can't write data from specific file."
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -710,6 +716,8 @@ public enum ZcashError: Equatable, Error {
|
|||
case .blockDAOLatestBlockHeight: return .blockDAOLatestBlockHeight
|
||||
case .blockDAOLatestBlock: return .blockDAOLatestBlock
|
||||
case .blockDAOLatestBlockCantDecode: return .blockDAOLatestBlockCantDecode
|
||||
case .blockDAOFirstUnenhancedHeight: return .blockDAOFirstUnenhancedHeight
|
||||
case .blockDAOFirstUnenhancedCantDecode: return .blockDAOFirstUnenhancedCantDecode
|
||||
case .rustCreateAccount: return .rustCreateAccount
|
||||
case .rustCreateToAddress: return .rustCreateToAddress
|
||||
case .rustDecryptAndStoreTransaction: return .rustDecryptAndStoreTransaction
|
||||
|
@ -829,14 +837,13 @@ public enum ZcashError: Equatable, Error {
|
|||
case .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb: return .compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb
|
||||
case .compactBlockProcessorChainName: return .compactBlockProcessorChainName
|
||||
case .compactBlockProcessorConsensusBranchID: return .compactBlockProcessorConsensusBranchID
|
||||
case .compactBlockProcessorDownloadBlockActionRewind: return .compactBlockProcessorDownloadBlockActionRewind
|
||||
case .synchronizerNotPrepared: return .synchronizerNotPrepared
|
||||
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
|
||||
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds
|
||||
case .synchronizerLatestUTXOsInvalidTAddress: return .synchronizerLatestUTXOsInvalidTAddress
|
||||
case .synchronizerRewindUnknownArchorHeight: return .synchronizerRewindUnknownArchorHeight
|
||||
case .synchronizerDisconnected: return .synchronizerDisconnected
|
||||
case .ispStorageCantLoad: return .ispStorageCantLoad
|
||||
case .ispStorageCantWrite: return .ispStorageCantWrite
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,8 +57,12 @@ public enum ZcashErrorCode: String {
|
|||
case blockDAOLatestBlockHeight = "ZBDAO0003"
|
||||
/// SQLite query failed when fetching the latest block from the database.
|
||||
case blockDAOLatestBlock = "ZBDAO0004"
|
||||
/// Fetched latesxt block information from DB but can't decode them.
|
||||
/// Fetched latest block information from DB but can't decode them.
|
||||
case blockDAOLatestBlockCantDecode = "ZBDAO0005"
|
||||
/// SQLite query failed when fetching the first unenhanced block from the database.
|
||||
case blockDAOFirstUnenhancedHeight = "ZBDAO0006"
|
||||
/// Fetched unenhanced block information from DB but can't decode them.
|
||||
case blockDAOFirstUnenhancedCantDecode = "ZBDAO0007"
|
||||
/// Error from rust layer when calling ZcashRustBackend.createAccount
|
||||
case rustCreateAccount = "ZRUST0001"
|
||||
/// Error from rust layer when calling ZcashRustBackend.createToAddress
|
||||
|
@ -297,6 +301,8 @@ public enum ZcashErrorCode: String {
|
|||
case compactBlockProcessorChainName = "ZCBPEO0016"
|
||||
/// Consensus BranchIDs don't match this is probably an API or programming error.
|
||||
case compactBlockProcessorConsensusBranchID = "ZCBPEO0017"
|
||||
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
|
||||
case compactBlockProcessorDownloadBlockActionRewind = "ZCBPEO0018"
|
||||
/// The synchronizer is unprepared.
|
||||
case synchronizerNotPrepared = "ZSYNCO0001"
|
||||
/// Memos can't be sent to transparent addresses.
|
||||
|
@ -309,8 +315,4 @@ public enum ZcashErrorCode: String {
|
|||
case synchronizerRewindUnknownArchorHeight = "ZSYNCO0005"
|
||||
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
|
||||
case synchronizerDisconnected = "ZSYNCO0006"
|
||||
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
|
||||
case ispStorageCantLoad = "ZISPDS0001"
|
||||
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
|
||||
case ispStorageCantWrite = "ZISPDS0002"
|
||||
}
|
||||
|
|
|
@ -122,10 +122,18 @@ enum ZcashErrorDefinition {
|
|||
/// - `sqliteError` is error produced by SQLite library.
|
||||
// sourcery: code="ZBDAO0004"
|
||||
case blockDAOLatestBlock(_ sqliteError: Error)
|
||||
/// Fetched latesxt block information from DB but can't decode them.
|
||||
/// Fetched latest block information from DB but can't decode them.
|
||||
/// - `error` is decoding error.
|
||||
// sourcery: code="ZBDAO0005"
|
||||
case blockDAOLatestBlockCantDecode(_ error: Error)
|
||||
/// SQLite query failed when fetching the first unenhanced block from the database.
|
||||
/// - `sqliteError` is error produced by SQLite library.
|
||||
// sourcery: code="ZBDAO0006"
|
||||
case blockDAOFirstUnenhancedHeight(_ sqliteError: Error)
|
||||
/// Fetched unenhanced block information from DB but can't decode them.
|
||||
/// - `error` is decoding error.
|
||||
// sourcery: code="ZBDAO0007"
|
||||
case blockDAOFirstUnenhancedCantDecode(_ error: Error)
|
||||
|
||||
// MARK: - Rust
|
||||
|
||||
|
@ -581,7 +589,10 @@ enum ZcashErrorDefinition {
|
|||
/// Consensus BranchIDs don't match this is probably an API or programming error.
|
||||
// sourcery: code="ZCBPEO0017"
|
||||
case compactBlockProcessorConsensusBranchID
|
||||
|
||||
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
|
||||
// sourcery: code="ZCBPEO0018"
|
||||
case compactBlockProcessorDownloadBlockActionRewind
|
||||
|
||||
// MARK: - SDKSynchronizer
|
||||
|
||||
/// The synchronizer is unprepared.
|
||||
|
@ -602,13 +613,4 @@ enum ZcashErrorDefinition {
|
|||
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
|
||||
// sourcery: code="ZSYNCO0006"
|
||||
case synchronizerDisconnected
|
||||
|
||||
// MARK: - InternalSyncProgressDiskStorage
|
||||
|
||||
/// `InternalSyncProgressDiskStorage` can't read data from specific file.
|
||||
// sourcery: code="ZISPDS0001"
|
||||
case ispStorageCantLoad(_ fileURL: URL, _ error: Error)
|
||||
/// `InternalSyncProgressDiskStorage` can't write data from specific file.
|
||||
// sourcery: code="ZISPDS0002"
|
||||
case ispStorageCantWrite(_ fileURL: URL, _ error: Error)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ protocol LatestBlocksDataProvider {
|
|||
var latestScannedTime: TimeInterval { get async }
|
||||
var latestBlockHeight: BlockHeight { get async }
|
||||
var walletBirthday: BlockHeight { get async }
|
||||
var firstUnenhancedHeight: BlockHeight? { get async }
|
||||
|
||||
func updateScannedData() async
|
||||
func updateBlockData() async
|
||||
|
@ -27,6 +28,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
|
|||
// Valid values are stored here after Synchronizer's `prepare` is called.
|
||||
private(set) var latestScannedHeight: BlockHeight = .zero
|
||||
private(set) var latestScannedTime: TimeInterval = 0.0
|
||||
private(set) var firstUnenhancedHeight: BlockHeight?
|
||||
// Valid value is stored here after block processor's `nextState` is called.
|
||||
private(set) var latestBlockHeight: BlockHeight = .zero
|
||||
// Valid values are stored here after Synchronizer's `prepare` is called.
|
||||
|
@ -54,7 +56,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
|
|||
latestBlockHeight = newLatestBlockHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateWalletBirthday(_ walletBirthday: BlockHeight) async {
|
||||
self.walletBirthday = walletBirthday
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ protocol TransactionRepository {
|
|||
func blockForHeight(_ height: BlockHeight) async throws -> Block?
|
||||
func lastScannedHeight() async throws -> BlockHeight
|
||||
func lastScannedBlock() async throws -> Block?
|
||||
func firstUnenhancedHeight() throws -> BlockHeight?
|
||||
func isInitialized() async throws -> Bool
|
||||
func find(id: Int) async throws -> ZcashTransaction.Overview
|
||||
func find(rawID: Data) async throws -> ZcashTransaction.Overview
|
||||
|
|
|
@ -88,12 +88,6 @@ enum Dependencies {
|
|||
container.register(type: SyncSessionIDGenerator.self, isSingleton: false) { _ in
|
||||
UniqueSyncSessionIDGenerator()
|
||||
}
|
||||
|
||||
container.register(type: InternalSyncProgress.self, isSingleton: true) { di in
|
||||
let logger = di.resolve(Logger.self)
|
||||
let storage = InternalSyncProgressDiskStorage(storageURL: urls.generalStorageURL, logger: logger)
|
||||
return InternalSyncProgress(alias: alias, storage: storage, logger: logger)
|
||||
}
|
||||
|
||||
container.register(type: ZcashFileManager.self, isSingleton: true) { _ in
|
||||
FileManager.default
|
||||
|
@ -109,7 +103,6 @@ enum Dependencies {
|
|||
let service = di.resolve(LightWalletService.self)
|
||||
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
|
||||
let storage = di.resolve(CompactBlockRepository.self)
|
||||
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
|
||||
let metrics = di.resolve(SDKMetrics.self)
|
||||
let logger = di.resolve(Logger.self)
|
||||
|
||||
|
@ -117,7 +110,6 @@ enum Dependencies {
|
|||
service: service,
|
||||
downloaderService: blockDownloaderService,
|
||||
storage: storage,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
metrics: metrics,
|
||||
logger: logger
|
||||
)
|
||||
|
@ -159,7 +151,6 @@ enum Dependencies {
|
|||
|
||||
container.register(type: BlockEnhancer.self, isSingleton: true) { di in
|
||||
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
|
||||
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
|
||||
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
||||
let transactionRepository = di.resolve(TransactionRepository.self)
|
||||
let metrics = di.resolve(SDKMetrics.self)
|
||||
|
@ -167,7 +158,6 @@ enum Dependencies {
|
|||
|
||||
return BlockEnhancerImpl(
|
||||
blockDownloaderService: blockDownloaderService,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
rustBackend: rustBackend,
|
||||
transactionRepository: transactionRepository,
|
||||
metrics: metrics,
|
||||
|
@ -178,7 +168,6 @@ enum Dependencies {
|
|||
container.register(type: UTXOFetcher.self, isSingleton: true) { di in
|
||||
let blockDownloaderService = di.resolve(BlockDownloaderService.self)
|
||||
let utxoFetcherConfig = UTXOFetcherConfig(walletBirthdayProvider: config.walletBirthdayProvider)
|
||||
let internalSyncProgress = di.resolve(InternalSyncProgress.self)
|
||||
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
||||
let metrics = di.resolve(SDKMetrics.self)
|
||||
let logger = di.resolve(Logger.self)
|
||||
|
@ -187,7 +176,6 @@ enum Dependencies {
|
|||
accountRepository: accountRepository,
|
||||
blockDownloaderService: blockDownloaderService,
|
||||
config: utxoFetcherConfig,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
rustBackend: rustBackend,
|
||||
metrics: metrics,
|
||||
logger: logger
|
||||
|
|
|
@ -40,7 +40,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
private let transactionEncoder: TransactionEncoder
|
||||
private let transactionRepository: TransactionRepository
|
||||
private let utxoRepository: UnspentTransactionOutputRepository
|
||||
let internalSyncProgress: InternalSyncProgress
|
||||
|
||||
private let syncSessionIDGenerator: SyncSessionIDGenerator
|
||||
private let syncSession: SyncSession
|
||||
|
@ -88,7 +87,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
self.syncSession = SyncSession(.nullID)
|
||||
self.syncSessionTicker = syncSessionTicker
|
||||
self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self)
|
||||
internalSyncProgress = initializer.container.resolve(InternalSyncProgress.self)
|
||||
|
||||
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
|
||||
self?.connectivityStateChanged(oldState: oldState, newState: newState)
|
||||
|
@ -139,7 +137,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
}
|
||||
|
||||
try await utxoRepository.initialise()
|
||||
try await internalSyncProgress.initialize()
|
||||
|
||||
if case .seedRequired = try await self.initializer.initialize(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday) {
|
||||
return .seedRequired
|
||||
|
|
|
@ -231,16 +231,6 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist")
|
||||
XCTAssertEqual(try fm.contentsOfDirectory(atPath: storage.blocksDirectory.path), [], "FS Cache directory should be empty")
|
||||
|
||||
let internalSyncProgress = coordinator.synchronizer.internalSyncProgress
|
||||
|
||||
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
|
||||
let latestEnhancedHeight = try await internalSyncProgress.load(.latestEnhancedHeight)
|
||||
let latestUTXOFetchedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
|
||||
|
||||
XCTAssertEqual(latestDownloadedBlockHeight, 0, "internalSyncProgress latestDownloadedBlockHeight should be 0")
|
||||
XCTAssertEqual(latestEnhancedHeight, 0, "internalSyncProgress latestEnhancedHeight should be 0")
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 0, "internalSyncProgress latestUTXOFetchedHeight should be 0")
|
||||
|
||||
let status = await coordinator.synchronizer.status
|
||||
XCTAssertEqual(status, .unprepared, "SDKSynchronizer state should be unprepared")
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
var endpoint: LightWalletEndpoint!
|
||||
var service: LightWalletService!
|
||||
var storage: FSCompactBlockRepository!
|
||||
var internalSyncProgress: InternalSyncProgress!
|
||||
var processorConfig: CompactBlockProcessor.Configuration!
|
||||
var latestBlockHeight: BlockHeight!
|
||||
var startHeight: BlockHeight!
|
||||
|
@ -65,7 +64,6 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
endpoint = nil
|
||||
service = nil
|
||||
storage = nil
|
||||
internalSyncProgress = nil
|
||||
processorConfig = nil
|
||||
}
|
||||
|
||||
|
@ -92,10 +90,6 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
)
|
||||
try await storage.create()
|
||||
|
||||
let internalSyncProgressStorage = InternalSyncProgressMemoryStorage()
|
||||
try await internalSyncProgressStorage.set(startHeight, for: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue)
|
||||
internalSyncProgress = InternalSyncProgress(alias: .default, storage: internalSyncProgressStorage, logger: logger)
|
||||
|
||||
processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
|
@ -113,7 +107,6 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
service: service,
|
||||
downloaderService: BlockDownloaderServiceImpl(service: service, storage: storage),
|
||||
storage: storage,
|
||||
internalSyncProgress: internalSyncProgress,
|
||||
metrics: SDKMetrics(),
|
||||
logger: logger
|
||||
)
|
||||
|
@ -141,24 +134,21 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
try await makeDependencies(timeout: 10000)
|
||||
|
||||
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
|
||||
let syncRanges = SyncRanges(
|
||||
let blockDownloader = mockContainer.resolve(BlockDownloader.self)
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadRange: startHeight...latestBlockHeight,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: startHeight,
|
||||
latestDownloadedBlockHeight: startHeight
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
let context = ActionContext(state: .download)
|
||||
await context.update(syncRanges: syncRanges)
|
||||
await context.update(syncControlData: syncControlData)
|
||||
|
||||
let expectation = XCTestExpectation()
|
||||
|
||||
let cancelableTask = Task {
|
||||
do {
|
||||
_ = try await action.run(with: context, didUpdate: { _ in })
|
||||
let lastDownloadedHeight = try await internalSyncProgress.latestDownloadedBlockHeight
|
||||
let lastDownloadedHeight = await blockDownloader.latestDownloadedBlockHeight()
|
||||
// Just to be sure that download was interrupted before download was finished.
|
||||
XCTAssertLessThan(lastDownloadedHeight, latestBlockHeight)
|
||||
expectation.fulfill()
|
||||
|
@ -180,17 +170,13 @@ class BlockStreamingTest: ZcashTestCase {
|
|||
try await makeDependencies(timeout: 100)
|
||||
|
||||
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
|
||||
let syncRanges = SyncRanges(
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: latestBlockHeight,
|
||||
downloadRange: startHeight...latestBlockHeight,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: startHeight,
|
||||
latestDownloadedBlockHeight: startHeight
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
let context = ActionContext(state: .download)
|
||||
await context.update(syncRanges: syncRanges)
|
||||
await context.update(syncControlData: syncControlData)
|
||||
|
||||
let date = Date()
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
|
||||
var rustBackend: ZcashRustBackendWelding!
|
||||
var processor: CompactBlockProcessor!
|
||||
var syncStartedExpect: XCTestExpectation!
|
||||
var syncStartedExpectation: XCTestExpectation!
|
||||
var updatedNotificationExpectation: XCTestExpectation!
|
||||
var stopNotificationExpectation: XCTestExpectation!
|
||||
var finishedNotificationExpectation: XCTestExpectation!
|
||||
|
@ -30,10 +30,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
try await super.setUp()
|
||||
logger = OSLogger(logLevel: .debug)
|
||||
|
||||
for key in InternalSyncProgress.Key.allCases {
|
||||
UserDefaults.standard.set(0, forKey: key.with(.default))
|
||||
}
|
||||
|
||||
let pathProvider = DefaultResourceProvider(network: network)
|
||||
processorConfig = CompactBlockProcessor.Configuration(
|
||||
alias: .default,
|
||||
|
@ -109,7 +105,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
return
|
||||
}
|
||||
|
||||
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")
|
||||
syncStartedExpectation = XCTestExpectation(description: "\(self.description) syncStartedExpectation")
|
||||
stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation")
|
||||
updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation")
|
||||
finishedNotificationExpectation = XCTestExpectation(description: "\(self.description) finishedNotificationExpectation")
|
||||
|
@ -147,7 +143,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
XCTAssertNotNil(processor)
|
||||
|
||||
let expectations: [CompactBlockProcessorEventHandler.EventIdentifier: XCTestExpectation] = [
|
||||
.startedSyncing: syncStartedExpect,
|
||||
.startedSyncing: syncStartedExpectation,
|
||||
.stopped: stopNotificationExpectation,
|
||||
.progressUpdated: updatedNotificationExpectation,
|
||||
.finished: finishedNotificationExpectation
|
||||
|
@ -162,7 +158,7 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
|
||||
await fulfillment(
|
||||
of: [
|
||||
syncStartedExpect,
|
||||
syncStartedExpectation,
|
||||
finishedNotificationExpectation
|
||||
],
|
||||
timeout: 30,
|
||||
|
@ -186,108 +182,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
|
|||
(abs(currentHeight - targetHeight) / batchSize)
|
||||
}
|
||||
|
||||
func testNextBatchBlockRange() async throws {
|
||||
// test first range
|
||||
var latestDownloadedHeight = processorConfig.walletBirthday // this can be either this or Wallet Birthday.
|
||||
var latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
||||
|
||||
var expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight...latestBlockchainHeight,
|
||||
scanRange: latestDownloadedHeight...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
latestDownloadedBlockHeight: latestDownloadedHeight
|
||||
)
|
||||
|
||||
var internalSyncProgress = InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
)
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
||||
|
||||
var syncRanges = try await internalSyncProgress.computeSyncRanges(
|
||||
birthday: processorConfig.walletBirthday,
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
latestScannedHeight: 0
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
expectedSyncRanges,
|
||||
syncRanges,
|
||||
"Failure when testing first range"
|
||||
)
|
||||
|
||||
// Test mid-range
|
||||
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultBatchSize)
|
||||
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
||||
|
||||
expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
latestDownloadedBlockHeight: latestDownloadedHeight
|
||||
)
|
||||
|
||||
internalSyncProgress = InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
)
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
||||
|
||||
syncRanges = try await internalSyncProgress.computeSyncRanges(
|
||||
birthday: processorConfig.walletBirthday,
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
latestScannedHeight: 0
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
expectedSyncRanges,
|
||||
syncRanges,
|
||||
"Failure when testing mid range"
|
||||
)
|
||||
|
||||
// Test last batch range
|
||||
|
||||
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + 950)
|
||||
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
||||
|
||||
expectedSyncRanges = SyncRanges(
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
||||
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
||||
latestScannedHeight: 0,
|
||||
latestDownloadedBlockHeight: latestDownloadedHeight
|
||||
)
|
||||
|
||||
internalSyncProgress = InternalSyncProgress(
|
||||
alias: .default,
|
||||
storage: InternalSyncProgressMemoryStorage(),
|
||||
logger: logger
|
||||
)
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
||||
|
||||
syncRanges = try await internalSyncProgress.computeSyncRanges(
|
||||
birthday: processorConfig.walletBirthday,
|
||||
latestBlockHeight: latestBlockchainHeight,
|
||||
latestScannedHeight: 0
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
expectedSyncRanges,
|
||||
syncRanges,
|
||||
"Failure when testing last range"
|
||||
)
|
||||
}
|
||||
|
||||
func testDetermineLowerBoundPastBirthday() async {
|
||||
let errorHeight = 781_906
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// ActionContextStateTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 15.06.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ActionContextStateTests: XCTestCase {
|
||||
func testPreviousState() async throws {
|
||||
let syncContext: ActionContext = .init(state: .idle)
|
||||
|
||||
await syncContext.update(state: .clearCache)
|
||||
|
||||
let currentState = await syncContext.state
|
||||
let prevState = await syncContext.prevState
|
||||
|
||||
XCTAssertTrue(
|
||||
currentState == .clearCache,
|
||||
"syncContext.state after update is expected to be .clearCache but received \(currentState)"
|
||||
)
|
||||
|
||||
if let prevState {
|
||||
XCTAssertTrue(
|
||||
prevState == .idle,
|
||||
"syncContext.prevState after update is expected to be .idle but received \(prevState)"
|
||||
)
|
||||
} else {
|
||||
XCTFail("syncContext.prevState is not expected to be nil.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
//
|
||||
// ChecksBeforeSyncActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 22.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ChecksBeforeSyncActionTests: ZcashTestCase {
|
||||
var underlyingDownloadRange: CompactBlockRange?
|
||||
var underlyingScanRange: CompactBlockRange?
|
||||
var underlyingLatestScannedHeight: BlockHeight?
|
||||
var underlyingLatestDownloadedBlockHeight: BlockHeight?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingDownloadRange = nil
|
||||
underlyingScanRange = nil
|
||||
underlyingLatestScannedHeight = nil
|
||||
underlyingLatestDownloadedBlockHeight = nil
|
||||
}
|
||||
|
||||
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_noDownloadNoScanRange() async throws {
|
||||
let checksBeforeSyncAction = setupAction()
|
||||
|
||||
let syncRanges = setupSyncRanges()
|
||||
|
||||
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
|
||||
XCTAssertNil(latestScannedHeight, "latestScannedHeight is expected to be nil.")
|
||||
}
|
||||
|
||||
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_nothingToClear() async throws {
|
||||
let checksBeforeSyncAction = setupAction()
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingLatestScannedHeight = BlockHeight(2000)
|
||||
underlyingLatestDownloadedBlockHeight = BlockHeight(2000)
|
||||
|
||||
let syncRanges = setupSyncRanges()
|
||||
|
||||
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
|
||||
XCTAssertNil(latestScannedHeight, "latestScannedHeight is expected to be nil.")
|
||||
}
|
||||
|
||||
func testChecksBeforeSyncAction_shouldClearBlockCacheAndUpdateInternalState_somethingToClear() async throws {
|
||||
let checksBeforeSyncAction = setupAction()
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingLatestScannedHeight = BlockHeight(2000)
|
||||
underlyingLatestDownloadedBlockHeight = BlockHeight(1000)
|
||||
|
||||
let syncRanges = setupSyncRanges()
|
||||
|
||||
let latestScannedHeight = checksBeforeSyncAction.shouldClearBlockCacheAndUpdateInternalState(syncRange: syncRanges)
|
||||
XCTAssertNotNil(latestScannedHeight, "latestScannedHeight is not expected to be nil.")
|
||||
}
|
||||
|
||||
func testChecksBeforeSyncAction_NextAction_ClearStorage() async throws {
|
||||
let compactBlockRepository = CompactBlockRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
compactBlockRepository.clearClosure = { }
|
||||
internalSyncProgressStorageMock.setForClosure = { _, _ in }
|
||||
|
||||
let checksBeforeSyncAction = setupAction(
|
||||
compactBlockRepository,
|
||||
internalSyncProgressStorageMock
|
||||
)
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingLatestScannedHeight = BlockHeight(2000)
|
||||
underlyingLatestDownloadedBlockHeight = BlockHeight(1000)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await checksBeforeSyncAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(compactBlockRepository.clearCalled, "storage.clear() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .fetchUTXO,
|
||||
"nextContext after .checksBeforeSync is expected to be .fetchUTXO but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testChecksBeforeSyncAction_NextAction_ClearStorage is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testChecksBeforeSyncAction_NextAction_CreateStorage() async throws {
|
||||
let compactBlockRepository = CompactBlockRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
compactBlockRepository.createClosure = { }
|
||||
|
||||
let checksBeforeSyncAction = setupAction(compactBlockRepository)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await checksBeforeSyncAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(compactBlockRepository.createCalled, "storage.create() is expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .fetchUTXO,
|
||||
"nextContext after .checksBeforeSync is expected to be .fetchUTXO but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testChecksBeforeSyncAction_NextAction_CreateStorage is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock(),
|
||||
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> ChecksBeforeSyncAction {
|
||||
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
|
||||
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
|
||||
}
|
||||
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
|
||||
|
||||
return ChecksBeforeSyncAction(
|
||||
container: mockContainer
|
||||
)
|
||||
}
|
||||
|
||||
private func setupSyncRanges() -> SyncRanges {
|
||||
SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: underlyingDownloadRange,
|
||||
scanRange: underlyingScanRange,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: underlyingLatestScannedHeight,
|
||||
latestDownloadedBlockHeight: underlyingLatestDownloadedBlockHeight
|
||||
)
|
||||
}
|
||||
|
||||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .checksBeforeSync)
|
||||
|
||||
await syncContext.update(syncRanges: setupSyncRanges())
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// ComputeSyncControlDataActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 22.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ComputeSyncControlDataActionTests: ZcashTestCase {
|
||||
var underlyingDownloadRange: CompactBlockRange?
|
||||
var underlyingScanRange: CompactBlockRange?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingDownloadRange = nil
|
||||
underlyingScanRange = nil
|
||||
}
|
||||
|
||||
func testComputeSyncControlDataAction_finishProcessingCase() async throws {
|
||||
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
|
||||
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
loggerMock
|
||||
)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateScannedDataCalled,
|
||||
"latestBlocksDataProvider.updateScannedData() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateBlockDataCalled,
|
||||
"latestBlocksDataProvider.updateBlockData() is expected to be called."
|
||||
)
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .finished,
|
||||
"nextContext after .computeSyncControlData is expected to be .finished but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncControlDataAction_finishProcessingCase is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testComputeSyncControlDataAction_fetchUTXOsCase() async throws {
|
||||
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
|
||||
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
loggerMock
|
||||
)
|
||||
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 10
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateScannedDataCalled,
|
||||
"latestBlocksDataProvider.updateScannedData() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
|
||||
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .fetchUTXO,
|
||||
"nextContext after .computeSyncControlData is expected to be .fetchUTXO but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncControlDataAction_checksBeforeSyncCase is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSyncControlData() -> SyncControlData {
|
||||
SyncControlData(
|
||||
latestBlockHeight: 0,
|
||||
latestScannedHeight: underlyingScanRange?.lowerBound,
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .computeSyncControlData)
|
||||
|
||||
await syncContext.update(syncControlData: setupSyncControlData())
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
|
||||
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> ComputeSyncControlDataAction {
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in blockDownloaderServiceMock }
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in latestBlocksDataProviderMock }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
return ComputeSyncControlDataAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
|
||||
private func setupDefaultMocksAndReturnAction(
|
||||
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
|
||||
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> ComputeSyncControlDataAction {
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 1
|
||||
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 1
|
||||
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 1
|
||||
latestBlocksDataProviderMock.updateScannedDataClosure = { }
|
||||
latestBlocksDataProviderMock.updateBlockDataClosure = { }
|
||||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
|
||||
return setupAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
loggerMock
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
//
|
||||
// ComputeSyncRangesActionTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 22.05.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
final class ComputeSyncRangesActionTests: ZcashTestCase {
|
||||
var underlyingDownloadRange: CompactBlockRange?
|
||||
var underlyingScanRange: CompactBlockRange?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
underlyingDownloadRange = nil
|
||||
underlyingScanRange = nil
|
||||
}
|
||||
|
||||
func testComputeSyncRangesAction_computeTotalProgressRange_noDownloadNoScanRange() async throws {
|
||||
let computeSyncRangesAction = setupAction()
|
||||
|
||||
let syncRanges = setupSyncRanges()
|
||||
|
||||
let totalProgressRange = computeSyncRangesAction.computeTotalProgressRange(from: syncRanges)
|
||||
|
||||
XCTAssertTrue(
|
||||
totalProgressRange == 0...0,
|
||||
"testComputeSyncRangesAction_computeTotalProgressRange_noDownloadNoScanRange is expected to be 0...0 but received \(totalProgressRange)"
|
||||
)
|
||||
}
|
||||
|
||||
func testComputeSyncRangesAction_computeTotalProgressRange_ValidRange() async throws {
|
||||
let computeSyncRangesAction = setupAction()
|
||||
|
||||
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
||||
let syncRanges = setupSyncRanges()
|
||||
let totalProgressRange = computeSyncRangesAction.computeTotalProgressRange(from: syncRanges)
|
||||
let expectedRange = 1000...2000
|
||||
|
||||
XCTAssertTrue(
|
||||
totalProgressRange == expectedRange,
|
||||
"testComputeSyncRangesAction_computeTotalProgressRange_ValidRange is expected to be \(expectedRange) but received \(totalProgressRange)"
|
||||
)
|
||||
}
|
||||
|
||||
func testComputeSyncRangesAction_finishProcessingCase() async throws {
|
||||
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
|
||||
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
internalSyncProgressStorageMock,
|
||||
loggerMock
|
||||
)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
|
||||
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateScannedDataCalled,
|
||||
"latestBlocksDataProvider.updateScannedData() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
|
||||
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .finished,
|
||||
"nextContext after .computeSyncRanges is expected to be .finished but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncRangesAction_finishProcessingCase is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testComputeSyncRangesAction_checksBeforeSyncCase() async throws {
|
||||
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
|
||||
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
internalSyncProgressStorageMock,
|
||||
loggerMock
|
||||
)
|
||||
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 10
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
|
||||
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateScannedDataCalled,
|
||||
"latestBlocksDataProvider.updateScannedData() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
|
||||
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .checksBeforeSync,
|
||||
"nextContext after .computeSyncRanges is expected to be .checksBeforeSync but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncRangesAction_checksBeforeSyncCase is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testComputeSyncRangesAction_waitCase() async throws {
|
||||
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
|
||||
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
let loggerMock = LoggerMock()
|
||||
|
||||
let computeSyncRangesAction = setupDefaultMocksAndReturnAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
internalSyncProgressStorageMock,
|
||||
loggerMock
|
||||
)
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 10
|
||||
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 10
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 10
|
||||
loggerMock.infoFileFunctionLineClosure = { _, _, _, _ in }
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
do {
|
||||
let nextContext = try await computeSyncRangesAction.run(with: syncContext) { _ in }
|
||||
|
||||
XCTAssertTrue(
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightCalled,
|
||||
"downloaderService.lastDownloadedBlockHeight() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(
|
||||
latestBlocksDataProviderMock.updateScannedDataCalled,
|
||||
"latestBlocksDataProvider.updateScannedData() is expected to be called."
|
||||
)
|
||||
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
|
||||
XCTAssertTrue(loggerMock.infoFileFunctionLineCalled, "logger.info() is expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .finished,
|
||||
"nextContext after .computeSyncRanges is expected to be .finished but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testComputeSyncRangesAction_waitCase is not expected to fail. \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSyncRanges() -> SyncRanges {
|
||||
SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: underlyingDownloadRange,
|
||||
scanRange: underlyingScanRange,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .computeSyncRanges)
|
||||
|
||||
await syncContext.update(syncRanges: setupSyncRanges())
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
||||
private func setupAction(
|
||||
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
|
||||
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
|
||||
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> ComputeSyncRangesAction {
|
||||
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
|
||||
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
|
||||
}
|
||||
|
||||
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in blockDownloaderServiceMock }
|
||||
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in latestBlocksDataProviderMock }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
||||
let config: CompactBlockProcessor.Configuration = .standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
|
||||
)
|
||||
|
||||
return ComputeSyncRangesAction(
|
||||
container: mockContainer,
|
||||
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
|
||||
)
|
||||
}
|
||||
|
||||
private func setupDefaultMocksAndReturnAction(
|
||||
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
|
||||
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
|
||||
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> ComputeSyncRangesAction {
|
||||
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 1
|
||||
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 1
|
||||
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 1
|
||||
latestBlocksDataProviderMock.updateScannedDataClosure = { }
|
||||
latestBlocksDataProviderMock.updateBlockDataClosure = { }
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
internalSyncProgressStorageMock.boolForReturnValue = true
|
||||
internalSyncProgressStorageMock.setBoolClosure = { _, _ in }
|
||||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
|
||||
return setupAction(
|
||||
blockDownloaderServiceMock,
|
||||
latestBlocksDataProviderMock,
|
||||
internalSyncProgressStorageMock,
|
||||
loggerMock
|
||||
)
|
||||
}
|
||||
}
|
|
@ -17,11 +17,12 @@ final class DownloadActionTests: ZcashTestCase {
|
|||
let blockDownloaderMock = BlockDownloaderMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1000
|
||||
blockDownloaderMock.setSyncRangeBatchSizeClosure = { _, _ in }
|
||||
blockDownloaderMock.setDownloadLimitClosure = { _ in }
|
||||
blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in }
|
||||
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in }
|
||||
blockDownloaderMock.updateLatestDownloadedBlockHeightClosure = { _ in }
|
||||
|
||||
let downloadAction = setupAction(
|
||||
blockDownloaderMock,
|
||||
|
@ -146,17 +147,13 @@ final class DownloadActionTests: ZcashTestCase {
|
|||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .download)
|
||||
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: underlyingDownloadRange,
|
||||
scanRange: underlyingScanRange,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: 2000,
|
||||
latestScannedHeight: underlyingScanRange?.lowerBound,
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(syncControlData: syncControlData)
|
||||
|
||||
return syncContext
|
||||
}
|
||||
|
|
|
@ -68,14 +68,12 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
func testEnhanceAction_NoEnhanceRange() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
@ -84,7 +82,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
_ = try await enhanceAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_NoEnhanceRange is not expected to fail. \(error)")
|
||||
}
|
||||
|
@ -93,15 +90,12 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
func testEnhanceAction_1000BlocksConditionNotFulfilled() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
@ -111,7 +105,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
do {
|
||||
_ = try await enhanceAction.run(with: syncContext) { _ in }
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
|
||||
XCTAssertFalse(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_1000BlocksConditionNotFulfilled is not expected to fail. \(error)")
|
||||
|
@ -121,10 +114,8 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
func testEnhanceAction_EnhancementOfBlocksCalled_FoundTransactions() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1500
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
|
||||
let transaction = ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
|
@ -151,8 +142,7 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
@ -174,7 +164,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
XCTAssertEqual(receivedTransaction.expiryHeight, transaction.expiryHeight, "ReceivedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_FoundTransactions is not expected to fail. \(error)")
|
||||
|
@ -184,10 +173,8 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
func testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1500
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
|
||||
let transaction = ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
|
@ -222,8 +209,7 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
|
||||
|
@ -241,7 +227,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
|
||||
|
@ -251,10 +236,8 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
func testEnhanceAction_EnhancementOfBlocksCalled_usingSmallRange_minedTransaction() async throws {
|
||||
let blockEnhancerMock = BlockEnhancerMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 200
|
||||
internalSyncProgressStorageMock.integerForReturnValue = 1
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 2000
|
||||
|
||||
let transaction = ZcashTransaction.Overview(
|
||||
accountId: 0,
|
||||
|
@ -289,11 +272,10 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
|
||||
let enhanceAction = setupAction(
|
||||
blockEnhancerMock,
|
||||
transactionRepositoryMock,
|
||||
internalSyncProgressStorageMock
|
||||
transactionRepositoryMock
|
||||
)
|
||||
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (100, 200))
|
||||
underlyingEnhanceRange = CompactBlockRange(uncheckedBounds: (1900, 2000))
|
||||
|
||||
let syncContext = await setupActionContext()
|
||||
|
||||
|
@ -308,7 +290,6 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
XCTAssertEqual(minedTransaction.expiryHeight, transaction.expiryHeight, "MinedTransaction differs from mocked one.")
|
||||
}
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.integerForCalled, "internalSyncProgress.load() is expected to be called.")
|
||||
XCTAssertTrue(blockEnhancerMock.enhanceAtDidEnhanceCalled, "blockEnhancer.enhance() is expected to be called.")
|
||||
} catch {
|
||||
XCTFail("testEnhanceAction_EnhancementOfBlocksCalled_minedTransaction is not expected to fail. \(error)")
|
||||
|
@ -318,17 +299,13 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .enhance)
|
||||
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: underlyingDownloadRange,
|
||||
scanRange: underlyingScanRange,
|
||||
enhanceRange: underlyingEnhanceRange,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: 2000,
|
||||
latestScannedHeight: underlyingScanRange?.lowerBound,
|
||||
firstUnenhancedHeight: underlyingEnhanceRange?.lowerBound
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(syncControlData: syncControlData)
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
|
@ -337,13 +314,8 @@ final class EnhanceActionTests: ZcashTestCase {
|
|||
private func setupAction(
|
||||
_ blockEnhancerMock: BlockEnhancerMock = BlockEnhancerMock(),
|
||||
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),
|
||||
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> EnhanceAction {
|
||||
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
|
||||
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
|
||||
}
|
||||
|
||||
mockContainer.mock(type: BlockEnhancer.self, isSingleton: true) { _ in blockEnhancerMock }
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
|
|
|
@ -17,7 +17,7 @@ final class FetchUTXOsActionTests: ZcashTestCase {
|
|||
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
|
||||
let insertedEntity = UnspentTransactionOutputEntityMock(address: "addr", txid: Data(), index: 0, script: Data(), valueZat: 1, height: 2)
|
||||
let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3)
|
||||
uTXOFetcherMock.fetchAtDidFetchReturnValue = (inserted: [insertedEntity], skipped: [skippedEntity])
|
||||
uTXOFetcherMock.fetchDidFetchReturnValue = (inserted: [insertedEntity], skipped: [skippedEntity])
|
||||
|
||||
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
|
||||
mockContainer.mock(type: UTXOFetcher.self, isSingleton: true) { _ in uTXOFetcherMock }
|
||||
|
@ -26,17 +26,13 @@ final class FetchUTXOsActionTests: ZcashTestCase {
|
|||
|
||||
let syncContext: ActionContext = .init(state: .fetchUTXO)
|
||||
|
||||
let syncRanges = SyncRanges(
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: nil,
|
||||
scanRange: nil,
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
latestScannedHeight: 0,
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(syncControlData: syncControlData)
|
||||
|
||||
do {
|
||||
let nextContext = try await fetchUTXOsAction.run(with: syncContext) { event in
|
||||
|
@ -48,7 +44,7 @@ final class FetchUTXOsActionTests: ZcashTestCase {
|
|||
XCTAssertEqual(result.skipped as! [UnspentTransactionOutputEntityMock], [skippedEntity])
|
||||
}
|
||||
XCTAssertTrue(loggerMock.debugFileFunctionLineCalled, "logger.debug(...) is expected to be called.")
|
||||
XCTAssertTrue(uTXOFetcherMock.fetchAtDidFetchCalled, "utxoFetcher.fetch() is expected to be called.")
|
||||
XCTAssertTrue(uTXOFetcherMock.fetchDidFetchCalled, "utxoFetcher.fetch() is expected to be called.")
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .handleSaplingParams,
|
||||
|
|
|
@ -26,13 +26,11 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
|
@ -45,7 +43,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
)
|
||||
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
|
||||
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
|
@ -61,15 +58,13 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
|
@ -83,7 +78,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
)
|
||||
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
|
||||
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testMigrateLegacyCacheDBAction_noFsBlockCacheRoot is expected to fail with \
|
||||
|
@ -96,7 +90,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
|
@ -107,8 +100,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
|
@ -121,7 +113,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
)
|
||||
XCTAssertFalse(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is not expected to be called.")
|
||||
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
|
@ -137,7 +128,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
|
@ -148,8 +138,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
|
@ -162,7 +151,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
)
|
||||
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
|
||||
XCTAssertFalse(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is not expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
|
@ -178,7 +166,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
|
@ -190,8 +177,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
|
@ -204,7 +190,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
)
|
||||
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
|
||||
XCTAssertTrue(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is expected to be called.")
|
||||
XCTAssertFalse(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is not expected to be called.")
|
||||
} catch {
|
||||
XCTFail("""
|
||||
testMigrateLegacyCacheDBAction_removeItemFailed is expected to fail with \
|
||||
|
@ -217,7 +202,6 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
|
||||
let transactionRepositoryMock = TransactionRepositoryMock()
|
||||
let zcashFileManagerMock = ZcashFileManagerMock()
|
||||
let internalSyncProgressStorageMock = InternalSyncProgressStorageMock()
|
||||
|
||||
// any valid URL needed...
|
||||
underlyingCacheDbURL = DefaultResourceProvider(network: ZcashNetworkBuilder.network(for: .testnet)).fsCacheURL
|
||||
|
@ -226,24 +210,19 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
zcashFileManagerMock.isReadableFileAtPathReturnValue = true
|
||||
zcashFileManagerMock.removeItemAtClosure = { _ in }
|
||||
compactBlockRepositoryMock.createClosure = { }
|
||||
transactionRepositoryMock.lastScannedHeightReturnValue = 1
|
||||
internalSyncProgressStorageMock.setForClosure = { _, _ in }
|
||||
|
||||
let migrateLegacyCacheDBAction = setupAction(
|
||||
compactBlockRepositoryMock,
|
||||
transactionRepositoryMock,
|
||||
zcashFileManagerMock,
|
||||
internalSyncProgressStorageMock
|
||||
zcashFileManagerMock
|
||||
)
|
||||
|
||||
do {
|
||||
let nextContext = try await migrateLegacyCacheDBAction.run(with: .init(state: .migrateLegacyCacheDB)) { _ in }
|
||||
|
||||
XCTAssertTrue(compactBlockRepositoryMock.createCalled, "storage.create() is expected to be called.")
|
||||
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
|
||||
XCTAssertTrue(zcashFileManagerMock.isReadableFileAtPathCalled, "fileManager.isReadableFile() is expected to be called.")
|
||||
XCTAssertTrue(zcashFileManagerMock.removeItemAtCalled, "fileManager.removeItem() is expected to be called.")
|
||||
XCTAssertTrue(internalSyncProgressStorageMock.setForCalled, "internalSyncProgress.set() is expected to be called.")
|
||||
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
|
@ -259,12 +238,8 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase {
|
|||
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock(),
|
||||
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),
|
||||
_ zcashFileManagerMock: ZcashFileManagerMock = ZcashFileManagerMock(),
|
||||
_ internalSyncProgressStorageMock: InternalSyncProgressStorageMock = InternalSyncProgressStorageMock(),
|
||||
_ loggerMock: LoggerMock = LoggerMock()
|
||||
) -> MigrateLegacyCacheDBAction {
|
||||
mockContainer.register(type: InternalSyncProgress.self, isSingleton: true) { _ in
|
||||
InternalSyncProgress(alias: .default, storage: internalSyncProgressStorageMock, logger: loggerMock)
|
||||
}
|
||||
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
|
||||
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
|
||||
mockContainer.mock(type: ZcashFileManager.self, isSingleton: true) { _ in zcashFileManagerMock }
|
||||
|
|
|
@ -108,17 +108,13 @@ final class ScanActionTests: ZcashTestCase {
|
|||
private func setupActionContext() async -> ActionContext {
|
||||
let syncContext: ActionContext = .init(state: .scan)
|
||||
|
||||
let syncRanges = SyncRanges(
|
||||
latestBlockHeight: 0,
|
||||
downloadRange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
|
||||
scanRange: CompactBlockRange(uncheckedBounds: (1000, 2000)),
|
||||
enhanceRange: nil,
|
||||
fetchUTXORange: nil,
|
||||
latestScannedHeight: nil,
|
||||
latestDownloadedBlockHeight: nil
|
||||
let syncControlData = SyncControlData(
|
||||
latestBlockHeight: 2000,
|
||||
latestScannedHeight: 1000,
|
||||
firstUnenhancedHeight: nil
|
||||
)
|
||||
|
||||
await syncContext.update(syncRanges: syncRanges)
|
||||
await syncContext.update(syncControlData: syncControlData)
|
||||
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
|
||||
|
||||
return syncContext
|
||||
|
|
|
@ -31,8 +31,8 @@ final class ValidateServerActionTests: ZcashTestCase {
|
|||
let nextContext = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
|
||||
let nextState = await nextContext.state
|
||||
XCTAssertTrue(
|
||||
nextState == .computeSyncRanges,
|
||||
"nextContext after .validateServer is expected to be .computeSyncRanges but received \(nextState)"
|
||||
nextState == .computeSyncControlData,
|
||||
"nextContext after .validateServer is expected to be .computeSyncControlData but received \(nextState)"
|
||||
)
|
||||
} catch {
|
||||
XCTFail("testValidateServerAction_NextAction is not expected to fail. \(error)")
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
//
|
||||
// InternalSyncProgressTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 30.11.2022.
|
||||
//
|
||||
|
||||
@testable import TestUtils
|
||||
import XCTest
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class InternalSyncProgressTests: ZcashTestCase {
|
||||
var storage: InternalSyncProgressDiskStorage!
|
||||
var internalSyncProgress: InternalSyncProgress!
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
for key in InternalSyncProgress.Key.allCases {
|
||||
UserDefaults.standard.removeObject(forKey: key.with(.default))
|
||||
}
|
||||
|
||||
storage = InternalSyncProgressDiskStorage(storageURL: testGeneralStorageDirectory, logger: logger)
|
||||
internalSyncProgress = InternalSyncProgress(alias: .default, storage: storage, logger: logger)
|
||||
try await internalSyncProgress.initialize()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
try super.tearDownWithError()
|
||||
storage = nil
|
||||
internalSyncProgress = nil
|
||||
}
|
||||
|
||||
func test__trackedValuesAreHigherThanLatestHeight__nextStateIsWait() async throws {
|
||||
let latestHeight = 623000
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
let nextState = try await internalSyncProgress.computeNextState(
|
||||
latestBlockHeight: latestHeight,
|
||||
latestScannedHeight: 630000,
|
||||
walletBirthday: 600000
|
||||
)
|
||||
|
||||
switch nextState {
|
||||
case let .wait(latestHeight, latestDownloadHeight):
|
||||
XCTAssertEqual(latestHeight, 623000)
|
||||
XCTAssertEqual(latestDownloadHeight, 630000)
|
||||
|
||||
default:
|
||||
XCTFail("State should be wait. Unexpected state: \(nextState)")
|
||||
}
|
||||
}
|
||||
|
||||
func test__trackedValuesAreLowerThanLatestHeight__nextStateIsProcessNewBlocks() async throws {
|
||||
let latestHeight = 640000
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
let nextState = try await internalSyncProgress.computeNextState(
|
||||
latestBlockHeight: latestHeight,
|
||||
latestScannedHeight: 620000,
|
||||
walletBirthday: 600000
|
||||
)
|
||||
|
||||
switch nextState {
|
||||
case let .processNewBlocks(ranges):
|
||||
XCTAssertEqual(ranges.downloadRange, 630001...640000)
|
||||
XCTAssertEqual(ranges.scanRange, 620001...640000)
|
||||
XCTAssertEqual(ranges.enhanceRange, 630001...640000)
|
||||
XCTAssertEqual(ranges.fetchUTXORange, 630001...640000)
|
||||
|
||||
default:
|
||||
XCTFail("State should be processNewBlocks. Unexpected state: \(nextState)")
|
||||
}
|
||||
}
|
||||
|
||||
func test__trackedValuesAreSameAsLatestHeight__nextStateIsFinishProcessing() async throws {
|
||||
let latestHeight = 630000
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 630000, alias: .default)
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
let nextState = try await internalSyncProgress.computeNextState(
|
||||
latestBlockHeight: latestHeight,
|
||||
latestScannedHeight: 630000,
|
||||
walletBirthday: 600000
|
||||
)
|
||||
|
||||
switch nextState {
|
||||
case let .finishProcessing(height):
|
||||
XCTAssertEqual(height, latestHeight)
|
||||
|
||||
default:
|
||||
XCTFail("State should be finishProcessing. Unexpected state: \(nextState)")
|
||||
}
|
||||
}
|
||||
|
||||
func test__rewindToHeightThatIsHigherThanTrackedHeight__rewindsToTrackedHeight() async throws {
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
try await internalSyncProgress.rewind(to: 640000)
|
||||
|
||||
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
|
||||
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
|
||||
XCTAssertEqual(latestEnhancedHeight, 630000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 630000)
|
||||
}
|
||||
|
||||
func test__rewindToHeightThatIsLowerThanTrackedHeight__rewindsToRewindHeight() async throws {
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
try await internalSyncProgress.rewind(to: 620000)
|
||||
|
||||
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
|
||||
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
|
||||
XCTAssertEqual(latestEnhancedHeight, 620000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 620000)
|
||||
}
|
||||
|
||||
func test__get__returnsStoredValue() async throws {
|
||||
try await storage.set(621000, for: "latestEnhancedHeight")
|
||||
let latestEnhancedHeight = try await internalSyncProgress.latestEnhancedHeight
|
||||
XCTAssertEqual(latestEnhancedHeight, 621000)
|
||||
|
||||
try await storage.set(619000, for: "latestUTXOFetchedHeight")
|
||||
let latestUTXOFetchedHeight = try await internalSyncProgress.latestUTXOFetchedHeight
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 619000)
|
||||
}
|
||||
|
||||
func test__set__storeValue() async throws {
|
||||
try await internalSyncProgress.set(521000, .latestEnhancedHeight)
|
||||
let latestEnhancedHeight = try await storage.integer(for: "latestEnhancedHeight")
|
||||
XCTAssertEqual(latestEnhancedHeight, 521000)
|
||||
|
||||
try await internalSyncProgress.set(519000, .latestUTXOFetchedHeight)
|
||||
let latestUTXOFetchedHeight = try await storage.integer(for: "latestUTXOFetchedHeight")
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 519000)
|
||||
}
|
||||
|
||||
func test__whenUsingDefaultAliasKeysAreBackwardsCompatible() async throws {
|
||||
try await internalSyncProgress.set(630000, .latestDownloadedBlockHeight)
|
||||
try await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress.set(630000, .latestEnhancedHeight)
|
||||
|
||||
let latestDownloadedBlockHeight = try await storage.integer(for: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue)
|
||||
let latestUTXOFetchedHeight = try await storage.integer(for: InternalSyncProgress.Key.latestUTXOFetchedHeight.rawValue)
|
||||
let latestEnhancedHeight = try await storage.integer(for: InternalSyncProgress.Key.latestEnhancedHeight.rawValue)
|
||||
XCTAssertEqual(latestDownloadedBlockHeight, 630000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeight, 630000)
|
||||
XCTAssertEqual(latestEnhancedHeight, 630000)
|
||||
}
|
||||
|
||||
func test__usingDifferentAliasesStoreValuesIndependently() async throws {
|
||||
let internalSyncProgress1 = InternalSyncProgress(alias: .custom("alias1"), storage: storage, logger: logger)
|
||||
try await internalSyncProgress1.set(121000, .latestDownloadedBlockHeight)
|
||||
try await internalSyncProgress1.set(121000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress1.set(121000, .latestEnhancedHeight)
|
||||
|
||||
let internalSyncProgress2 = InternalSyncProgress(alias: .custom("alias2"), storage: storage, logger: logger)
|
||||
try await internalSyncProgress2.set(630000, .latestDownloadedBlockHeight)
|
||||
try await internalSyncProgress2.set(630000, .latestUTXOFetchedHeight)
|
||||
try await internalSyncProgress2.set(630000, .latestEnhancedHeight)
|
||||
|
||||
let latestDownloadedBlockHeight1 = try await internalSyncProgress1.load(.latestDownloadedBlockHeight)
|
||||
let latestUTXOFetchedHeigh1 = try await internalSyncProgress1.load(.latestUTXOFetchedHeight)
|
||||
let latestEnhancedHeight1 = try await internalSyncProgress1.load(.latestEnhancedHeight)
|
||||
XCTAssertEqual(latestDownloadedBlockHeight1, 121000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeigh1, 121000)
|
||||
XCTAssertEqual(latestEnhancedHeight1, 121000)
|
||||
|
||||
let latestDownloadedBlockHeight2 = try await internalSyncProgress2.load(.latestDownloadedBlockHeight)
|
||||
let latestUTXOFetchedHeigh2 = try await internalSyncProgress2.load(.latestUTXOFetchedHeight)
|
||||
let latestEnhancedHeight2 = try await internalSyncProgress2.load(.latestEnhancedHeight)
|
||||
XCTAssertEqual(latestDownloadedBlockHeight2, 630000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeigh2, 630000)
|
||||
XCTAssertEqual(latestEnhancedHeight2, 630000)
|
||||
}
|
||||
|
||||
func test__migrateFromUserDefaults__withDefaultAlias() async throws {
|
||||
let userDefaults = UserDefaults.standard
|
||||
userDefaults.set(113000, forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(.default))
|
||||
userDefaults.set(114000, forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(.default))
|
||||
userDefaults.set(115000, forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(.default))
|
||||
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 150000, alias: .default)
|
||||
|
||||
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
|
||||
let latestUTXOFetchedHeigh = try await internalSyncProgress.load(.latestEnhancedHeight)
|
||||
let latestEnhancedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
|
||||
XCTAssertEqual(latestDownloadedBlockHeight, 113000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeigh, 114000)
|
||||
XCTAssertEqual(latestEnhancedHeight, 115000)
|
||||
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(.default)), 0)
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(.default)), 0)
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(.default)), 0)
|
||||
}
|
||||
|
||||
func test__migrateFromUserDefaults__withAlias() async throws {
|
||||
let userDefaults = UserDefaults.standard
|
||||
let alias: ZcashSynchronizerAlias = .custom("something")
|
||||
internalSyncProgress = InternalSyncProgress(alias: alias, storage: storage, logger: logger)
|
||||
|
||||
userDefaults.set(113000, forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(alias))
|
||||
userDefaults.set(114000, forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(alias))
|
||||
userDefaults.set(115000, forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(alias))
|
||||
|
||||
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: 150000, alias: alias)
|
||||
|
||||
let latestDownloadedBlockHeight = try await internalSyncProgress.load(.latestDownloadedBlockHeight)
|
||||
let latestUTXOFetchedHeigh = try await internalSyncProgress.load(.latestEnhancedHeight)
|
||||
let latestEnhancedHeight = try await internalSyncProgress.load(.latestUTXOFetchedHeight)
|
||||
XCTAssertEqual(latestDownloadedBlockHeight, 113000)
|
||||
XCTAssertEqual(latestUTXOFetchedHeigh, 114000)
|
||||
XCTAssertEqual(latestEnhancedHeight, 115000)
|
||||
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.with(alias)), 0)
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestEnhancedHeight.with(alias)), 0)
|
||||
XCTAssertEqual(userDefaults.integer(forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.with(alias)), 0)
|
||||
}
|
||||
}
|
|
@ -92,7 +92,6 @@ class SynchronizerTests: ZcashTestCase {
|
|||
let syncSyncedExpectation = XCTestExpectation(description: "synchronizerSynced Expectation")
|
||||
sdkSynchronizerInternalSyncStatusHandler.subscribe(to: synchronizer.stateStream, expectations: [.synced: syncSyncedExpectation])
|
||||
|
||||
try await resetDefaultInternalSyncProgress(to: birthday)
|
||||
await (synchronizer.blockProcessor.service as? LightWalletGRPCService)?.latestBlockHeightProvider = MockLatestBlockHeightProvider(
|
||||
birthday: self.birthday + 99
|
||||
)
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// InternalSyncProgressMemoryStorage.swift
|
||||
//
|
||||
//
|
||||
// Created by Michal Fousek on 24.11.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class InternalSyncProgressMemoryStorage: InternalSyncProgressStorage {
|
||||
private var boolStorage: [String: Bool] = [:]
|
||||
private var storage: [String: Int] = [:]
|
||||
|
||||
func initialize() async throws { }
|
||||
|
||||
func bool(for key: String) async throws -> Bool {
|
||||
return boolStorage[key, default: false]
|
||||
}
|
||||
|
||||
func integer(for key: String) async throws -> Int {
|
||||
return storage[key, default: 0]
|
||||
}
|
||||
|
||||
func set(_ value: Int, for key: String) async throws {
|
||||
storage[key] = value
|
||||
}
|
||||
|
||||
func set(_ value: Bool, for key: String) async throws {
|
||||
boolStorage[key] = value
|
||||
}
|
||||
|
||||
func synchronize() -> Bool { true }
|
||||
}
|
|
@ -115,6 +115,10 @@ extension MockTransactionRepository: TransactionRepository {
|
|||
nil
|
||||
}
|
||||
|
||||
func firstUnenhancedHeight() throws -> ZcashLightClientKit.BlockHeight? {
|
||||
nil
|
||||
}
|
||||
|
||||
func isInitialized() throws -> Bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ extension BlockEnhancer { }
|
|||
extension BlockScanner { }
|
||||
extension BlockValidator { }
|
||||
extension CompactBlockRepository { }
|
||||
extension InternalSyncProgressStorage { }
|
||||
extension LatestBlocksDataProvider { }
|
||||
extension LightWalletdInfo { }
|
||||
extension LightWalletService { }
|
||||
|
|
|
@ -95,6 +95,54 @@ class BlockDownloaderMock: BlockDownloader {
|
|||
try await waitUntilRequestedBlocksAreDownloadedInClosure!(range)
|
||||
}
|
||||
|
||||
// MARK: - update
|
||||
|
||||
var updateLatestDownloadedBlockHeightCallsCount = 0
|
||||
var updateLatestDownloadedBlockHeightCalled: Bool {
|
||||
return updateLatestDownloadedBlockHeightCallsCount > 0
|
||||
}
|
||||
var updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight: BlockHeight?
|
||||
var updateLatestDownloadedBlockHeightClosure: ((BlockHeight) async -> Void)?
|
||||
|
||||
func update(latestDownloadedBlockHeight: BlockHeight) async {
|
||||
updateLatestDownloadedBlockHeightCallsCount += 1
|
||||
updateLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight = latestDownloadedBlockHeight
|
||||
await updateLatestDownloadedBlockHeightClosure!(latestDownloadedBlockHeight)
|
||||
}
|
||||
|
||||
// MARK: - latestDownloadedBlockHeight
|
||||
|
||||
var latestDownloadedBlockHeightCallsCount = 0
|
||||
var latestDownloadedBlockHeightCalled: Bool {
|
||||
return latestDownloadedBlockHeightCallsCount > 0
|
||||
}
|
||||
var latestDownloadedBlockHeightReturnValue: BlockHeight!
|
||||
var latestDownloadedBlockHeightClosure: (() async -> BlockHeight)?
|
||||
|
||||
func latestDownloadedBlockHeight() async -> BlockHeight {
|
||||
latestDownloadedBlockHeightCallsCount += 1
|
||||
if let closure = latestDownloadedBlockHeightClosure {
|
||||
return await closure()
|
||||
} else {
|
||||
return latestDownloadedBlockHeightReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - rewind
|
||||
|
||||
var rewindLatestDownloadedBlockHeightCallsCount = 0
|
||||
var rewindLatestDownloadedBlockHeightCalled: Bool {
|
||||
return rewindLatestDownloadedBlockHeightCallsCount > 0
|
||||
}
|
||||
var rewindLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight: BlockHeight?
|
||||
var rewindLatestDownloadedBlockHeightClosure: ((BlockHeight?) async -> Void)?
|
||||
|
||||
func rewind(latestDownloadedBlockHeight: BlockHeight?) async {
|
||||
rewindLatestDownloadedBlockHeightCallsCount += 1
|
||||
rewindLatestDownloadedBlockHeightReceivedLatestDownloadedBlockHeight = latestDownloadedBlockHeight
|
||||
await rewindLatestDownloadedBlockHeightClosure!(latestDownloadedBlockHeight)
|
||||
}
|
||||
|
||||
}
|
||||
class BlockDownloaderServiceMock: BlockDownloaderService {
|
||||
|
||||
|
@ -472,117 +520,6 @@ class CompactBlockRepositoryMock: CompactBlockRepository {
|
|||
try await clearClosure!()
|
||||
}
|
||||
|
||||
}
|
||||
class InternalSyncProgressStorageMock: InternalSyncProgressStorage {
|
||||
|
||||
|
||||
init(
|
||||
) {
|
||||
}
|
||||
|
||||
// MARK: - initialize
|
||||
|
||||
var initializeThrowableError: Error?
|
||||
var initializeCallsCount = 0
|
||||
var initializeCalled: Bool {
|
||||
return initializeCallsCount > 0
|
||||
}
|
||||
var initializeClosure: (() async throws -> Void)?
|
||||
|
||||
func initialize() async throws {
|
||||
if let error = initializeThrowableError {
|
||||
throw error
|
||||
}
|
||||
initializeCallsCount += 1
|
||||
try await initializeClosure!()
|
||||
}
|
||||
|
||||
// MARK: - bool
|
||||
|
||||
var boolForThrowableError: Error?
|
||||
var boolForCallsCount = 0
|
||||
var boolForCalled: Bool {
|
||||
return boolForCallsCount > 0
|
||||
}
|
||||
var boolForReceivedKey: String?
|
||||
var boolForReturnValue: Bool!
|
||||
var boolForClosure: ((String) async throws -> Bool)?
|
||||
|
||||
func bool(for key: String) async throws -> Bool {
|
||||
if let error = boolForThrowableError {
|
||||
throw error
|
||||
}
|
||||
boolForCallsCount += 1
|
||||
boolForReceivedKey = key
|
||||
if let closure = boolForClosure {
|
||||
return try await closure(key)
|
||||
} else {
|
||||
return boolForReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - integer
|
||||
|
||||
var integerForThrowableError: Error?
|
||||
var integerForCallsCount = 0
|
||||
var integerForCalled: Bool {
|
||||
return integerForCallsCount > 0
|
||||
}
|
||||
var integerForReceivedKey: String?
|
||||
var integerForReturnValue: Int!
|
||||
var integerForClosure: ((String) async throws -> Int)?
|
||||
|
||||
func integer(for key: String) async throws -> Int {
|
||||
if let error = integerForThrowableError {
|
||||
throw error
|
||||
}
|
||||
integerForCallsCount += 1
|
||||
integerForReceivedKey = key
|
||||
if let closure = integerForClosure {
|
||||
return try await closure(key)
|
||||
} else {
|
||||
return integerForReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - set
|
||||
|
||||
var setForThrowableError: Error?
|
||||
var setForCallsCount = 0
|
||||
var setForCalled: Bool {
|
||||
return setForCallsCount > 0
|
||||
}
|
||||
var setForReceivedArguments: (value: Int, key: String)?
|
||||
var setForClosure: ((Int, String) async throws -> Void)?
|
||||
|
||||
func set(_ value: Int, for key: String) async throws {
|
||||
if let error = setForThrowableError {
|
||||
throw error
|
||||
}
|
||||
setForCallsCount += 1
|
||||
setForReceivedArguments = (value: value, key: key)
|
||||
try await setForClosure!(value, key)
|
||||
}
|
||||
|
||||
// MARK: - set
|
||||
|
||||
var setBoolThrowableError: Error?
|
||||
var setBoolCallsCount = 0
|
||||
var setBoolCalled: Bool {
|
||||
return setBoolCallsCount > 0
|
||||
}
|
||||
var setBoolReceivedArguments: (value: Bool, key: String)?
|
||||
var setBoolClosure: ((Bool, String) async throws -> Void)?
|
||||
|
||||
func set(_ value: Bool, for key: String) async throws {
|
||||
if let error = setBoolThrowableError {
|
||||
throw error
|
||||
}
|
||||
setBoolCallsCount += 1
|
||||
setBoolReceivedArguments = (value: value, key: key)
|
||||
try await setBoolClosure!(value, key)
|
||||
}
|
||||
|
||||
}
|
||||
class LatestBlocksDataProviderMock: LatestBlocksDataProvider {
|
||||
|
||||
|
@ -606,6 +543,7 @@ class LatestBlocksDataProviderMock: LatestBlocksDataProvider {
|
|||
get { return underlyingWalletBirthday }
|
||||
}
|
||||
var underlyingWalletBirthday: BlockHeight!
|
||||
var firstUnenhancedHeight: BlockHeight?
|
||||
|
||||
// MARK: - updateScannedData
|
||||
|
||||
|
@ -1709,6 +1647,28 @@ class TransactionRepositoryMock: TransactionRepository {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - firstUnenhancedHeight
|
||||
|
||||
var firstUnenhancedHeightThrowableError: Error?
|
||||
var firstUnenhancedHeightCallsCount = 0
|
||||
var firstUnenhancedHeightCalled: Bool {
|
||||
return firstUnenhancedHeightCallsCount > 0
|
||||
}
|
||||
var firstUnenhancedHeightReturnValue: BlockHeight?
|
||||
var firstUnenhancedHeightClosure: (() throws -> BlockHeight?)?
|
||||
|
||||
func firstUnenhancedHeight() throws -> BlockHeight? {
|
||||
if let error = firstUnenhancedHeightThrowableError {
|
||||
throw error
|
||||
}
|
||||
firstUnenhancedHeightCallsCount += 1
|
||||
if let closure = firstUnenhancedHeightClosure {
|
||||
return try closure()
|
||||
} else {
|
||||
return firstUnenhancedHeightReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - isInitialized
|
||||
|
||||
var isInitializedThrowableError: Error?
|
||||
|
@ -2005,25 +1965,25 @@ class UTXOFetcherMock: UTXOFetcher {
|
|||
|
||||
// MARK: - fetch
|
||||
|
||||
var fetchAtDidFetchThrowableError: Error?
|
||||
var fetchAtDidFetchCallsCount = 0
|
||||
var fetchAtDidFetchCalled: Bool {
|
||||
return fetchAtDidFetchCallsCount > 0
|
||||
var fetchDidFetchThrowableError: Error?
|
||||
var fetchDidFetchCallsCount = 0
|
||||
var fetchDidFetchCalled: Bool {
|
||||
return fetchDidFetchCallsCount > 0
|
||||
}
|
||||
var fetchAtDidFetchReceivedArguments: (range: CompactBlockRange, didFetch: (Float) async -> Void)?
|
||||
var fetchAtDidFetchReturnValue: (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])!
|
||||
var fetchAtDidFetchClosure: ((CompactBlockRange, @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))?
|
||||
var fetchDidFetchReceivedDidFetch: ((Float) async -> Void)?
|
||||
var fetchDidFetchReturnValue: (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])!
|
||||
var fetchDidFetchClosure: ((@escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))?
|
||||
|
||||
func fetch(at range: CompactBlockRange, didFetch: @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
|
||||
if let error = fetchAtDidFetchThrowableError {
|
||||
func fetch(didFetch: @escaping (Float) async -> Void) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
|
||||
if let error = fetchDidFetchThrowableError {
|
||||
throw error
|
||||
}
|
||||
fetchAtDidFetchCallsCount += 1
|
||||
fetchAtDidFetchReceivedArguments = (range: range, didFetch: didFetch)
|
||||
if let closure = fetchAtDidFetchClosure {
|
||||
return try await closure(range, didFetch)
|
||||
fetchDidFetchCallsCount += 1
|
||||
fetchDidFetchReceivedDidFetch = didFetch
|
||||
if let closure = fetchDidFetchClosure {
|
||||
return try await closure(didFetch)
|
||||
} else {
|
||||
return fetchAtDidFetchReturnValue
|
||||
return fetchDidFetchReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,11 +66,6 @@ class TestCoordinator {
|
|||
let databases = TemporaryDbBuilder.build()
|
||||
self.databases = databases
|
||||
|
||||
let storage = InternalSyncProgressDiskStorage(storageURL: databases.generalStorageURL, logger: logger)
|
||||
let internalSyncProgress = InternalSyncProgress(alias: alias, storage: storage, logger: logger)
|
||||
try await internalSyncProgress.initialize()
|
||||
try await internalSyncProgress.rewind(to: 0)
|
||||
|
||||
let initializer = Initializer(
|
||||
container: container,
|
||||
cacheDbURL: nil,
|
||||
|
|
|
@ -59,22 +59,12 @@ class ZcashTestCase: XCTestCase {
|
|||
testGeneralStorageDirectory = nil
|
||||
}
|
||||
|
||||
// MARK: - InternalSyncProgress
|
||||
|
||||
func resetDefaultInternalSyncProgress(to height: BlockHeight = 0) async throws {
|
||||
let storage = InternalSyncProgressDiskStorage(storageURL: testGeneralStorageDirectory, logger: logger)
|
||||
let internalSyncProgress = InternalSyncProgress(alias: .default, storage: storage, logger: logger)
|
||||
try await internalSyncProgress.initialize()
|
||||
try await internalSyncProgress.rewind(to: 0)
|
||||
}
|
||||
|
||||
// MARK: - XCTestCase
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
createMockContainer()
|
||||
try createPaths()
|
||||
try await resetDefaultInternalSyncProgress()
|
||||
}
|
||||
|
||||
override func setUp() {
|
||||
|
|
Loading…
Reference in New Issue