From e2956a190e24743249d8ed6a4efa85780fbea387 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Fri, 11 Apr 2025 14:36:34 +0200 Subject: [PATCH] Sync progress merged - the progress is merged now but boolean value whether funds are spendable or not has been added extra --- .../DemoAppConfig.swift | 8 +++- .../Send/SendViewController.swift | 4 +- .../SyncBlocksListViewController.swift | 4 +- .../SyncBlocksViewController.swift | 6 ++- .../Block/Actions/ScanAction.swift | 28 +++++++++++--- .../Block/CompactBlockProcessor.swift | 10 ++--- .../Block/Utils/CompactBlockProgress.swift | 10 ++--- .../ZcashLightClientKit/Synchronizer.swift | 12 +++--- .../Synchronizer/SDKSynchronizer.swift | 38 +++++++++++++++---- .../SynchronizerDarksideTests.swift | 18 ++++----- .../SynchronizerOfflineTests.swift | 30 +++++++-------- Tests/TestUtils/Stubs.swift | 2 +- 12 files changed, 109 insertions(+), 61 deletions(-) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift index 9add4e13..52c5beba 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift @@ -21,14 +21,18 @@ enum DemoAppConfig { static let host = ZcashSDK.isMainnet ? "zec.rocks" : "lightwalletd.testnet.electriccoin.co" static let port: Int = 443 - static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000 + static let defaultBirthdayHeight: BlockHeight = 2832500//ZcashSDK.isMainnet ? 935000 : 1386000 // static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """ // wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame +// """) + +// 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 // """) 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 + wreck craft number between hard warfare wisdom leave radar host local crane float play logic whale clap parade dynamic cotton attitude people guard together """) static let otherSynchronizers: [SynchronizerInitData] = [ diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift index 341dbb37..3e926ecc 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift @@ -319,8 +319,8 @@ extension SendViewController: UITextViewDelegate { extension SDKSynchronizer { static func textFor(state: SyncStatus) -> String { switch state { - case let .syncing(syncProgress, recoveryProgress): - return "Syncing \(syncProgress * 100.0)% \((recoveryProgress ?? 0) * 100.0)%" + case let .syncing(syncProgress, areFundsSpendable): + return "Syncing \(syncProgress * 100.0)% spendable: \(areFundsSpendable)" case .upToDate: return "Up to Date 😎" diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift index 8cf62422..a2348862 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift @@ -172,8 +172,8 @@ extension SyncBlocksListViewController: UITableViewDataSource { extension SyncStatus { var text: String { switch self { - case let .syncing(syncProgress, recoveryProgress): - return "Syncing 🤖 \(floor(syncProgress * 1000) / 10)% \(floor((recoveryProgress ?? 0) * 1000) / 10)%" + case let .syncing(syncProgress, areFundsSpendable): + return "Syncing 🤖 \(floor(syncProgress * 1000) / 10)% spendable: \(areFundsSpendable)" case .upToDate: return "Up to Date 😎" case .unprepared: diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift index d4ef0573..f9259468 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift @@ -68,11 +68,13 @@ class SyncBlocksViewController: UIViewController { case .unprepared: break - case let .syncing(syncProgress, recoveryProgress): + case let .syncing(syncProgress, areFundsSpendable): enhancingStarted = false + print("__LD syncProgress \(syncProgress) areFundsSpendable \(areFundsSpendable)") + progressBar.progress = syncProgress - progressLabel.text = "\(floor(syncProgress * 1000) / 10)% \(floor((recoveryProgress ?? 0) * 1000) / 10)%" + progressLabel.text = "\(floor(syncProgress * 1000) / 10)% spendable: \(areFundsSpendable)" let progressText = """ latest block height \(state.latestBlockHeight) """ diff --git a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift index 12f59d10..8d3810fb 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift @@ -74,14 +74,32 @@ extension ScanAction: Action { // TODO: [#1353] Advanced progress reporting, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1353 if progressReportReducer == 0 { let walletSummary = try? await rustBackend.getWalletSummary() - let recoveryProgress = try? walletSummary?.recoveryProgress?.progress() + let recoveryProgress = walletSummary?.recoveryProgress // report scan progress only if it's available if let scanProgress = walletSummary?.scanProgress { - logger.debug("progress ratio: \(scanProgress.numerator)/\(scanProgress.denominator)") - let progress = try scanProgress.progress() - logger.debug("progress float: \(progress) \(String(describing: recoveryProgress))") - await didUpdate(.syncProgress(progress, recoveryProgress)) + let composedNumerator: Float = Float(scanProgress.numerator) + Float(recoveryProgress?.numerator ?? 0) + let composedDenominator: Float = Float(scanProgress.denominator) + Float(recoveryProgress?.denominator ?? 0) + + logger.debug("progress ratio: \(composedNumerator)/\(composedDenominator)") + + let progress: Float + if composedDenominator == 0 { + progress = 1.0 + } else { + progress = composedNumerator / composedDenominator + } + + // this shouldn't happen but if it does, we need to get notified by clients and work on a fix + if progress > 1.0 { + throw ZcashError.rustScanProgressOutOfRange("\(progress)") + } + + let scanProgress: Float = (try? scanProgress.progress()) ?? 0.0 + let areFundsSpendable = scanProgress == 1.0 + + logger.debug("progress float: \(progress)") + await didUpdate(.syncProgress(progress, areFundsSpendable)) } progressReportReducer = Constants.reportDelay diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 217fe05f..90f9038c 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -483,10 +483,10 @@ extension CompactBlockProcessor { case handledReorg(_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight) /// Event sent when progress of some specific action happened. - case syncProgress(Float, Float?) + case syncProgress(Float, Bool) /// Event sent when progress of the sync process changes. - case progressUpdated(Float, Float?) + case progressUpdated(Float, Bool) /// Event sent when the CompactBlockProcessor fetched utxos from lightwalletd attempted to store them. case storedUTXOs((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])) @@ -568,9 +568,9 @@ extension CompactBlockProcessor { context = try await action.run(with: context) { [weak self] event in await self?.send(event: event) if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged { - if let progress = await self?.compactBlockProgress.syncProgress { + if let progress = await self?.compactBlockProgress.progress { await self?.send( - event: .progressUpdated(progress, self?.compactBlockProgress.recoveryProgress) + event: .progressUpdated(progress, self?.compactBlockProgress.areFundsSpendable ?? false) ) } } @@ -715,7 +715,7 @@ extension CompactBlockProcessor { let lastScannedHeight = await latestBlocksDataProvider.maxScannedHeight // Some actions may not run. For example there are no transactions to enhance and therefore there is no enhance progress. And in // cases like this computation of final progress won't work properly. So let's fake 100% progress at the end of the sync process. - await send(event: .progressUpdated(1, 1)) + await send(event: .progressUpdated(1, false)) await send(event: .finished(lastScannedHeight)) await context.update(state: .finished) diff --git a/Sources/ZcashLightClientKit/Block/Utils/CompactBlockProgress.swift b/Sources/ZcashLightClientKit/Block/Utils/CompactBlockProgress.swift index b6562298..10919848 100644 --- a/Sources/ZcashLightClientKit/Block/Utils/CompactBlockProgress.swift +++ b/Sources/ZcashLightClientKit/Block/Utils/CompactBlockProgress.swift @@ -10,16 +10,16 @@ import Foundation final actor CompactBlockProgress { static let zero = CompactBlockProgress() - var syncProgress: Float = 0.0 - var recoveryProgress: Float? + var progress: Float = 0.0 + var areFundsSpendable: Bool = false func hasProgressUpdated(_ event: CompactBlockProcessor.Event) -> Bool { - guard case let .syncProgress(syncProgress, recoveryProgress) = event else { + guard case let .syncProgress(progress, areFundsSpendable) = event else { return false } - self.syncProgress = syncProgress - self.recoveryProgress = recoveryProgress + self.progress = progress + self.areFundsSpendable = areFundsSpendable return true } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index eaf45df0..a778dfe6 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -449,7 +449,7 @@ public enum SyncStatus: Equatable { /// taking other maintenance steps that need to occur after an upgrade. case unprepared - case syncing(_ syncProgress: Float, _ recoveryProgress: Float?) + case syncing(_ syncProgress: Float, _ areFundsSpendable: Bool) /// 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. @@ -502,7 +502,7 @@ enum InternalSyncStatus: Equatable { case unprepared /// Indicates that this Synchronizer is actively processing new blocks (consists of fetch, scan and enhance operations) - case syncing(Float, Float?) + case syncing(Float, Bool) /// 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. @@ -610,8 +610,8 @@ extension InternalSyncStatus { } extension InternalSyncStatus { - init(_ syncProgress: Float, _ recoveryProgress: Float?) { - self = .syncing(syncProgress, recoveryProgress) + init(_ syncProgress: Float, _ areFundsSpendable: Bool) { + self = .syncing(syncProgress, areFundsSpendable) } } @@ -620,8 +620,8 @@ extension InternalSyncStatus { switch self { case .unprepared: return .unprepared - case let .syncing(syncProgress, recoveryProgress): - return .syncing(syncProgress, recoveryProgress) + case let .syncing(syncProgress, areFundsSpendable): + return .syncing(syncProgress, areFundsSpendable) case .synced: return .upToDate case .stopped: diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 712b3a47..3bf0d77b 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -174,9 +174,33 @@ public class SDKSynchronizer: Synchronizer { case .stopped, .synced, .disconnected, .error: let walletSummary = try? await initializer.rustBackend.getWalletSummary() - let recoveryProgress: Float? = try? walletSummary?.recoveryProgress?.progress() - let syncProgress = (try? walletSummary?.scanProgress?.progress()) ?? 0 - await updateStatus(.syncing(syncProgress, recoveryProgress)) + let recoveryProgress = walletSummary?.recoveryProgress + + var syncProgress: Float = 0.0 + var areFundsSpendable = false + + if let scanProgress = walletSummary?.scanProgress { + let composedNumerator: Float = Float(scanProgress.numerator) + Float(recoveryProgress?.numerator ?? 0) + let composedDenominator: Float = Float(scanProgress.denominator) + Float(recoveryProgress?.denominator ?? 0) + + let progress: Float + if composedDenominator == 0 { + progress = 1.0 + } else { + progress = composedNumerator / composedDenominator + } + + // this shouldn't happen but if it does, we need to get notified by clients and work on a fix + if progress > 1.0 { + throw ZcashError.rustScanProgressOutOfRange("\(progress)") + } + + let scanProgress: Float = (try? scanProgress.progress()) ?? 0.0 + areFundsSpendable = scanProgress == 1.0 + + syncProgress = progress + } + await updateStatus(.syncing(syncProgress, areFundsSpendable)) await blockProcessor.start(retry: retry) } } @@ -242,8 +266,8 @@ public class SDKSynchronizer: Synchronizer { // log reorg information self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)") - case let .progressUpdated(syncProgress, recoveryProgress): - await self?.progressUpdated(syncProgress, recoveryProgress) + case let .progressUpdated(syncProgress, areFundsSpendable): + await self?.progressUpdated(syncProgress, areFundsSpendable) case .syncProgress: break @@ -283,8 +307,8 @@ public class SDKSynchronizer: Synchronizer { } } - private func progressUpdated(_ syncProgress: Float, _ recoveryProgress: Float?) async { - let newStatus = InternalSyncStatus(syncProgress, recoveryProgress) + private func progressUpdated(_ syncProgress: Float, _ areFundsSpendable: Bool) async { + let newStatus = InternalSyncStatus(syncProgress, areFundsSpendable) await updateStatus(newStatus) } diff --git a/Tests/DarksideTests/SynchronizerDarksideTests.swift b/Tests/DarksideTests/SynchronizerDarksideTests.swift index 10921e47..e87b0c81 100644 --- a/Tests/DarksideTests/SynchronizerDarksideTests.swift +++ b/Tests/DarksideTests/SynchronizerDarksideTests.swift @@ -197,19 +197,19 @@ class SynchronizerDarksideTests: ZcashTestCase { SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(0, 0), + internalSyncStatus: .syncing(0, false), latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(0.9, 0), + internalSyncStatus: .syncing(0.9, false), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(1.0, 0), + internalSyncStatus: .syncing(1.0, false), latestBlockHeight: 663189 ), SynchronizerState( @@ -269,19 +269,19 @@ class SynchronizerDarksideTests: ZcashTestCase { SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(0, 0), + internalSyncStatus: .syncing(0, false), latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(0.9, 0), + internalSyncStatus: .syncing(0.9, false), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], accountsBalances: [:], - internalSyncStatus: .syncing(1.0, 0), + internalSyncStatus: .syncing(1.0, false), latestBlockHeight: 663189 ), SynchronizerState( @@ -320,19 +320,19 @@ class SynchronizerDarksideTests: ZcashTestCase { SynchronizerState( syncSessionID: uuids[1], accountsBalances: [:], - internalSyncStatus: .syncing(0, 0), + internalSyncStatus: .syncing(0, false), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[1], accountsBalances: [:], - internalSyncStatus: .syncing(0.9, 0), + internalSyncStatus: .syncing(0.9, false), latestBlockHeight: 663200 ), SynchronizerState( syncSessionID: uuids[1], accountsBalances: [:], - internalSyncStatus: .syncing(1.0, 0), + internalSyncStatus: .syncing(1.0, false), latestBlockHeight: 663200 ), SynchronizerState( diff --git a/Tests/OfflineTests/SynchronizerOfflineTests.swift b/Tests/OfflineTests/SynchronizerOfflineTests.swift index 72dd2698..4569a0f9 100644 --- a/Tests/OfflineTests/SynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/SynchronizerOfflineTests.swift @@ -333,7 +333,7 @@ class SynchronizerOfflineTests: ZcashTestCase { } func testIsNewSessionOnUnpreparedToValidTransition() { - XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0, 0))) + XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0, false))) } func testIsNotNewSessionOnUnpreparedToStateThatWontSync() { @@ -348,8 +348,8 @@ class SynchronizerOfflineTests: ZcashTestCase { func testIsNotNewSyncSessionOnSameSession() { XCTAssertFalse( SessionTicker.live.isNewSyncSession( - .syncing(0.5, 0), - .syncing(0.6, 0) + .syncing(0.5, false), + .syncing(0.6, false) ) ) } @@ -358,7 +358,7 @@ class SynchronizerOfflineTests: ZcashTestCase { XCTAssertTrue( SessionTicker.live.isNewSyncSession( .synced, - .syncing(0.6, 0) + .syncing(0.6, false) ) ) } @@ -367,7 +367,7 @@ class SynchronizerOfflineTests: ZcashTestCase { XCTAssertTrue( SessionTicker.live.isNewSyncSession( .disconnected, - .syncing(0.6, 0) + .syncing(0.6, false) ) ) } @@ -376,14 +376,14 @@ class SynchronizerOfflineTests: ZcashTestCase { XCTAssertTrue( SessionTicker.live.isNewSyncSession( .stopped, - .syncing(0.6, 0) + .syncing(0.6, false) ) ) } func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() { XCTAssertFalse(InternalSyncStatus.disconnected.isDifferent(from: .disconnected)) - XCTAssertFalse(InternalSyncStatus.syncing(0, 0).isDifferent(from: .syncing(0, 0))) + XCTAssertFalse(InternalSyncStatus.syncing(0, false).isDifferent(from: .syncing(0, false))) XCTAssertFalse(InternalSyncStatus.stopped.isDifferent(from: .stopped)) XCTAssertFalse(InternalSyncStatus.synced.isDifferent(from: .synced)) XCTAssertFalse(InternalSyncStatus.unprepared.isDifferent(from: .unprepared)) @@ -392,10 +392,10 @@ class SynchronizerOfflineTests: ZcashTestCase { func testInternalSyncStatusMap_SyncingLowerBound() { let synchronizerState = synchronizerState( for: - InternalSyncStatus.syncing(0, 0) + InternalSyncStatus.syncing(0, false) ) - if case let .syncing(data, 0) = synchronizerState.syncStatus, data != nextafter(0.0, data) { + if case let .syncing(data, false) = synchronizerState.syncStatus, data != nextafter(0.0, data) { XCTFail("Syncing is expected to be 0% (0.0) but received \(data).") } } @@ -403,10 +403,10 @@ class SynchronizerOfflineTests: ZcashTestCase { func testInternalSyncStatusMap_SyncingInTheMiddle() { let synchronizerState = synchronizerState( for: - InternalSyncStatus.syncing(0.45, 0) + InternalSyncStatus.syncing(0.45, false) ) - if case let .syncing(data, 0) = synchronizerState.syncStatus, data != nextafter(0.45, data) { + if case let .syncing(data, false) = synchronizerState.syncStatus, data != nextafter(0.45, data) { XCTFail("Syncing is expected to be 45% (0.45) but received \(data).") } } @@ -414,18 +414,18 @@ class SynchronizerOfflineTests: ZcashTestCase { func testInternalSyncStatusMap_SyncingUpperBound() { let synchronizerState = synchronizerState( for: - InternalSyncStatus.syncing(0.9, 0) + InternalSyncStatus.syncing(0.9, false) ) - if case let .syncing(data, 0) = synchronizerState.syncStatus, data != nextafter(0.9, data) { + if case let .syncing(data, false) = synchronizerState.syncStatus, data != nextafter(0.9, data) { XCTFail("Syncing is expected to be 90% (0.9) but received \(data).") } } func testInternalSyncStatusMap_FetchingUpperBound() { - let synchronizerState = synchronizerState(for: InternalSyncStatus.syncing(1, 0)) + let synchronizerState = synchronizerState(for: InternalSyncStatus.syncing(1, false)) - if case let .syncing(data, 0) = synchronizerState.syncStatus, data != nextafter(1.0, data) { + if case let .syncing(data, false) = synchronizerState.syncStatus, data != nextafter(1.0, data) { XCTFail("Syncing is expected to be 100% (1.0) but received \(data).") } } diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 176dab9c..cb2ad6af 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -146,7 +146,7 @@ extension SynchronizerState { SynchronizerState( syncSessionID: .nullID, accountsBalances: [:], - internalSyncStatus: .syncing(0, 0), + internalSyncStatus: .syncing(0, false), latestBlockHeight: 222222 ) }