[#843] Simplify Synchronizer Status

- SyncStatus refactored to InternalSyncStatus, so the SDK works internally with the SyncStatus as previously
- new SyncStatus is a computed property that maps InternalSyncStatus to simplified SyncStatus

[#843] Simplify Synchronizer Status (#1010)

- simplification of SyncStatus done
- % computation moved to the mapping function so the rest of the SDK works just like before
- fixed unit tests
- new unit tests checking the boundaries of the new progress

[#843] Simplify Synchronizer Status (#1010)

- removed commented code

[#843] Simplify Synchronizer Status (#1010)

- package.resolved

[#843] Simplify Synchronizer Status (#1010)

- fixed dark side tests

[#843] Simplify Synchronizer Status (#1010)

- package.resolved
This commit is contained in:
Lukas Korba 2023-04-28 19:13:21 +02:00
parent 336c5a7c38
commit 44e17ca042
19 changed files with 348 additions and 153 deletions

View File

@ -123,7 +123,7 @@ class SendViewController: UIViewController {
func isFormValid() async -> Bool {
switch synchronizer.latestState.syncStatus {
case .synced:
case .upToDate:
let isBalanceValid = await self.isBalanceValid()
let isAmountValid = await self.isAmountValid()
return isBalanceValid && isAmountValid && isRecipientValid()
@ -301,26 +301,17 @@ extension SDKSynchronizer {
static func textFor(state: SyncStatus) -> String {
switch state {
case .syncing(let progress):
return "Syncing \(progress.progressHeight)/\(progress.targetHeight)"
return "Syncing \(progress)"
case .enhancing(let enhanceProgress):
return "Enhancing tx \(enhanceProgress.enhancedTransactions) of \(enhanceProgress.totalTransactions)"
case .fetching:
return "fetching UTXOs"
case .disconnected:
return "disconnected 💔"
case .stopped:
return "Stopped 🚫"
case .synced:
return "Synced 😎"
case .upToDate:
return "Up to Date 😎"
case .unprepared:
return "Unprepared 😅"
case .error(ZcashError.synchronizerDisconnected):
return "disconnected 💔"
case .error(let error):
return "Error: \(error)"
}

View File

@ -64,7 +64,7 @@ class SyncBlocksListViewController: UIViewController {
loggerProxy.debug("Processing synchronizer with alias \(synchronizer.alias.description) \(index)")
switch syncStatus {
case .stopped, .unprepared, .synced, .disconnected, .error:
case .unprepared, .upToDate, .error(ZcashError.synchronizerDisconnected), .error:
do {
if syncStatus == .unprepared {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
@ -82,7 +82,7 @@ class SyncBlocksListViewController: UIViewController {
} catch {
loggerProxy.error("Can't start synchronizer: \(error)")
}
case .syncing, .enhancing, .fetching:
case .syncing:
synchronizer.stop()
}
}
@ -147,9 +147,9 @@ extension SyncBlocksListViewController: UITableViewDataSource {
let image: UIImage?
switch synchronizerStatus {
case .unprepared, .synced, .stopped, .disconnected, .error:
case .unprepared, .upToDate, .error(ZcashError.synchronizerDisconnected), .error:
image = UIImage(systemName: "play.circle")
case .syncing, .enhancing, .fetching:
case .syncing:
image = UIImage(systemName: "stop.circle")
}
@ -173,21 +173,15 @@ extension SyncStatus {
var text: String {
switch self {
case let .syncing(progress):
return "Syncing 🤖 \(floor(progress.progress * 1000) / 10)%"
case let .error(error):
return "error 💔 \(error)"
case .stopped:
return "Stopped 🚫"
case .synced:
return "Synced 😎"
case let .enhancing(progress):
return "Enhancing 🤖 \(floor(progress.progress * 1000) / 10)%"
case .fetching:
return "Fetching UTXOs"
return "Syncing 🤖 \(floor(progress * 1000) / 10)%"
case .upToDate:
return "Up to Date 😎"
case .unprepared:
return "Unprepared"
case .disconnected:
case .error(ZcashError.synchronizerDisconnected):
return "Disconnected"
case let .error(error):
return "error 💔 \(error.localizedDescription)"
}
}
}

View File

@ -82,8 +82,8 @@ class SyncBlocksViewController: UIViewController {
case let .syncing(progress):
enhancingStarted = false
progressBar.progress = progress.progress
progressLabel.text = "\(floor(progress.progress * 1000) / 10)%"
progressBar.progress = progress
progressLabel.text = "\(floor(progress * 1000) / 10)%"
let syncedDate = dateFormatter.string(from: Date(timeIntervalSince1970: state.latestScannedTime))
let progressText = """
synced date \(syncedDate)
@ -97,27 +97,11 @@ class SyncBlocksViewController: UIViewController {
metricLabel.text = currentMetricName + report.debugDescription
}
case .enhancing:
guard !enhancingStarted else { return }
enhancingStarted = true
accumulateMetrics()
summaryLabel.text = "scan: \(accumulatedMetrics.debugDescription)"
accumulatedMetrics = .initial
currentMetric = .enhancement
case .fetching:
break
case .synced:
case .upToDate:
accumulateMetrics()
summaryLabel.text = "enhancement: \(accumulatedMetrics.debugDescription)"
overallSummary()
case .stopped:
break
case .disconnected:
break
case .error:
break
}
@ -163,7 +147,7 @@ class SyncBlocksViewController: UIViewController {
func doStartStop() async {
let syncStatus = synchronizer.latestState.syncStatus
switch syncStatus {
case .stopped, .unprepared, .error:
case .unprepared, .error:
do {
if syncStatus == .unprepared {
// swiftlint:disable:next force_try
@ -212,7 +196,7 @@ class SyncBlocksViewController: UIViewController {
statusLabel.text = textFor(state: syncStatus)
startPause.setTitle(buttonText(for: syncStatus), for: .normal)
if case SyncStatus.synced = syncStatus {
if case SyncStatus.upToDate = syncStatus {
startPause.isEnabled = false
} else {
startPause.isEnabled = true
@ -223,16 +207,12 @@ class SyncBlocksViewController: UIViewController {
switch state {
case .syncing:
return "Pause"
case .stopped, .unprepared:
case .unprepared:
return "Start"
case .error, .disconnected:
return "Retry"
case .synced:
case .upToDate:
return "Chill!"
case .enhancing:
return "Enhance"
case .fetching:
return "fetch"
case .error:
return "Retry"
}
}
@ -240,20 +220,14 @@ class SyncBlocksViewController: UIViewController {
switch state {
case .syncing:
return "Syncing 🤖"
case .error:
return "error 💔"
case .stopped:
return "Stopped 🚫"
case .synced:
return "Synced 😎"
case .enhancing:
return "Enhancing 🤖"
case .fetching:
return "Fetching UTXOs"
case .upToDate:
return "Up to Date 😎"
case .unprepared:
return "Unprepared"
case .disconnected:
case .error(ZcashError.synchronizerDisconnected):
return "Disconnected"
case .error:
return "error 💔"
}
}
}

View File

@ -15,19 +15,19 @@ public typealias RefreshedUTXOs = (inserted: [UnspentTransactionOutputEntity], s
public enum CompactBlockProgress {
case syncing(_ progress: BlockProgress)
case enhance(_ progress: EnhancementProgress)
case fetch
case fetch(_ progress: Float)
public var progress: Float {
switch self {
case .syncing(let blockProgress):
return blockProgress.progress
case .enhance(let enhancementProgress):
return enhancementProgress.progress
default:
return 0
case .fetch(let fetchingProgress):
return fetchingProgress
}
}
public var progressHeight: BlockHeight? {
switch self {
case .syncing(let blockProgress):
@ -38,15 +38,15 @@ public enum CompactBlockProgress {
return 0
}
}
public var blockDate: Date? {
if case .enhance(let enhancementProgress) = self, let time = enhancementProgress.lastFoundTransaction?.blockTime {
return Date(timeIntervalSince1970: time)
}
return nil
}
public var targetHeight: BlockHeight? {
switch self {
case .syncing(let blockProgress):
@ -682,7 +682,9 @@ actor CompactBlockProcessor {
anyActionExecuted = true
logger.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
await updateState(.fetching)
let result = try await utxoFetcher.fetch(at: range)
let result = try await utxoFetcher.fetch(at: range) { [weak self] progress in
await self?.notifyProgress(.fetch(progress))
}
await send(event: .storedUTXOs(result))
}

View File

@ -17,7 +17,7 @@ struct UTXOFetcherConfig {
}
protocol UTXOFetcher {
func fetch(at range: CompactBlockRange) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
func fetch(at range: CompactBlockRange, didFetch: (Float) async -> ()) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
}
struct UTXOFetcherImpl {
@ -31,7 +31,7 @@ struct UTXOFetcherImpl {
}
extension UTXOFetcherImpl: UTXOFetcher {
func fetch(at range: CompactBlockRange) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
func fetch(at range: CompactBlockRange, didFetch: (Float) async -> ()) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
try Task.checkCancellation()
let accounts = try accountRepository.getAll()
@ -60,6 +60,8 @@ extension UTXOFetcherImpl: UTXOFetcher {
var skipped: [UnspentTransactionOutputEntity] = []
let startTime = Date()
let all = Float(utxos.count)
var counter = Float(0)
for utxo in utxos {
do {
try await rustBackend.putUnspentTransparentOutput(
@ -72,6 +74,8 @@ extension UTXOFetcherImpl: UTXOFetcher {
refreshed.append(utxo)
counter += 1
await didFetch(counter / all)
await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight)
} catch {
logger.error("failed to put utxo - error: \(error)")

View File

@ -502,6 +502,9 @@ public enum ZcashError: Equatable, Error {
/// Rewind failed, unknown archor height
/// ZSYNCO0005
case synchronizerRewindUnknownArchorHeight
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
/// ZSYNCO0006
case synchronizerDisconnected
public var message: String {
switch self {
@ -650,6 +653,7 @@ public enum ZcashError: Equatable, Error {
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."
}
}
@ -800,6 +804,7 @@ public enum ZcashError: Equatable, Error {
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds
case .synchronizerLatestUTXOsInvalidTAddress: return .synchronizerLatestUTXOsInvalidTAddress
case .synchronizerRewindUnknownArchorHeight: return .synchronizerRewindUnknownArchorHeight
case .synchronizerDisconnected: return .synchronizerDisconnected
}
}

View File

@ -299,4 +299,6 @@ public enum ZcashErrorCode: String {
case synchronizerLatestUTXOsInvalidTAddress = "ZSYNCO0004"
/// Rewind failed, unknown archor height
case synchronizerRewindUnknownArchorHeight = "ZSYNCO0005"
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
case synchronizerDisconnected = "ZSYNCO0006"
}

View File

@ -584,4 +584,7 @@ enum ZcashErrorDefinition {
/// Rewind failed, unknown archor height
// sourcery: code="ZSYNCO0005"
case synchronizerRewindUnknownArchorHeight
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
// sourcery: code="ZSYNCO0006"
case synchronizerDisconnected
}

View File

@ -23,7 +23,7 @@ protocol LatestBlocksDataProvider {
actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
let service: LightWalletService
let transactionRepository: TransactionRepository
// Valid values are stored here after Synchronizer's `prepare` is called.
private(set) var latestScannedHeight: BlockHeight = .zero
private(set) var latestScannedTime: TimeInterval = 0.0

View File

@ -26,6 +26,7 @@ public enum ConnectionState {
/// the connection has been closed
case shutdown
}
/// Reports the state of a synchronizer.
public struct SynchronizerState: Equatable {
/// Unique Identifier for the current sync attempt
@ -39,6 +40,7 @@ public struct SynchronizerState: Equatable {
/// transparent balance known to this synchronizer given the data that has processed locally
public var transparentBalance: WalletBalance
/// status of the whole sync process
var internalSyncStatus: InternalSyncStatus
public var syncStatus: SyncStatus
/// height of the latest scanned block known to this synchronizer.
public var latestScannedHeight: BlockHeight
@ -54,12 +56,31 @@ public struct SynchronizerState: Equatable {
syncSessionID: .nullID,
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .unprepared,
internalSyncStatus: .unprepared,
latestScannedHeight: .zero,
latestBlockHeight: .zero,
latestScannedTime: 0
)
}
init(
syncSessionID: UUID,
shieldedBalance: WalletBalance,
transparentBalance: WalletBalance,
internalSyncStatus: InternalSyncStatus,
latestScannedHeight: BlockHeight,
latestBlockHeight: BlockHeight,
latestScannedTime: TimeInterval
) {
self.syncSessionID = syncSessionID
self.shieldedBalance = shieldedBalance
self.transparentBalance = transparentBalance
self.internalSyncStatus = internalSyncStatus
self.latestScannedHeight = latestScannedHeight
self.latestBlockHeight = latestBlockHeight
self.latestScannedTime = latestScannedTime
self.syncStatus = internalSyncStatus.mapToSyncStatus()
}
}
public enum SynchronizerEvent {
@ -86,9 +107,9 @@ public protocol Synchronizer: AnyObject {
var connectionState: ConnectionState { get }
/// This stream is backed by `CurrentValueSubject`. This is primary source of information about what is the SDK doing. New values are emitted when
/// `SyncStatus` is changed inside the SDK.
/// `InternalSyncStatus` is changed inside the SDK.
///
/// Synchronization progress is part of the `SyncStatus` so this stream emits lot of values. `throttle` can be used to control amout of values
/// Synchronization progress is part of the `InternalSyncStatus` so this stream emits lot of values. `throttle` can be used to control amout of values
/// delivered. Values are delivered on random background thread.
var stateStream: AnyPublisher<SynchronizerState, Never> { get }
@ -290,6 +311,55 @@ public protocol Synchronizer: AnyObject {
}
public enum SyncStatus: Equatable {
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
switch (lhs, rhs) {
case (.unprepared, .unprepared): return true
case let (.syncing(lhsProgress), .syncing(rhsProgress)): return lhsProgress == rhsProgress
case (.upToDate, .upToDate): return true
case (.error, .error): return true
default: return false
}
}
/// Indicates that this Synchronizer is actively preparing to start,
/// which usually involves setting up database tables, migrations or
/// taking other maintenance steps that need to occur after an upgrade.
case unprepared
case syncing(_ progress: Float)
/// Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
/// When set, a UI element may want to turn green.
case upToDate
case error(_ error: Error)
public var isSyncing: Bool {
guard case .syncing = self else { return true }
return false
}
public var isSynced: Bool {
guard case .upToDate = self else { return true }
return false
}
public var isPrepared: Bool {
guard case .unprepared = self else { return false }
return true
}
public var briefDebugDescription: String {
switch self {
case .unprepared: return "unprepared"
case .syncing: return "syncing"
case .upToDate: return "up to date"
case .error: return "error"
}
}
}
enum InternalSyncStatus: Equatable {
/// Indicates that this Synchronizer is actively preparing to start,
/// which usually involves setting up database tables, migrations or
/// taking other maintenance steps that need to occur after an upgrade.
@ -302,7 +372,7 @@ public enum SyncStatus: Equatable {
case enhancing(_ progress: EnhancementProgress)
/// fetches the transparent balance and stores it locally
case fetching
case fetching(_ progress: Float)
/// Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
/// When set, a UI element may want to turn green.
@ -373,8 +443,8 @@ public enum RewindPolicy {
case quick
}
extension SyncStatus {
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
extension InternalSyncStatus {
public static func == (lhs: InternalSyncStatus, rhs: InternalSyncStatus) -> Bool {
switch (lhs, rhs) {
case (.unprepared, .unprepared): return true
case let (.syncing(lhsProgress), .syncing(rhsProgress)): return lhsProgress == rhsProgress
@ -389,15 +459,38 @@ extension SyncStatus {
}
}
extension SyncStatus {
extension InternalSyncStatus {
init(_ blockProcessorProgress: CompactBlockProgress) {
switch blockProcessorProgress {
case .syncing(let progressReport):
self = .syncing(progressReport)
case .enhance(let enhancingReport):
self = .enhancing(enhancingReport)
case .fetch:
self = .fetching
case .fetch(let fetchingProgress):
self = .fetching(fetchingProgress)
}
}
}
extension InternalSyncStatus {
func mapToSyncStatus() -> SyncStatus {
switch self {
case .unprepared:
return .unprepared
case .syncing(let progress):
return .syncing(0.9 * progress.progress)
case .enhancing(let progress):
return .syncing(0.9 + 0.08 * progress.progress)
case .fetching(let progress):
return .syncing(0.98 + 0.02 * progress)
case .synced:
return .upToDate
case .stopped:
return .upToDate
case .disconnected:
return .error(ZcashError.synchronizerDisconnected)
case .error(let error):
return .error(error)
}
}
}

View File

@ -26,8 +26,8 @@ public class SDKSynchronizer: Synchronizer {
public let logger: Logger
// Don't read this variable directly. Use `status` instead. And don't update this variable directly use `updateStatus()` methods instead.
private var underlyingStatus: GenericActor<SyncStatus>
var status: SyncStatus {
private var underlyingStatus: GenericActor<InternalSyncStatus>
var status: InternalSyncStatus {
get async { await underlyingStatus.value }
}
@ -65,7 +65,7 @@ public class SDKSynchronizer: Synchronizer {
}
init(
status: SyncStatus,
status: InternalSyncStatus,
initializer: Initializer,
transactionEncoder: TransactionEncoder,
transactionRepository: TransactionRepository,
@ -102,13 +102,13 @@ public class SDKSynchronizer: Synchronizer {
}
}
func updateStatus(_ newValue: SyncStatus) async {
func updateStatus(_ newValue: InternalSyncStatus) async {
let oldValue = await underlyingStatus.update(newValue)
await notify(oldStatus: oldValue, newStatus: newValue)
}
func throwIfUnprepared() throws {
if !latestState.syncStatus.isPrepared {
if !latestState.internalSyncStatus.isPrepared {
throw ZcashError.synchronizerNotPrepared
}
}
@ -223,7 +223,7 @@ public class SDKSynchronizer: Synchronizer {
await self?.updateStatus(.enhancing(.zero))
case .startedFetching:
await self?.updateStatus(.fetching)
await self?.updateStatus(.fetching(0))
case .startedSyncing:
await self?.updateStatus(.syncing(.nullProgress))
@ -260,7 +260,7 @@ public class SDKSynchronizer: Synchronizer {
}
private func progressUpdated(progress: CompactBlockProgress) async {
let newStatus = SyncStatus(progress)
let newStatus = InternalSyncStatus(progress)
await updateStatus(newStatus)
}
@ -461,7 +461,7 @@ public class SDKSynchronizer: Synchronizer {
public func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error> {
let subject = PassthroughSubject<Void, Error>()
Task(priority: .high) {
if !latestState.syncStatus.isPrepared {
if !latestState.internalSyncStatus.isPrepared {
subject.send(completion: .failure(ZcashError.synchronizerNotPrepared))
return
}
@ -537,7 +537,7 @@ public class SDKSynchronizer: Synchronizer {
// MARK: notify state
private func snapshotState(status: SyncStatus) async -> SynchronizerState {
private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState {
return await SynchronizerState(
syncSessionID: syncSession.value,
shieldedBalance: WalletBalance(
@ -545,14 +545,14 @@ public class SDKSynchronizer: Synchronizer {
total: (try? await getShieldedBalance()) ?? .zero
),
transparentBalance: (try? await blockProcessor.getTransparentBalance(accountIndex: 0)) ?? .zero,
syncStatus: status,
internalSyncStatus: status,
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
latestScannedTime: latestBlocksDataProvider.latestScannedTime
)
}
private func notify(oldStatus: SyncStatus, newStatus: SyncStatus) async {
private func notify(oldStatus: InternalSyncStatus, newStatus: InternalSyncStatus) async {
guard oldStatus != newStatus else { return }
let newState: SynchronizerState
@ -617,8 +617,8 @@ extension SDKSynchronizer {
}
}
extension SyncStatus {
func isDifferent(from otherStatus: SyncStatus) -> Bool {
extension InternalSyncStatus {
func isDifferent(from otherStatus: InternalSyncStatus) -> Bool {
switch (self, otherStatus) {
case (.unprepared, .unprepared): return false
case (.syncing, .syncing): return false
@ -636,7 +636,7 @@ extension SyncStatus {
struct SessionTicker {
/// Helper function to determine whether we are in front of a SyncSession change for a given syncStatus
/// transition we consider that every sync attempt is a new sync session and should have it's unique UUID reported.
var isNewSyncSession: (SyncStatus, SyncStatus) -> Bool
var isNewSyncSession: (InternalSyncStatus, InternalSyncStatus) -> Bool
}
extension SessionTicker {

View File

@ -22,7 +22,7 @@ final class InternalStateConsistencyTests: ZcashTestCase {
let branchID = "2bb40e60"
let chainName = "main"
let network = DarksideWalletDNetwork()
var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler()
var sdkSynchronizerInternalSyncStatusHandler: SDKSynchronizerInternalSyncStatusHandler! = SDKSynchronizerInternalSyncStatusHandler()
override func setUp() async throws {
try await super.setUp()
@ -41,7 +41,7 @@ final class InternalStateConsistencyTests: ZcashTestCase {
try await super.tearDown()
let coordinator = self.coordinator!
self.coordinator = nil
sdkSynchronizerSyncStatusHandler = nil
sdkSynchronizerInternalSyncStatusHandler = nil
try await coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
@ -49,7 +49,7 @@ final class InternalStateConsistencyTests: ZcashTestCase {
}
func testInternalStateIsConsistentWhenMigrating() async throws {
sdkSynchronizerSyncStatusHandler.subscribe(
sdkSynchronizerInternalSyncStatusHandler.subscribe(
to: coordinator.synchronizer.stateStream,
expectations: [.stopped: firstSyncExpectation]
)

View File

@ -186,7 +186,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: .nullID,
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .disconnected,
internalSyncStatus: .disconnected,
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833
@ -195,7 +195,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833
@ -204,7 +204,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -213,7 +213,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
internalSyncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -222,7 +222,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .enhancing(
internalSyncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
enhancedTransactions: 1,
@ -254,7 +254,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .enhancing(
internalSyncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
enhancedTransactions: 2,
@ -286,7 +286,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .fetching,
internalSyncStatus: .fetching(0),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -295,7 +295,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .synced,
internalSyncStatus: .synced,
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -346,7 +346,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: .nullID,
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .disconnected,
internalSyncStatus: .disconnected,
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833.0
@ -355,7 +355,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833.0
@ -364,7 +364,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -373,7 +373,9 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
internalSyncStatus: .enhancing(
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)
),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -382,7 +384,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(
internalSyncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
enhancedTransactions: 1,
@ -414,7 +416,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(
internalSyncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
enhancedTransactions: 2,
@ -446,7 +448,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching,
internalSyncStatus: .fetching(0),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -455,7 +457,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced,
internalSyncStatus: .synced,
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
@ -491,7 +493,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1.0
@ -500,7 +502,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)),
internalSyncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)),
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
@ -509,7 +511,9 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
internalSyncStatus: .enhancing(
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)
),
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
@ -518,7 +522,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching,
internalSyncStatus: .fetching(0),
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
@ -527,7 +531,7 @@ class SynchronizerDarksideTests: ZcashTestCase {
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced,
internalSyncStatus: .synced,
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1

View File

@ -22,7 +22,7 @@ final class SynchronizerTests: ZcashTestCase {
let chainName = "main"
let network = DarksideWalletDNetwork()
var cancellables: [AnyCancellable] = []
var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler()
var sdkSynchronizerInternalSyncStatusHandler: SDKSynchronizerInternalSyncStatusHandler! = SDKSynchronizerInternalSyncStatusHandler()
override func setUp() async throws {
try await super.setUp()
@ -49,7 +49,7 @@ final class SynchronizerTests: ZcashTestCase {
try await super.tearDown()
let coordinator = self.coordinator!
self.coordinator = nil
sdkSynchronizerSyncStatusHandler = nil
sdkSynchronizerInternalSyncStatusHandler = nil
cancellables = []
try await coordinator.stop()
@ -79,7 +79,7 @@ final class SynchronizerTests: ZcashTestCase {
sleep(10)
let syncStoppedExpectation = XCTestExpectation(description: "SynchronizerStopped Expectation")
sdkSynchronizerSyncStatusHandler.subscribe(
sdkSynchronizerInternalSyncStatusHandler.subscribe(
to: coordinator.synchronizer.stateStream,
expectations: [.stopped: syncStoppedExpectation]
)

View File

@ -376,7 +376,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
func testIsNotNewSessionOnUnpreparedToInvalidOrUnexpectedTransitions() {
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .synced))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .fetching))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .fetching(0)))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .enhancing(.zero)))
}
@ -426,12 +426,135 @@ class SynchronizerOfflineTests: ZcashTestCase {
)
}
func testSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
XCTAssertFalse(SyncStatus.disconnected.isDifferent(from: .disconnected))
XCTAssertFalse(SyncStatus.fetching.isDifferent(from: .fetching))
XCTAssertFalse(SyncStatus.stopped.isDifferent(from: .stopped))
XCTAssertFalse(SyncStatus.synced.isDifferent(from: .synced))
XCTAssertFalse(SyncStatus.syncing(.nullProgress).isDifferent(from: .syncing(.nullProgress)))
XCTAssertFalse(SyncStatus.unprepared.isDifferent(from: .unprepared))
func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
XCTAssertFalse(InternalSyncStatus.disconnected.isDifferent(from: .disconnected))
XCTAssertFalse(InternalSyncStatus.fetching(0).isDifferent(from: .fetching(0)))
XCTAssertFalse(InternalSyncStatus.stopped.isDifferent(from: .stopped))
XCTAssertFalse(InternalSyncStatus.synced.isDifferent(from: .synced))
XCTAssertFalse(InternalSyncStatus.syncing(.nullProgress).isDifferent(from: .syncing(.nullProgress)))
XCTAssertFalse(InternalSyncStatus.unprepared.isDifferent(from: .unprepared))
}
func testInternalSyncStatusMap_SyncingLowerBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 0))
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.0, data) {
XCTFail("Syncing is expected to be 0% (0.0) but received \(data).")
}
}
func testInternalSyncStatusMap_SyncingInTheMiddle() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 50))
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.45, data) {
XCTFail("Syncing is expected to be 45% (0.45) but received \(data).")
}
}
func testInternalSyncStatusMap_SyncingUpperBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(BlockProgress(startHeight: 0, targetHeight: 100, progressHeight: 100))
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.9, data) {
XCTFail("Syncing is expected to be 90% (0.9) but received \(data).")
}
}
func testInternalSyncStatusMap_EnhancingLowerBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.enhancing(
EnhancementProgress(
totalTransactions: 100,
enhancedTransactions: 0,
lastFoundTransaction: nil,
range: CompactBlockRange(uncheckedBounds: (0, 100))
)
)
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.9, data) {
XCTFail("Syncing is expected to be 90% (0.9) but received \(data).")
}
}
func testInternalSyncStatusMap_EnhancingInTheMiddle() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.enhancing(
EnhancementProgress(
totalTransactions: 100,
enhancedTransactions: 50,
lastFoundTransaction: nil,
range: CompactBlockRange(uncheckedBounds: (0, 100))
)
)
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.94, data) {
XCTFail("Syncing is expected to be 94% (0.94) but received \(data).")
}
}
func testInternalSyncStatusMap_EnhancingUpperBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.enhancing(
EnhancementProgress(
totalTransactions: 100,
enhancedTransactions: 100,
lastFoundTransaction: nil,
range: CompactBlockRange(uncheckedBounds: (0, 100))
)
)
)
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.98, data) {
XCTFail("Syncing is expected to be 98% (0.98) but received \(data).")
}
}
func testInternalSyncStatusMap_FetchingLowerBound() {
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(0))
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.98, data) {
XCTFail("Syncing is expected to be 98% (0.98) but received \(data).")
}
}
func testInternalSyncStatusMap_FetchingInTheMiddle() {
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(0.5))
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(0.99, data) {
XCTFail("Syncing is expected to be 99% (0.99) but received \(data).")
}
}
func testInternalSyncStatusMap_FetchingUpperBound() {
let synchronizerState = synchronizerState(for: InternalSyncStatus.fetching(1))
if case let .syncing(data) = synchronizerState.syncStatus, data != nextafter(1.0, data) {
XCTFail("Syncing is expected to be 100% (1.0) but received \(data).")
}
}
func synchronizerState(for internalSyncStatus: InternalSyncStatus) -> SynchronizerState {
SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: internalSyncStatus,
latestScannedHeight: .zero,
latestBlockHeight: .zero,
latestScannedTime: 0
)
}
}

View File

@ -25,7 +25,7 @@ class SynchronizerTests: XCTestCase {
var coordinator: TestCoordinator!
var cancellables: [AnyCancellable] = []
var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler()
var sdkSynchronizerInternalSyncStatusHandler: SDKSynchronizerInternalSyncStatusHandler! = SDKSynchronizerInternalSyncStatusHandler()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
@ -45,7 +45,7 @@ class SynchronizerTests: XCTestCase {
super.tearDown()
coordinator = nil
cancellables = []
sdkSynchronizerSyncStatusHandler = nil
sdkSynchronizerInternalSyncStatusHandler = nil
rustBackend = nil
testTempDirectory = nil
}
@ -92,7 +92,7 @@ class SynchronizerTests: XCTestCase {
_ = try await synchronizer.prepare(with: seedBytes, viewingKeys: [ufvk], walletBirthday: birthday)
let syncSyncedExpectation = XCTestExpectation(description: "synchronizerSynced Expectation")
sdkSynchronizerSyncStatusHandler.subscribe(to: synchronizer.stateStream, expectations: [.synced: syncSyncedExpectation])
sdkSynchronizerInternalSyncStatusHandler.subscribe(to: synchronizer.stateStream, expectations: [.synced: syncSyncedExpectation])
let internalSyncProgress = InternalSyncProgress(
alias: .default,

View File

@ -10,7 +10,7 @@ import Foundation
import XCTest
@testable import ZcashLightClientKit
class SDKSynchronizerSyncStatusHandler {
class SDKSynchronizerInternalSyncStatusHandler {
enum StatusIdentifier: String {
case unprepared
case syncing
@ -22,20 +22,20 @@ class SDKSynchronizerSyncStatusHandler {
case error
}
private let queue = DispatchQueue(label: "SDKSynchronizerSyncStatusHandler")
private let queue = DispatchQueue(label: "SDKSynchronizerInternalSyncStatusHandler")
private var cancellables: [AnyCancellable] = []
func subscribe(to stateStream: AnyPublisher<SynchronizerState, Never>, expectations: [StatusIdentifier: XCTestExpectation]) {
stateStream
.receive(on: queue)
.map { $0.syncStatus }
.map { $0.internalSyncStatus }
.sink { status in expectations[status.identifier]?.fulfill() }
.store(in: &cancellables)
}
}
extension SyncStatus {
var identifier: SDKSynchronizerSyncStatusHandler.StatusIdentifier {
extension InternalSyncStatus {
var identifier: SDKSynchronizerInternalSyncStatusHandler.StatusIdentifier {
switch self {
case .unprepared: return .unprepared
case .syncing: return .syncing

View File

@ -209,7 +209,7 @@ extension SynchronizerState {
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
internalSyncStatus: .fetching(0),
latestScannedHeight: 111111,
latestBlockHeight: 222222,
latestScannedTime: 12345678

View File

@ -147,7 +147,7 @@ class TestCoordinator {
synchronizer.stateStream
.sink(
receiveValue: { [weak self] state in
switch state.syncStatus {
switch state.internalSyncStatus {
case let .error(error):
self?.synchronizerFailed(error: error)
case .synced:
@ -167,7 +167,7 @@ class TestCoordinator {
}
func synchronizerSynced() throws {
if case .stopped = self.synchronizer.latestState.syncStatus {
if case .stopped = self.synchronizer.latestState.internalSyncStatus {
LoggerProxy.debug("WARNING: notification received after synchronizer was stopped")
return
}