Merge e2956a190e
into e5e826d133
This commit is contained in:
commit
f54ad85106
|
@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
# Unreleased
|
# 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
|
## 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.
|
- `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.
|
||||||
|
|
||||||
|
|
|
@ -176,8 +176,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
|
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "78cc7388a2ba5530888a99e584823a7399631d48",
|
"branch" : "preview/release/0.15.0",
|
||||||
"version" : "0.14.2"
|
"revision" : "c336dfc88b81aa3c857bc35e2d7d5452ae816d6e"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -21,14 +21,18 @@ enum DemoAppConfig {
|
||||||
static let host = ZcashSDK.isMainnet ? "zec.rocks" : "lightwalletd.testnet.electriccoin.co"
|
static let host = ZcashSDK.isMainnet ? "zec.rocks" : "lightwalletd.testnet.electriccoin.co"
|
||||||
static let port: Int = 443
|
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: """
|
// 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
|
// 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: """
|
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] = [
|
static let otherSynchronizers: [SynchronizerInitData] = [
|
||||||
|
|
|
@ -319,8 +319,8 @@ extension SendViewController: UITextViewDelegate {
|
||||||
extension SDKSynchronizer {
|
extension SDKSynchronizer {
|
||||||
static func textFor(state: SyncStatus) -> String {
|
static func textFor(state: SyncStatus) -> String {
|
||||||
switch state {
|
switch state {
|
||||||
case .syncing(let progress):
|
case let .syncing(syncProgress, areFundsSpendable):
|
||||||
return "Syncing \(progress * 100.0)%"
|
return "Syncing \(syncProgress * 100.0)% spendable: \(areFundsSpendable)"
|
||||||
|
|
||||||
case .upToDate:
|
case .upToDate:
|
||||||
return "Up to Date 😎"
|
return "Up to Date 😎"
|
||||||
|
|
|
@ -172,8 +172,8 @@ extension SyncBlocksListViewController: UITableViewDataSource {
|
||||||
extension SyncStatus {
|
extension SyncStatus {
|
||||||
var text: String {
|
var text: String {
|
||||||
switch self {
|
switch self {
|
||||||
case let .syncing(progress):
|
case let .syncing(syncProgress, areFundsSpendable):
|
||||||
return "Syncing 🤖 \(floor(progress * 1000) / 10)%"
|
return "Syncing 🤖 \(floor(syncProgress * 1000) / 10)% spendable: \(areFundsSpendable)"
|
||||||
case .upToDate:
|
case .upToDate:
|
||||||
return "Up to Date 😎"
|
return "Up to Date 😎"
|
||||||
case .unprepared:
|
case .unprepared:
|
||||||
|
|
|
@ -68,11 +68,13 @@ class SyncBlocksViewController: UIViewController {
|
||||||
case .unprepared:
|
case .unprepared:
|
||||||
break
|
break
|
||||||
|
|
||||||
case let .syncing(progress):
|
case let .syncing(syncProgress, areFundsSpendable):
|
||||||
enhancingStarted = false
|
enhancingStarted = false
|
||||||
|
|
||||||
progressBar.progress = progress
|
print("__LD syncProgress \(syncProgress) areFundsSpendable \(areFundsSpendable)")
|
||||||
progressLabel.text = "\(floor(progress * 1000) / 10)%"
|
|
||||||
|
progressBar.progress = syncProgress
|
||||||
|
progressLabel.text = "\(floor(syncProgress * 1000) / 10)% spendable: \(areFundsSpendable)"
|
||||||
let progressText = """
|
let progressText = """
|
||||||
latest block height \(state.latestBlockHeight)
|
latest block height \(state.latestBlockHeight)
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,7 +16,8 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.24.2"),
|
.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/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: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|
|
@ -73,13 +73,35 @@ extension ScanAction: Action {
|
||||||
// Proper solution is handled in
|
// Proper solution is handled in
|
||||||
// TODO: [#1353] Advanced progress reporting, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1353
|
// TODO: [#1353] Advanced progress reporting, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1353
|
||||||
if progressReportReducer == 0 {
|
if progressReportReducer == 0 {
|
||||||
|
let walletSummary = try? await rustBackend.getWalletSummary()
|
||||||
|
let recoveryProgress = walletSummary?.recoveryProgress
|
||||||
|
|
||||||
// report scan progress only if it's available
|
// report scan progress only if it's available
|
||||||
if let scanProgress = try? await rustBackend.getWalletSummary()?.scanProgress {
|
if let scanProgress = walletSummary?.scanProgress {
|
||||||
logger.debug("progress ratio: \(scanProgress.numerator)/\(scanProgress.denominator)")
|
let composedNumerator: Float = Float(scanProgress.numerator) + Float(recoveryProgress?.numerator ?? 0)
|
||||||
let progress = try scanProgress.progress()
|
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)")
|
logger.debug("progress float: \(progress)")
|
||||||
await didUpdate(.syncProgress(progress))
|
await didUpdate(.syncProgress(progress, areFundsSpendable))
|
||||||
}
|
}
|
||||||
|
|
||||||
progressReportReducer = Constants.reportDelay
|
progressReportReducer = Constants.reportDelay
|
||||||
} else {
|
} else {
|
||||||
progressReportReducer -= 1
|
progressReportReducer -= 1
|
||||||
|
|
|
@ -483,10 +483,10 @@ extension CompactBlockProcessor {
|
||||||
case handledReorg(_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight)
|
case handledReorg(_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight)
|
||||||
|
|
||||||
/// Event sent when progress of some specific action happened.
|
/// 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.
|
/// 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.
|
/// Event sent when the CompactBlockProcessor fetched utxos from lightwalletd attempted to store them.
|
||||||
case storedUTXOs((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))
|
case storedUTXOs((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))
|
||||||
|
@ -569,7 +569,9 @@ extension CompactBlockProcessor {
|
||||||
await self?.send(event: event)
|
await self?.send(event: event)
|
||||||
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
|
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
|
||||||
if let progress = await self?.compactBlockProgress.progress {
|
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
|
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
|
// 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.
|
// 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 send(event: .finished(lastScannedHeight))
|
||||||
await context.update(state: .finished)
|
await context.update(state: .finished)
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,15 @@ final actor CompactBlockProgress {
|
||||||
static let zero = CompactBlockProgress()
|
static let zero = CompactBlockProgress()
|
||||||
|
|
||||||
var progress: Float = 0.0
|
var progress: Float = 0.0
|
||||||
|
var areFundsSpendable: Bool = false
|
||||||
|
|
||||||
func hasProgressUpdated(_ event: CompactBlockProcessor.Event) -> Bool {
|
func hasProgressUpdated(_ event: CompactBlockProcessor.Event) -> Bool {
|
||||||
guard case .syncProgress(let update) = event else {
|
guard case let .syncProgress(progress, areFundsSpendable) = event else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
progress = update
|
self.progress = progress
|
||||||
|
self.areFundsSpendable = areFundsSpendable
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,91 @@ struct BundleCheckpointSource: CheckpointSource {
|
||||||
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
|
checkpointDirectory: BundleCheckpointURLProvider.default.url(self.network)
|
||||||
) ?? saplingActivation
|
) ?? 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,8 @@ protocol CheckpointSource {
|
||||||
/// - Note: When the user knows the exact height of the first received funds for a wallet,
|
/// - 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`.
|
/// the effective birthday of that wallet is `transaction.height - 1`.
|
||||||
func birthday(for height: BlockHeight) -> Checkpoint
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,8 @@ public protocol ClosureSynchronizer {
|
||||||
|
|
||||||
func refreshExchangeRateUSD()
|
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
|
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.
|
provide different implementations for these two methods. So Combine it is even here.
|
||||||
|
|
|
@ -148,6 +148,8 @@ public protocol CombineSynchronizer {
|
||||||
|
|
||||||
func refreshExchangeRateUSD()
|
func refreshExchangeRateUSD()
|
||||||
|
|
||||||
|
func estimateBirthdayHeight(for date: Date) -> SinglePublisher<BlockHeight, Error>
|
||||||
|
|
||||||
func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error>
|
func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error>
|
||||||
func wipe() -> CompletablePublisher<Error>
|
func wipe() -> CompletablePublisher<Error>
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,7 @@ struct ScanProgress: Equatable {
|
||||||
|
|
||||||
func progress() throws -> Float {
|
func progress() throws -> Float {
|
||||||
guard denominator != 0 else {
|
guard denominator != 0 else {
|
||||||
// this shouldn't happen but if it does, we need to get notified by clients and work on a fix
|
return 1.0
|
||||||
throw ZcashError.rustScanProgressOutOfRange("\(numerator)/\(denominator)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = Float(numerator) / Float(denominator)
|
let value = Float(numerator) / Float(denominator)
|
||||||
|
@ -52,6 +51,7 @@ struct WalletSummary: Equatable {
|
||||||
let accountBalances: [AccountUUID: AccountBalance]
|
let accountBalances: [AccountUUID: AccountBalance]
|
||||||
let chainTipHeight: BlockHeight
|
let chainTipHeight: BlockHeight
|
||||||
let fullyScannedHeight: BlockHeight
|
let fullyScannedHeight: BlockHeight
|
||||||
|
let recoveryProgress: ScanProgress?
|
||||||
let scanProgress: ScanProgress?
|
let scanProgress: ScanProgress?
|
||||||
let nextSaplingSubtreeIndex: UInt32
|
let nextSaplingSubtreeIndex: UInt32
|
||||||
let nextOrchardSubtreeIndex: UInt32
|
let nextOrchardSubtreeIndex: UInt32
|
||||||
|
|
|
@ -909,6 +909,7 @@ struct ZcashRustBackend: ZcashRustBackendWelding {
|
||||||
accountBalances: accountBalances,
|
accountBalances: accountBalances,
|
||||||
chainTipHeight: BlockHeight(summaryPtr.pointee.chain_tip_height),
|
chainTipHeight: BlockHeight(summaryPtr.pointee.chain_tip_height),
|
||||||
fullyScannedHeight: BlockHeight(summaryPtr.pointee.fully_scanned_height),
|
fullyScannedHeight: BlockHeight(summaryPtr.pointee.fully_scanned_height),
|
||||||
|
recoveryProgress: summaryPtr.pointee.recovery_progress?.pointee.toScanProgress(),
|
||||||
scanProgress: summaryPtr.pointee.scan_progress?.pointee.toScanProgress(),
|
scanProgress: summaryPtr.pointee.scan_progress?.pointee.toScanProgress(),
|
||||||
nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index),
|
nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index),
|
||||||
nextOrchardSubtreeIndex: UInt32(summaryPtr.pointee.next_orchard_subtree_index)
|
nextOrchardSubtreeIndex: UInt32(summaryPtr.pointee.next_orchard_subtree_index)
|
||||||
|
|
|
@ -426,13 +426,18 @@ public protocol Synchronizer: AnyObject {
|
||||||
kServers: Int,
|
kServers: Int,
|
||||||
network: NetworkType
|
network: NetworkType
|
||||||
) async -> [LightWalletEndpoint]
|
) 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 enum SyncStatus: Equatable {
|
||||||
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
|
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.unprepared, .unprepared): return true
|
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 (.upToDate, .upToDate): return true
|
||||||
case (.error, .error): return true
|
case (.error, .error): return true
|
||||||
default: return false
|
default: return false
|
||||||
|
@ -444,7 +449,7 @@ public enum SyncStatus: Equatable {
|
||||||
/// taking other maintenance steps that need to occur after an upgrade.
|
/// taking other maintenance steps that need to occur after an upgrade.
|
||||||
case unprepared
|
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.
|
/// 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.
|
/// When set, a UI element may want to turn green.
|
||||||
|
@ -497,7 +502,7 @@ enum InternalSyncStatus: Equatable {
|
||||||
case unprepared
|
case unprepared
|
||||||
|
|
||||||
/// Indicates that this Synchronizer is actively processing new blocks (consists of fetch, scan and enhance operations)
|
/// 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.
|
/// 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.
|
/// When set, a UI element may want to turn green.
|
||||||
|
@ -593,7 +598,8 @@ extension InternalSyncStatus {
|
||||||
public static func == (lhs: InternalSyncStatus, rhs: InternalSyncStatus) -> Bool {
|
public static func == (lhs: InternalSyncStatus, rhs: InternalSyncStatus) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.unprepared, .unprepared): return true
|
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 (.synced, .synced): return true
|
||||||
case (.stopped, .stopped): return true
|
case (.stopped, .stopped): return true
|
||||||
case (.disconnected, .disconnected): return true
|
case (.disconnected, .disconnected): return true
|
||||||
|
@ -604,8 +610,8 @@ extension InternalSyncStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InternalSyncStatus {
|
extension InternalSyncStatus {
|
||||||
init(_ blockProcessorProgress: Float) {
|
init(_ syncProgress: Float, _ areFundsSpendable: Bool) {
|
||||||
self = .syncing(blockProcessorProgress)
|
self = .syncing(syncProgress, areFundsSpendable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,8 +620,8 @@ extension InternalSyncStatus {
|
||||||
switch self {
|
switch self {
|
||||||
case .unprepared:
|
case .unprepared:
|
||||||
return .unprepared
|
return .unprepared
|
||||||
case .syncing(let progress):
|
case let .syncing(syncProgress, areFundsSpendable):
|
||||||
return .syncing(progress)
|
return .syncing(syncProgress, areFundsSpendable)
|
||||||
case .synced:
|
case .synced:
|
||||||
return .upToDate
|
return .upToDate
|
||||||
case .stopped:
|
case .stopped:
|
||||||
|
|
|
@ -256,6 +256,11 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
|
||||||
synchronizer.refreshExchangeRateUSD()
|
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
|
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.
|
provide different implementations for these two methods. So Combine it is even here.
|
||||||
|
|
|
@ -254,6 +254,14 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
|
||||||
synchronizer.refreshExchangeRateUSD()
|
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 rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) }
|
||||||
public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() }
|
public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,8 +173,34 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
await blockProcessor.start(retry: retry)
|
await blockProcessor.start(retry: retry)
|
||||||
|
|
||||||
case .stopped, .synced, .disconnected, .error:
|
case .stopped, .synced, .disconnected, .error:
|
||||||
let syncProgress = (try? await initializer.rustBackend.getWalletSummary()?.scanProgress?.progress()) ?? 0
|
let walletSummary = try? await initializer.rustBackend.getWalletSummary()
|
||||||
await updateStatus(.syncing(syncProgress))
|
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)
|
await blockProcessor.start(retry: retry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,8 +266,8 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
// log reorg information
|
// log reorg information
|
||||||
self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
|
self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
|
||||||
|
|
||||||
case let .progressUpdated(progress):
|
case let .progressUpdated(syncProgress, areFundsSpendable):
|
||||||
await self?.progressUpdated(progress: progress)
|
await self?.progressUpdated(syncProgress, areFundsSpendable)
|
||||||
|
|
||||||
case .syncProgress:
|
case .syncProgress:
|
||||||
break
|
break
|
||||||
|
@ -281,8 +307,8 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func progressUpdated(progress: Float) async {
|
private func progressUpdated(_ syncProgress: Float, _ areFundsSpendable: Bool) async {
|
||||||
let newStatus = InternalSyncStatus(progress)
|
let newStatus = InternalSyncStatus(syncProgress, areFundsSpendable)
|
||||||
await updateStatus(newStatus)
|
await updateStatus(newStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,6 +892,10 @@ public class SDKSynchronizer: Synchronizer {
|
||||||
return finalResult
|
return finalResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func estimateBirthdayHeight(for date: Date) -> BlockHeight {
|
||||||
|
initializer.container.resolve(CheckpointSource.self).estimateBirthdayHeight(for: date)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Server switch
|
// MARK: Server switch
|
||||||
|
|
||||||
public func switchTo(endpoint: LightWalletEndpoint) async throws {
|
public func switchTo(endpoint: LightWalletEndpoint) async throws {
|
||||||
|
|
|
@ -197,19 +197,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0),
|
internalSyncStatus: .syncing(0, false),
|
||||||
latestBlockHeight: 0
|
latestBlockHeight: 0
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0.9),
|
internalSyncStatus: .syncing(0.9, false),
|
||||||
latestBlockHeight: 663189
|
latestBlockHeight: 663189
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(1.0),
|
internalSyncStatus: .syncing(1.0, false),
|
||||||
latestBlockHeight: 663189
|
latestBlockHeight: 663189
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
|
@ -269,19 +269,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0),
|
internalSyncStatus: .syncing(0, false),
|
||||||
latestBlockHeight: 0
|
latestBlockHeight: 0
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0.9),
|
internalSyncStatus: .syncing(0.9, false),
|
||||||
latestBlockHeight: 663189
|
latestBlockHeight: 663189
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[0],
|
syncSessionID: uuids[0],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(1.0),
|
internalSyncStatus: .syncing(1.0, false),
|
||||||
latestBlockHeight: 663189
|
latestBlockHeight: 663189
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
|
@ -320,19 +320,19 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[1],
|
syncSessionID: uuids[1],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0),
|
internalSyncStatus: .syncing(0, false),
|
||||||
latestBlockHeight: 663189
|
latestBlockHeight: 663189
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[1],
|
syncSessionID: uuids[1],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0.9),
|
internalSyncStatus: .syncing(0.9, false),
|
||||||
latestBlockHeight: 663200
|
latestBlockHeight: 663200
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: uuids[1],
|
syncSessionID: uuids[1],
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(1.0),
|
internalSyncStatus: .syncing(1.0, false),
|
||||||
latestBlockHeight: 663200
|
latestBlockHeight: 663200
|
||||||
),
|
),
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
|
|
|
@ -333,7 +333,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIsNewSessionOnUnpreparedToValidTransition() {
|
func testIsNewSessionOnUnpreparedToValidTransition() {
|
||||||
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0)))
|
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(0, false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIsNotNewSessionOnUnpreparedToStateThatWontSync() {
|
func testIsNotNewSessionOnUnpreparedToStateThatWontSync() {
|
||||||
|
@ -348,12 +348,8 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
func testIsNotNewSyncSessionOnSameSession() {
|
func testIsNotNewSyncSessionOnSameSession() {
|
||||||
XCTAssertFalse(
|
XCTAssertFalse(
|
||||||
SessionTicker.live.isNewSyncSession(
|
SessionTicker.live.isNewSyncSession(
|
||||||
.syncing(
|
.syncing(0.5, false),
|
||||||
0.5
|
.syncing(0.6, false)
|
||||||
),
|
|
||||||
.syncing(
|
|
||||||
0.6
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -362,9 +358,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
SessionTicker.live.isNewSyncSession(
|
SessionTicker.live.isNewSyncSession(
|
||||||
.synced,
|
.synced,
|
||||||
.syncing(
|
.syncing(0.6, false)
|
||||||
0.6
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -373,9 +367,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
SessionTicker.live.isNewSyncSession(
|
SessionTicker.live.isNewSyncSession(
|
||||||
.disconnected,
|
.disconnected,
|
||||||
.syncing(
|
.syncing(0.6, false)
|
||||||
0.6
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -384,16 +376,14 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
SessionTicker.live.isNewSyncSession(
|
SessionTicker.live.isNewSyncSession(
|
||||||
.stopped,
|
.stopped,
|
||||||
.syncing(
|
.syncing(0.6, false)
|
||||||
0.6
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
|
func testInternalSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
|
||||||
XCTAssertFalse(InternalSyncStatus.disconnected.isDifferent(from: .disconnected))
|
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.stopped.isDifferent(from: .stopped))
|
||||||
XCTAssertFalse(InternalSyncStatus.synced.isDifferent(from: .synced))
|
XCTAssertFalse(InternalSyncStatus.synced.isDifferent(from: .synced))
|
||||||
XCTAssertFalse(InternalSyncStatus.unprepared.isDifferent(from: .unprepared))
|
XCTAssertFalse(InternalSyncStatus.unprepared.isDifferent(from: .unprepared))
|
||||||
|
@ -402,10 +392,10 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
func testInternalSyncStatusMap_SyncingLowerBound() {
|
func testInternalSyncStatusMap_SyncingLowerBound() {
|
||||||
let synchronizerState = synchronizerState(
|
let synchronizerState = synchronizerState(
|
||||||
for:
|
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).")
|
XCTFail("Syncing is expected to be 0% (0.0) but received \(data).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,10 +403,10 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
func testInternalSyncStatusMap_SyncingInTheMiddle() {
|
func testInternalSyncStatusMap_SyncingInTheMiddle() {
|
||||||
let synchronizerState = synchronizerState(
|
let synchronizerState = synchronizerState(
|
||||||
for:
|
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).")
|
XCTFail("Syncing is expected to be 45% (0.45) but received \(data).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,18 +414,18 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
||||||
func testInternalSyncStatusMap_SyncingUpperBound() {
|
func testInternalSyncStatusMap_SyncingUpperBound() {
|
||||||
let synchronizerState = synchronizerState(
|
let synchronizerState = synchronizerState(
|
||||||
for:
|
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).")
|
XCTFail("Syncing is expected to be 90% (0.9) but received \(data).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInternalSyncStatusMap_FetchingUpperBound() {
|
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).")
|
XCTFail("Syncing is expected to be 100% (1.0) but received \(data).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,9 +135,6 @@ class ZcashRustBackendTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testScanProgressThrowsOnWrongValues() {
|
func testScanProgressThrowsOnWrongValues() {
|
||||||
// Assert that throws on Zero denominator
|
|
||||||
XCTAssertThrowsError(try ScanProgress(numerator: 0, denominator: 0).progress())
|
|
||||||
|
|
||||||
// Assert that throws on numerator > denominator
|
// Assert that throws on numerator > denominator
|
||||||
XCTAssertThrowsError(try ScanProgress(numerator: 23, denominator: 2).progress())
|
XCTAssertThrowsError(try ScanProgress(numerator: 23, denominator: 2).progress())
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
class TransactionRepositoryMock: TransactionRepository {
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ extension SynchronizerState {
|
||||||
SynchronizerState(
|
SynchronizerState(
|
||||||
syncSessionID: .nullID,
|
syncSessionID: .nullID,
|
||||||
accountsBalances: [:],
|
accountsBalances: [:],
|
||||||
internalSyncStatus: .syncing(0),
|
internalSyncStatus: .syncing(0, false),
|
||||||
latestBlockHeight: 222222
|
latestBlockHeight: 222222
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue