Sync progress merged

- the progress is merged now but boolean value whether funds are spendable or not has been added extra
This commit is contained in:
Lukas Korba 2025-04-11 14:36:34 +02:00
parent f56fde9cb9
commit e2956a190e
12 changed files with 109 additions and 61 deletions

View File

@ -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] = [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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).")
}
}

View File

@ -146,7 +146,7 @@ extension SynchronizerState {
SynchronizerState(
syncSessionID: .nullID,
accountsBalances: [:],
internalSyncStatus: .syncing(0, 0),
internalSyncStatus: .syncing(0, false),
latestBlockHeight: 222222
)
}