This commit is contained in:
Lukas Korba 2025-04-11 12:36:42 +00:00 committed by GitHub
commit f54ad85106
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 267 additions and 77 deletions

View File

@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased
## Added
- `SDKSynchronizer.estimateBirthdayHeight(for date: Date)`: Get an estimated height for a given date, typically used for estimating birthday.
# 2.2.11 - 2025-04-03
## Fixed
- `transparent_gap_limit_handling` migration, whereby wallets having received transparent outputs at child indices below the index of the default address could cause the migration to fail.

View File

@ -176,8 +176,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
"state" : {
"revision" : "78cc7388a2ba5530888a99e584823a7399631d48",
"version" : "0.14.2"
"branch" : "preview/release/0.15.0",
"revision" : "c336dfc88b81aa3c857bc35e2d7d5452ae816d6e"
}
}
],

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 .syncing(let progress):
return "Syncing \(progress * 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(progress):
return "Syncing 🤖 \(floor(progress * 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(progress):
case let .syncing(syncProgress, areFundsSpendable):
enhancingStarted = false
progressBar.progress = progress
progressLabel.text = "\(floor(progress * 1000) / 10)%"
print("__LD syncProgress \(syncProgress) areFundsSpendable \(areFundsSpendable)")
progressBar.progress = syncProgress
progressLabel.text = "\(floor(syncProgress * 1000) / 10)% spendable: \(areFundsSpendable)"
let progressText = """
latest block height \(state.latestBlockHeight)
"""

View File

@ -16,7 +16,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.24.2"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3"),
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", exact: "0.14.2")
// .package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", exact: "0.14.2")
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", branch: "preview/release/0.15.0")
],
targets: [
.target(

View File

@ -73,13 +73,35 @@ extension ScanAction: Action {
// Proper solution is handled in
// 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 = walletSummary?.recoveryProgress
// report scan progress only if it's available
if let scanProgress = try? await rustBackend.getWalletSummary()?.scanProgress {
logger.debug("progress ratio: \(scanProgress.numerator)/\(scanProgress.denominator)")
let progress = try scanProgress.progress()
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)
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))
await didUpdate(.syncProgress(progress, areFundsSpendable))
}
progressReportReducer = Constants.reportDelay
} else {
progressReportReducer -= 1

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)
case syncProgress(Float, Bool)
/// Event sent when progress of the sync process changes.
case progressUpdated(Float)
case progressUpdated(Float, Bool)
/// Event sent when the CompactBlockProcessor fetched utxos from lightwalletd attempted to store them.
case storedUTXOs((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))
@ -569,7 +569,9 @@ extension CompactBlockProcessor {
await self?.send(event: event)
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
if let progress = await self?.compactBlockProgress.progress {
await self?.send(event: .progressUpdated(progress))
await self?.send(
event: .progressUpdated(progress, self?.compactBlockProgress.areFundsSpendable ?? false)
)
}
}
}
@ -713,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))
await send(event: .progressUpdated(1, false))
await send(event: .finished(lastScannedHeight))
await context.update(state: .finished)

View File

@ -11,13 +11,15 @@ final actor CompactBlockProgress {
static let zero = CompactBlockProgress()
var progress: Float = 0.0
var areFundsSpendable: Bool = false
func hasProgressUpdated(_ event: CompactBlockProcessor.Event) -> Bool {
guard case .syncProgress(let update) = event else {
guard case let .syncProgress(progress, areFundsSpendable) = event else {
return false
}
progress = update
self.progress = progress
self.areFundsSpendable = areFundsSpendable
return true
}

View File

@ -30,4 +30,91 @@ struct BundleCheckpointSource: CheckpointSource {
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
) ?? saplingActivation
}
func estimateBirthdayHeight(for date: Date) -> BlockHeight {
// the average time between 2500 blocks during last 10 checkpoints (estimated March 31, 2025) is 52.33 hours for mainnet
// the average time between 10,000 blocks during last 10 checkpoints (estimated March 31, 2025) is 134.93 hours for testnet
let avgIntervalTime: TimeInterval = network == .mainnet ? 52.33 : 134.93
let blockInterval: Double = network == .mainnet ? 2500 : 10_000
let saplingActivationHeight = network == .mainnet
? ZcashMainnet().constants.saplingActivationHeight
: ZcashTestnet().constants.saplingActivationHeight
let latestCheckpoint = latestKnownCheckpoint()
let latestCheckpointTime = TimeInterval(latestCheckpoint.time)
// above latest checkpoint, return it
guard date.timeIntervalSince1970 < latestCheckpointTime else {
return latestCheckpoint.height
}
// Phase 1, estimate possible height
let nowTimeIntervalSince1970 = Date().timeIntervalSince1970
let timeDiff = (nowTimeIntervalSince1970 - date.timeIntervalSince1970) - (nowTimeIntervalSince1970 - latestCheckpointTime)
let blockDiff = ((timeDiff / 3600) / avgIntervalTime) * blockInterval
var heightToLookAround = Double(Int(latestCheckpoint.height - Int(blockDiff)) / Int(blockInterval)) * blockInterval
// bellow sapling activation height
guard Int(heightToLookAround) > saplingActivationHeight else {
return saplingActivationHeight
}
// Phase 2, load checkpoint and evaluate against given date
guard let loadedCheckpoint = Checkpoint.birthday(
with: BlockHeight(heightToLookAround),
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
) else {
return saplingActivationHeight
}
// loaded checkpoint is exactly the one
var hoursApart = (TimeInterval(loadedCheckpoint.time) - date.timeIntervalSince1970) / 3600
if hoursApart < 0 && abs(hoursApart) < avgIntervalTime {
return loadedCheckpoint.height
}
if hoursApart < 0 {
// loaded checkpoint is lower, increase until reached the one
var closestHeight = loadedCheckpoint.height
while abs(hoursApart) > avgIntervalTime {
heightToLookAround += blockInterval
if let loadedCheckpoint = Checkpoint.birthday(
with: BlockHeight(heightToLookAround),
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
) {
hoursApart = (TimeInterval(loadedCheckpoint.time) - date.timeIntervalSince1970) / 3600
if hoursApart < 0 && abs(hoursApart) < avgIntervalTime {
return loadedCheckpoint.height
} else if hoursApart >= 0 {
return closestHeight
}
closestHeight = loadedCheckpoint.height
} else {
return saplingActivationHeight
}
}
} else {
// loaded checkpoint is higher, descrease until reached the one
while hoursApart > 0 {
heightToLookAround -= blockInterval
if let loadedCheckpoint = Checkpoint.birthday(
with: BlockHeight(heightToLookAround),
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
) {
hoursApart = (TimeInterval(loadedCheckpoint.time) - date.timeIntervalSince1970) / 3600
if hoursApart < 0 {
return loadedCheckpoint.height
}
} else {
return saplingActivationHeight
}
}
}
return saplingActivationHeight
}
}

View File

@ -31,4 +31,8 @@ protocol CheckpointSource {
/// - Note: When the user knows the exact height of the first received funds for a wallet,
/// the effective birthday of that wallet is `transaction.height - 1`.
func birthday(for height: BlockHeight) -> Checkpoint
/// Takes a given date and finds out the closes checkpoint's height for it.
/// Each checkpoint has a timestamp stored so it can be used for the calculations.
func estimateBirthdayHeight(for date: Date) -> BlockHeight
}

View File

@ -156,6 +156,8 @@ public protocol ClosureSynchronizer {
func refreshExchangeRateUSD()
func estimateBirthdayHeight(for date: Date, completion: @escaping (BlockHeight) -> Void)
/*
It can be missleading that these two methods are returning Publisher even this protocol is closure based. Reason is that Synchronizer doesn't
provide different implementations for these two methods. So Combine it is even here.

View File

@ -148,6 +148,8 @@ public protocol CombineSynchronizer {
func refreshExchangeRateUSD()
func estimateBirthdayHeight(for date: Date) -> SinglePublisher<BlockHeight, Error>
func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error>
func wipe() -> CompletablePublisher<Error>
}

View File

@ -33,8 +33,7 @@ struct ScanProgress: Equatable {
func progress() throws -> Float {
guard denominator != 0 else {
// this shouldn't happen but if it does, we need to get notified by clients and work on a fix
throw ZcashError.rustScanProgressOutOfRange("\(numerator)/\(denominator)")
return 1.0
}
let value = Float(numerator) / Float(denominator)
@ -52,6 +51,7 @@ struct WalletSummary: Equatable {
let accountBalances: [AccountUUID: AccountBalance]
let chainTipHeight: BlockHeight
let fullyScannedHeight: BlockHeight
let recoveryProgress: ScanProgress?
let scanProgress: ScanProgress?
let nextSaplingSubtreeIndex: UInt32
let nextOrchardSubtreeIndex: UInt32

View File

@ -909,6 +909,7 @@ struct ZcashRustBackend: ZcashRustBackendWelding {
accountBalances: accountBalances,
chainTipHeight: BlockHeight(summaryPtr.pointee.chain_tip_height),
fullyScannedHeight: BlockHeight(summaryPtr.pointee.fully_scanned_height),
recoveryProgress: summaryPtr.pointee.recovery_progress?.pointee.toScanProgress(),
scanProgress: summaryPtr.pointee.scan_progress?.pointee.toScanProgress(),
nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index),
nextOrchardSubtreeIndex: UInt32(summaryPtr.pointee.next_orchard_subtree_index)

View File

@ -426,13 +426,18 @@ public protocol Synchronizer: AnyObject {
kServers: Int,
network: NetworkType
) async -> [LightWalletEndpoint]
/// Takes a given date and finds out the closes checkpoint's height for it.
/// Each checkpoint has a timestamp stored so it can be used for the calculations.
func estimateBirthdayHeight(for date: Date) -> BlockHeight
}
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 let (.syncing(lhsSyncProgress, lhsRecoveryPrgoress), .syncing(rhsSyncProgress, rhsRecoveryPrgoress)):
return lhsSyncProgress == rhsSyncProgress && lhsRecoveryPrgoress == rhsRecoveryPrgoress
case (.upToDate, .upToDate): return true
case (.error, .error): return true
default: return false
@ -444,7 +449,7 @@ public enum SyncStatus: Equatable {
/// taking other maintenance steps that need to occur after an upgrade.
case unprepared
case syncing(_ progress: 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.
@ -497,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)
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.
@ -593,7 +598,8 @@ 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
case let (.syncing(lhsSyncProgress, lhsRecoveryPrgoress), .syncing(rhsSyncProgress, rhsRecoveryPrgoress)):
return lhsSyncProgress == rhsSyncProgress && lhsRecoveryPrgoress == rhsRecoveryPrgoress
case (.synced, .synced): return true
case (.stopped, .stopped): return true
case (.disconnected, .disconnected): return true
@ -604,8 +610,8 @@ extension InternalSyncStatus {
}
extension InternalSyncStatus {
init(_ blockProcessorProgress: Float) {
self = .syncing(blockProcessorProgress)
init(_ syncProgress: Float, _ areFundsSpendable: Bool) {
self = .syncing(syncProgress, areFundsSpendable)
}
}
@ -614,8 +620,8 @@ extension InternalSyncStatus {
switch self {
case .unprepared:
return .unprepared
case .syncing(let progress):
return .syncing(progress)
case let .syncing(syncProgress, areFundsSpendable):
return .syncing(syncProgress, areFundsSpendable)
case .synced:
return .upToDate
case .stopped:

View File

@ -256,6 +256,11 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
synchronizer.refreshExchangeRateUSD()
}
public func estimateBirthdayHeight(for date: Date, completion: @escaping (BlockHeight) -> Void) {
let height = synchronizer.estimateBirthdayHeight(for: date)
completion(height)
}
/*
It can be missleading that these two methods are returning Publisher even this protocol is closure based. Reason is that Synchronizer doesn't
provide different implementations for these two methods. So Combine it is even here.

View File

@ -254,6 +254,14 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
synchronizer.refreshExchangeRateUSD()
}
public func estimateBirthdayHeight(for date: Date) -> SinglePublisher<BlockHeight, Error> {
let height = synchronizer.estimateBirthdayHeight(for: date)
let subject = PassthroughSubject<BlockHeight, Error>()
subject.send(height)
subject.send(completion: .finished)
return subject.eraseToAnyPublisher()
}
public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) }
public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() }
}

View File

@ -173,8 +173,34 @@ public class SDKSynchronizer: Synchronizer {
await blockProcessor.start(retry: retry)
case .stopped, .synced, .disconnected, .error:
let syncProgress = (try? await initializer.rustBackend.getWalletSummary()?.scanProgress?.progress()) ?? 0
await updateStatus(.syncing(syncProgress))
let walletSummary = try? await initializer.rustBackend.getWalletSummary()
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)
}
}
@ -240,8 +266,8 @@ public class SDKSynchronizer: Synchronizer {
// log reorg information
self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
case let .progressUpdated(progress):
await self?.progressUpdated(progress: progress)
case let .progressUpdated(syncProgress, areFundsSpendable):
await self?.progressUpdated(syncProgress, areFundsSpendable)
case .syncProgress:
break
@ -281,8 +307,8 @@ public class SDKSynchronizer: Synchronizer {
}
}
private func progressUpdated(progress: Float) async {
let newStatus = InternalSyncStatus(progress)
private func progressUpdated(_ syncProgress: Float, _ areFundsSpendable: Bool) async {
let newStatus = InternalSyncStatus(syncProgress, areFundsSpendable)
await updateStatus(newStatus)
}
@ -866,6 +892,10 @@ public class SDKSynchronizer: Synchronizer {
return finalResult
}
public func estimateBirthdayHeight(for date: Date) -> BlockHeight {
initializer.container.resolve(CheckpointSource.self).estimateBirthdayHeight(for: date)
}
// MARK: Server switch
public func switchTo(endpoint: LightWalletEndpoint) async throws {

View File

@ -197,19 +197,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(0),
internalSyncStatus: .syncing(0, false),
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(0.9),
internalSyncStatus: .syncing(0.9, false),
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(1.0),
internalSyncStatus: .syncing(1.0, false),
latestBlockHeight: 663189
),
SynchronizerState(
@ -269,19 +269,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(0),
internalSyncStatus: .syncing(0, false),
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(0.9),
internalSyncStatus: .syncing(0.9, false),
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
accountsBalances: [:],
internalSyncStatus: .syncing(1.0),
internalSyncStatus: .syncing(1.0, false),
latestBlockHeight: 663189
),
SynchronizerState(
@ -320,19 +320,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
SynchronizerState(
syncSessionID: uuids[1],
accountsBalances: [:],
internalSyncStatus: .syncing(0),
internalSyncStatus: .syncing(0, false),
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[1],
accountsBalances: [:],
internalSyncStatus: .syncing(0.9),
internalSyncStatus: .syncing(0.9, false),
latestBlockHeight: 663200
),
SynchronizerState(
syncSessionID: uuids[1],
accountsBalances: [:],
internalSyncStatus: .syncing(1.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)))
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0, false)))
}
func testIsNotNewSessionOnUnpreparedToStateThatWontSync() {
@ -348,12 +348,8 @@ class SynchronizerOfflineTests: ZcashTestCase {
func testIsNotNewSyncSessionOnSameSession() {
XCTAssertFalse(
SessionTicker.live.isNewSyncSession(
.syncing(
0.5
),
.syncing(
0.6
)
.syncing(0.5, false),
.syncing(0.6, false)
)
)
}
@ -362,9 +358,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.synced,
.syncing(
0.6
)
.syncing(0.6, false)
)
)
}
@ -373,9 +367,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.disconnected,
.syncing(
0.6
)
.syncing(0.6, false)
)
)
}
@ -384,16 +376,14 @@ class SynchronizerOfflineTests: ZcashTestCase {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.stopped,
.syncing(
0.6
)
.syncing(0.6, false)
)
)
}
func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
XCTAssertFalse(InternalSyncStatus.disconnected.isDifferent(from: .disconnected))
XCTAssertFalse(InternalSyncStatus.syncing(0).isDifferent(from: .syncing(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))
@ -402,10 +392,10 @@ class SynchronizerOfflineTests: ZcashTestCase {
func testInternalSyncStatusMap_SyncingLowerBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(0)
InternalSyncStatus.syncing(0, false)
)
if case let .syncing(data) = 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).")
}
}
@ -413,10 +403,10 @@ class SynchronizerOfflineTests: ZcashTestCase {
func testInternalSyncStatusMap_SyncingInTheMiddle() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(0.45)
InternalSyncStatus.syncing(0.45, false)
)
if case let .syncing(data) = 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).")
}
}
@ -424,18 +414,18 @@ class SynchronizerOfflineTests: ZcashTestCase {
func testInternalSyncStatusMap_SyncingUpperBound() {
let synchronizerState = synchronizerState(
for:
InternalSyncStatus.syncing(0.9)
InternalSyncStatus.syncing(0.9, false)
)
if case let .syncing(data) = 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))
let synchronizerState = synchronizerState(for: InternalSyncStatus.syncing(1, false))
if case let .syncing(data) = 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

@ -135,9 +135,6 @@ class ZcashRustBackendTests: XCTestCase {
}
func testScanProgressThrowsOnWrongValues() {
// Assert that throws on Zero denominator
XCTAssertThrowsError(try ScanProgress(numerator: 0, denominator: 0).progress())
// Assert that throws on numerator > denominator
XCTAssertThrowsError(try ScanProgress(numerator: 23, denominator: 2).progress())

View File

@ -2072,6 +2072,26 @@ class SynchronizerMock: Synchronizer {
}
}
// MARK: - estimateBirthdayHeight
var estimateBirthdayHeightForCallsCount = 0
var estimateBirthdayHeightForCalled: Bool {
return estimateBirthdayHeightForCallsCount > 0
}
var estimateBirthdayHeightForReceivedDate: Date?
var estimateBirthdayHeightForReturnValue: BlockHeight!
var estimateBirthdayHeightForClosure: ((Date) -> BlockHeight)?
func estimateBirthdayHeight(for date: Date) -> BlockHeight {
estimateBirthdayHeightForCallsCount += 1
estimateBirthdayHeightForReceivedDate = date
if let closure = estimateBirthdayHeightForClosure {
return closure(date)
} else {
return estimateBirthdayHeightForReturnValue
}
}
}
class TransactionRepositoryMock: TransactionRepository {

View File

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