[#908] Add last and latest blocks data to Synchronizer State (#911)

- attached to the processor's timer
- added latest scanned time and latest block height
- sample app showing the new values alongside with the %
- unit tests fixed
- latestBlock() API added
- the transaction repository is no longer used to update the exposed values during the syncing
This commit is contained in:
Lukas Korba 2023-04-14 09:08:37 +02:00 committed by GitHub
parent b73ac78960
commit 99b24c2081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 417 additions and 195 deletions

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb">
<device id="retina6_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -979,16 +979,16 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="246" verticalCompressionResistancePriority="250" ambiguous="YES" alignment="center" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="IIl-kO-2M8">
<rect key="frame" x="0.0" y="79.000000000000014" width="374" height="201.33333333333337"/>
<rect key="frame" x="0.0" y="79.000000000000014" width="374" height="177.33333333333337"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" text="Status:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3t3-Tr-UlI">
<rect key="frame" x="0.0" y="81.666666666666657" width="95.666666666666671" height="38.333333333333343"/>
<rect key="frame" x="0.0" y="69.666666666666657" width="95.666666666666671" height="38.333333333333343"/>
<fontDescription key="fontDescription" type="system" pointSize="32"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jj9-7r-s2Y">
<rect key="frame" x="108.66666666666666" y="87" width="265.33333333333337" height="27.666666666666671"/>
<rect key="frame" x="108.66666666666666" y="75" width="265.33333333333337" height="27.666666666666671"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
@ -999,20 +999,26 @@
</constraints>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Progress" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JPx-ol-2nc">
<rect key="frame" x="0.0" y="304.33333333333331" width="366" height="43"/>
<rect key="frame" x="0.0" y="280.33333333333331" width="366" height="43"/>
<fontDescription key="fontDescription" type="system" pointSize="36"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" ambiguous="YES" progressViewStyle="bar" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="oKg-9s-8Ym">
<rect key="frame" x="0.0" y="371.33333333333331" width="366" height="2.6666666666666856"/>
<rect key="frame" x="0.0" y="347.33333333333331" width="366" height="2.6666666666666856"/>
</progressView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="0%" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kXE-5z-HN2">
<rect key="frame" x="0.0" y="397" width="374" height="39.666666666666686"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="0%" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kXE-5z-HN2">
<rect key="frame" x="0.0" y="373" width="374" height="39.666666666666686"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x3B-hR-coo" userLabel="ProgressDataLabel">
<rect key="frame" x="0.0" y="436.66666666666663" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux">
<rect key="frame" x="0.0" y="460.66666666666663" width="374" height="45"/>
<fontDescription key="fontDescription" type="system" pointSize="27"/>
@ -1071,6 +1077,7 @@
<connections>
<outlet property="metricLabel" destination="XK2-GA-Ufv" id="oej-al-2Of"/>
<outlet property="progressBar" destination="oKg-9s-8Ym" id="fbY-Hb-EPl"/>
<outlet property="progressDataLabel" destination="x3B-hR-coo" id="uyX-dj-EVb"/>
<outlet property="progressLabel" destination="kXE-5z-HN2" id="Eez-By-eZj"/>
<outlet property="startPause" destination="G5M-gm-1ux" id="Odc-mw-E1D"/>
<outlet property="statusLabel" destination="Jj9-7r-s2Y" id="nuZ-ad-FaF"/>
@ -1086,17 +1093,17 @@
<objects>
<viewController id="tq2-zN-roj" customClass="LatestHeightViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="tie-BL-1Ip">
<rect key="frame" x="0.0" y="0.0" width="390" height="787"/>
<rect key="frame" x="0.0" y="0.0" width="390" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Hj-Jx-J3i">
<rect key="frame" x="0.0" y="393.66666666666669" width="390" height="0.0"/>
<rect key="frame" x="0.0" y="417" width="390" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="23"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Latest Block Height" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WKf-j9-SjN">
<rect key="frame" x="16" y="305" width="358" height="49"/>
<rect key="frame" x="16" y="258" width="358" height="49"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="AXS-cf-OMk"/>
</constraints>

View File

@ -16,6 +16,7 @@ class SyncBlocksViewController: UIViewController {
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var progressBar: UIProgressView!
@IBOutlet weak var progressLabel: UILabel!
@IBOutlet weak var progressDataLabel: UILabel!
@IBOutlet weak var startPause: UIButton!
@IBOutlet weak var metricLabel: UILabel!
@IBOutlet weak var summaryLabel: UILabel!
@ -36,6 +37,7 @@ class SyncBlocksViewController: UIViewController {
}
var cancellables: [AnyCancellable] = []
let dateFormatter = DateFormatter()
let synchronizer = AppDelegate.shared.sharedSynchronizer
let closureSynchronizer = ClosureSDKSynchronizer(synchronizer: AppDelegate.shared.sharedSynchronizer)
@ -55,6 +57,8 @@ class SyncBlocksViewController: UIViewController {
statusLabel.text = textFor(state: synchronizer.latestState.syncStatus)
progressBar.progress = 0
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
synchronizer.stateStream
.throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: true)
@ -80,6 +84,13 @@ class SyncBlocksViewController: UIViewController {
progressBar.progress = progress.progress
progressLabel.text = "\(floor(progress.progress * 1000) / 10)%"
let syncedDate = dateFormatter.string(from: Date(timeIntervalSince1970: state.latestScannedTime))
let progressText = """
synced date \(syncedDate)
synced block \(state.latestScannedHeight)
target block \(state.latestBlockHeight)
"""
progressDataLabel.text = progressText
if let currentMetric {
let report = synchronizer.metrics.popBlock(operation: currentMetric)?.last

View File

@ -311,6 +311,7 @@ actor CompactBlockProcessor {
let blockEnhancer: BlockEnhancer
let utxoFetcher: UTXOFetcher
let saplingParametersHandler: SaplingParametersHandler
private let latestBlocksDataProvider: LatestBlocksDataProvider
var service: LightWalletService
var storage: CompactBlockRepository
@ -347,7 +348,8 @@ actor CompactBlockProcessor {
rustBackend: ZcashRustBackendWelding,
config: Configuration,
metrics: SDKMetrics,
logger: Logger
logger: Logger,
latestBlocksDataProvider: LatestBlocksDataProvider
) {
self.init(
service: service,
@ -357,7 +359,8 @@ actor CompactBlockProcessor {
repository: TransactionRepositoryBuilder.build(dataDbURL: config.dataDb),
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: logger),
metrics: metrics,
logger: logger
logger: logger,
latestBlocksDataProvider: latestBlocksDataProvider
)
}
@ -368,6 +371,7 @@ actor CompactBlockProcessor {
initializer: Initializer,
metrics: SDKMetrics,
logger: Logger,
latestBlocksDataProvider: LatestBlocksDataProvider,
walletBirthdayProvider: @escaping () -> BlockHeight
) {
self.init(
@ -387,7 +391,8 @@ actor CompactBlockProcessor {
repository: initializer.transactionRepository,
accountRepository: initializer.accountRepository,
metrics: metrics,
logger: logger
logger: logger,
latestBlocksDataProvider: latestBlocksDataProvider
)
}
@ -399,10 +404,12 @@ actor CompactBlockProcessor {
repository: TransactionRepository,
accountRepository: AccountRepository,
metrics: SDKMetrics,
logger: Logger
logger: Logger,
latestBlocksDataProvider: LatestBlocksDataProvider
) {
self.metrics = metrics
self.logger = logger
self.latestBlocksDataProvider = latestBlocksDataProvider
let internalSyncProgress = InternalSyncProgress(alias: config.alias, storage: UserDefaults.standard, logger: logger)
self.internalSyncProgress = internalSyncProgress
let blockDownloaderService = BlockDownloaderServiceImpl(service: service, storage: storage)
@ -433,7 +440,8 @@ actor CompactBlockProcessor {
rustBackend: rustBackend,
transactionRepository: repository,
metrics: metrics,
logger: logger
logger: logger,
latestBlocksDataProvider: latestBlocksDataProvider
)
self.blockEnhancer = BlockEnhancerImpl(
@ -710,8 +718,6 @@ actor CompactBlockProcessor {
/// Processes new blocks on the given range based on the configuration set for this instance
func processNewBlocks(ranges: SyncRanges) async {
self.foundBlocks = true
self.backoffTimer?.invalidate()
self.backoffTimer = nil
cancelableTask = Task(priority: .userInitiated) {
do {
@ -991,11 +997,12 @@ actor CompactBlockProcessor {
private func nextBatch() async {
await updateState(.syncing)
if backoffTimer == nil { await setTimer() }
do {
let nextState = try await NextStateHelper.nextState(
service: self.service,
downloaderService: blockDownloaderService,
transactionRepository: transactionRepository,
latestBlocksDataProvider: latestBlocksDataProvider,
config: self.config,
rustBackend: self.rustBackend,
internalSyncProgress: internalSyncProgress
@ -1085,18 +1092,23 @@ actor CompactBlockProcessor {
block: { [weak self] _ in
Task { [weak self] in
guard let self else { return }
if await self.shouldStart {
self.logger.debug(
"""
Timer triggered: Starting compact Block processor!.
Processor State: \(await self.state)
latestHeight: \(try await self.transactionRepository.lastScannedHeight())
attempts: \(await self.retryAttempts)
"""
)
await self.start()
} else if await self.maxAttemptsReached {
await self.fail(CompactBlockProcessorError.maxAttemptsReached(attempts: self.config.retries))
switch await self.state {
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
await self.latestBlocksDataProvider.updateBlockData()
case .stopped, .error, .synced:
if await self.shouldStart {
self.logger.debug(
"""
Timer triggered: Starting compact Block processor!.
Processor State: \(await self.state)
latestHeight: \(try await self.transactionRepository.lastScannedHeight())
attempts: \(await self.retryAttempts)
"""
)
await self.start()
} else if await self.maxAttemptsReached {
await self.fail(CompactBlockProcessorError.maxAttemptsReached(attempts: self.config.retries))
}
}
}
}
@ -1329,7 +1341,7 @@ extension CompactBlockProcessor {
return try await CompactBlockProcessor.NextStateHelper.nextState(
service: service,
downloaderService: downloaderService,
transactionRepository: transactionRepository,
latestBlocksDataProvider: latestBlocksDataProvider,
config: config,
rustBackend: rustBackend,
internalSyncProgress: internalSyncProgress
@ -1346,7 +1358,7 @@ extension CompactBlockProcessor {
static func nextState(
service: LightWalletService,
downloaderService: BlockDownloaderService,
transactionRepository: TransactionRepository,
latestBlocksDataProvider: LatestBlocksDataProvider,
config: Configuration,
rustBackend: ZcashRustBackendWelding,
internalSyncProgress: InternalSyncProgress
@ -1367,12 +1379,12 @@ extension CompactBlockProcessor {
await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadHeight)
let latestBlockHeight = try await service.latestBlockHeight()
let latestScannedHeight = try await transactionRepository.lastScannedHeight()
await latestBlocksDataProvider.updateScannedData()
await latestBlocksDataProvider.updateBlockData()
return try await internalSyncProgress.computeNextState(
latestBlockHeight: latestBlockHeight,
latestScannedHeight: latestScannedHeight,
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
walletBirthday: config.walletBirthday
)
}

View File

@ -22,6 +22,7 @@ struct BlockScannerImpl {
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let logger: Logger
let latestBlocksDataProvider: LatestBlocksDataProvider
}
extension BlockScannerImpl: BlockScanner {
@ -52,8 +53,12 @@ extension BlockScannerImpl: BlockScanner {
let scanFinishTime = Date()
lastScannedHeight = try await transactionRepository.lastScannedHeight()
if let lastScannedBlock = try await transactionRepository.lastScannedBlock() {
lastScannedHeight = lastScannedBlock.height
await latestBlocksDataProvider.updateLatestScannedHeight(lastScannedHeight)
await latestBlocksDataProvider.updateLatestScannedTime(TimeInterval(lastScannedBlock.time))
}
scannedNewBlocks = previousScannedHeight != lastScannedHeight
if scannedNewBlocks {
await didScan(lastScannedHeight)

View File

@ -11,6 +11,7 @@ import SQLite
protocol BlockDao {
func latestBlockHeight() throws -> BlockHeight
func latestBlock() throws -> Block?
func block(at height: BlockHeight) throws -> Block?
}
@ -41,7 +42,7 @@ class BlockSQLDAO: BlockDao {
var dbProvider: ConnectionProvider
var table: Table
var height = Expression<Int>("height")
init(dbProvider: ConnectionProvider) {
self.dbProvider = dbProvider
self.table = Table("Blocks")
@ -54,10 +55,18 @@ class BlockSQLDAO: BlockDao {
.map({ try $0.decode() })
.first
}
func latestBlockHeight() throws -> BlockHeight {
try dbProvider.connection().scalar(table.select(height.max)) ?? BlockHeight.empty()
}
func latestBlock() throws -> Block? {
try dbProvider
.connection()
.prepare(Block.table.order(height.desc).limit(1))
.map({ try $0.decode() })
.first
}
}
extension BlockSQLDAO: BlockRepository {

View File

@ -43,6 +43,10 @@ class TransactionSQLDAO: TransactionRepository {
try blockDao.latestBlockHeight()
}
func lastScannedBlock() async throws -> Block? {
try blockDao.latestBlock()
}
func isInitialized() async throws -> Bool {
true
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// URL+UpdateWithAlias.swift
//
//
// Created by Michal Fousek on 23.03.2023.

View File

@ -154,7 +154,6 @@ protocol LightWalletService: AnyObject {
func getInfo() async throws -> LightWalletdInfo
/// Return the latest block height known to the service.
/// Blocking API
func latestBlockHeight() async throws -> BlockHeight
/// Return the given range of blocks.
@ -162,7 +161,7 @@ protocol LightWalletService: AnyObject {
/// For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error>
/// Submits a raw transaction over lightwalletd. Non-Blocking
/// Submits a raw transaction over lightwalletd.
/// - Parameter spendTransaction: data representing the transaction to be sent
func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse

View File

@ -0,0 +1,69 @@
//
// LatestBlocksDataProvider.swift
//
//
// Created by Lukáš Korba on 11.04.2023.
//
import Foundation
protocol LatestBlocksDataProvider {
var latestScannedHeight: BlockHeight { get async }
var latestScannedTime: TimeInterval { get async }
var latestBlockHeight: BlockHeight { get async }
var walletBirthday: BlockHeight { get async }
func updateScannedData() async
func updateBlockData() async
func updateWalletBirthday(_ walletBirthday: BlockHeight) async
func updateLatestScannedHeight(_ latestScannedHeight: BlockHeight) async
func updateLatestScannedTime(_ latestScannedTime: TimeInterval) async
}
actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
let service: LightWalletService
let transactionRepository: TransactionRepository
// Valid values are stored here after Synchronizer's `prepare` is called.
private(set) var latestScannedHeight: BlockHeight = .zero
private(set) var latestScannedTime: TimeInterval = 0.0
// Valid value is stored here after block processor's `nextState` is called.
private(set) var latestBlockHeight: BlockHeight = .zero
// Valid values are stored here after Synchronizer's `prepare` is called.
private(set) var walletBirthday: BlockHeight = .zero
init(service: LightWalletService, transactionRepository: TransactionRepository) {
self.service = service
self.transactionRepository = transactionRepository
}
/// Call of this function is potentially dangerous and can result in `database lock` errors.
/// Typical use is outside of a sync process. Example: Synchronizer's prepare function, call there is a safe one.
/// The update of `latestScannedHeight` and `latestScannedTime` during the syncing is done via
/// appropriate `updateX()` methods inside `BlockScanner` so `transactionRepository` is omitted.
func updateScannedData() async {
latestScannedHeight = (try? await transactionRepository.lastScannedHeight()) ?? walletBirthday
if let time = try? await transactionRepository.blockForHeight(latestScannedHeight)?.time {
latestScannedTime = TimeInterval(time)
}
}
func updateBlockData() async {
if let newLatestBlockHeight = try? await service.latestBlockHeight(),
latestBlockHeight < newLatestBlockHeight {
latestBlockHeight = newLatestBlockHeight
}
}
func updateWalletBirthday(_ walletBirthday: BlockHeight) async {
self.walletBirthday = walletBirthday
}
func updateLatestScannedHeight(_ latestScannedHeight: BlockHeight) async {
self.latestScannedHeight = latestScannedHeight
}
func updateLatestScannedTime(_ latestScannedTime: TimeInterval) async {
self.latestScannedTime = latestScannedTime
}
}

View File

@ -38,7 +38,6 @@ protocol CompactBlockRepository {
/**
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
Non-Blocking
- Parameters:
- Parameter blocks: array of blocks to be written to storage
- Throws: an error when there's a failure

View File

@ -24,12 +24,12 @@ public protocol PaginatedTransactionRepository {
var itemCount: Int { get async }
/**
Returns the page number if exists. Blocking
Returns the page number if exists.
*/
func page(_ number: Int) async throws -> [ZcashTransaction.Overview]?
/**
Returns the page number if exists. Non-blocking
Returns the page number if exists.
*/
func page(_ number: Int, result: @escaping (Result<[ZcashTransaction.Overview]?, Error>) -> Void)
}

View File

@ -19,6 +19,7 @@ protocol TransactionRepository {
func countUnmined() async throws -> Int
func blockForHeight(_ height: BlockHeight) async throws -> Block?
func lastScannedHeight() async throws -> BlockHeight
func lastScannedBlock() async throws -> Block?
func isInitialized() async throws -> Bool
func find(id: Int) async throws -> ZcashTransaction.Overview
func find(rawID: Data) async throws -> ZcashTransaction.Overview

View File

@ -80,6 +80,11 @@ public struct SynchronizerState: Equatable {
public var syncStatus: SyncStatus
/// height of the latest scanned block known to this synchronizer.
public var latestScannedHeight: BlockHeight
/// height of the latest block on the blockchain known to this synchronizer.
public var latestBlockHeight: BlockHeight
/// timestamp of the latest scanned block on the blockchain known to this synchronizer.
/// The anchor point is timeIntervalSince1970
public var latestScannedTime: TimeInterval
/// Represents a synchronizer that has made zero progress hasn't done a sync attempt
public static var zero: SynchronizerState {
@ -88,7 +93,9 @@ public struct SynchronizerState: Equatable {
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .unprepared,
latestScannedHeight: .zero
latestScannedHeight: .zero,
latestBlockHeight: .zero,
latestScannedTime: 0
)
}
}
@ -268,7 +275,6 @@ public protocol Synchronizer: AnyObject {
func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview]
/// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking
func latestHeight() async throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on

View File

@ -35,8 +35,6 @@ public class SDKSynchronizer: Synchronizer {
lazy var blockProcessorEventProcessingQueue = { DispatchQueue(label: "blockProcessorEventProcessingQueue_\(initializer.alias.description)") }()
public private(set) var initializer: Initializer
// Valid value is stored here after `prepare` is called.
public private(set) var latestScannedHeight: BlockHeight = .zero
public private(set) var connectionState: ConnectionState
public private(set) var network: ZcashNetwork
private var transactionManager: OutboundTransactionManager
@ -47,11 +45,16 @@ public class SDKSynchronizer: Synchronizer {
private var syncSession: SyncSession
private var syncSessionTicker: SessionTicker
private var syncStartDate: Date?
let latestBlocksDataProvider: LatestBlocksDataProvider
/// Creates an SDKSynchronizer instance
/// - Parameter initializer: a wallet Initializer object
public convenience init(initializer: Initializer) {
let metrics = SDKMetrics()
let latestBlocksDataProvider = LatestBlocksDataProviderImpl(
service: initializer.lightWalletService,
transactionRepository: initializer.transactionRepository
)
self.init(
status: .unprepared,
initializer: initializer,
@ -62,11 +65,13 @@ public class SDKSynchronizer: Synchronizer {
initializer: initializer,
metrics: metrics,
logger: initializer.logger,
latestBlocksDataProvider: latestBlocksDataProvider,
walletBirthdayProvider: { initializer.walletBirthday }
),
metrics: metrics,
syncSessionIDGenerator: UniqueSyncSessionIDGenerator(),
syncSessionTicker: .live
syncSessionTicker: .live,
latestBlocksDataProvider: latestBlocksDataProvider
)
}
@ -79,7 +84,8 @@ public class SDKSynchronizer: Synchronizer {
blockProcessor: CompactBlockProcessor,
metrics: SDKMetrics,
syncSessionIDGenerator: SyncSessionIDGenerator,
syncSessionTicker: SessionTicker
syncSessionTicker: SessionTicker,
latestBlocksDataProvider: LatestBlocksDataProvider
) {
self.connectionState = .idle
self.underlyingStatus = GenericActor(status)
@ -94,6 +100,7 @@ public class SDKSynchronizer: Synchronizer {
self.syncSessionIDGenerator = syncSessionIDGenerator
self.syncSession = SyncSession(.nullID)
self.syncSessionTicker = syncSessionTicker
self.latestBlocksDataProvider = latestBlocksDataProvider
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
self?.connectivityStateChanged(oldState: oldState, newState: newState)
@ -149,8 +156,9 @@ public class SDKSynchronizer: Synchronizer {
return .seedRequired
}
latestScannedHeight = (try? await transactionRepository.lastScannedHeight()) ?? initializer.walletBirthday
await latestBlocksDataProvider.updateWalletBirthday(initializer.walletBirthday)
await latestBlocksDataProvider.updateScannedData()
await updateStatus(.disconnected)
return .success
@ -241,8 +249,8 @@ public class SDKSynchronizer: Synchronizer {
}
private func finished(lastScannedHeight: BlockHeight, foundBlocks: Bool) async {
await latestBlocksDataProvider.updateScannedData()
// FIX: Pending transaction updates fail if done from another thread. Improvement needed: explicitly define queues for sql repositories see: https://github.com/zcash/ZcashLightClientKit/issues/450
latestScannedHeight = lastScannedHeight
await refreshPendingTransactions()
await updateStatus(.synced)
@ -328,7 +336,7 @@ public class SDKSynchronizer: Synchronizer {
let tBalance = try await self.getTransparentBalance(accountIndex: accountIndex)
// Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet.
guard tBalance.verified >= self.network.constants.defaultFee(for: self.latestScannedHeight) else {
guard tBalance.verified >= self.network.constants.defaultFee(for: await self.latestBlocksDataProvider.latestScannedHeight) else {
throw ShieldFundsError.insuficientTransparentFunds
}
@ -574,7 +582,7 @@ public class SDKSynchronizer: Synchronizer {
// MARK: notify state
private func snapshotState(status: SyncStatus) async -> SynchronizerState {
await SynchronizerState(
return await SynchronizerState(
syncSessionID: syncSession.value,
shieldedBalance: WalletBalance(
verified: (try? await getShieldedVerifiedBalance()) ?? .zero,
@ -582,7 +590,9 @@ public class SDKSynchronizer: Synchronizer {
),
transparentBalance: (try? await blockProcessor.getTransparentBalance(accountIndex: 0)) ?? .zero,
syncStatus: status,
latestScannedHeight: self.latestScannedHeight
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
latestScannedTime: latestBlocksDataProvider.latestScannedTime
)
}
@ -602,21 +612,10 @@ public class SDKSynchronizer: Synchronizer {
nextState.syncSessionID = nextSessionID
newState = nextState
} else {
if oldStatus.isDifferent(from: newStatus) {
if SessionTicker.live.isNewSyncSession(oldStatus, newStatus) {
await self.syncSession.newSession(with: self.syncSessionIDGenerator)
}
newState = await snapshotState(status: newStatus)
} else {
newState = await SynchronizerState(
syncSessionID: syncSession.value,
shieldedBalance: latestState.shieldedBalance,
transparentBalance: latestState.transparentBalance,
syncStatus: newStatus,
latestScannedHeight: latestState.latestScannedHeight
)
if SessionTicker.live.isNewSyncSession(oldStatus, newStatus) {
await self.syncSession.newSession(with: self.syncSessionIDGenerator)
}
newState = await snapshotState(status: newStatus)
}
latestState = newState

View File

@ -21,7 +21,6 @@ protocol TransactionEncoder {
/// Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
/// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
/// double-bangs for things).
/// Non-blocking
///
/// - Parameters:
/// - Parameter spendingKey: a `UnifiedSpendingKey` containing the spending key
@ -37,16 +36,15 @@ protocol TransactionEncoder {
from accountIndex: Int
) async throws -> ZcashTransaction.Overview
/**
Creates a transaction that will attempt to shield transparent funds that are present on the blocks cache .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things).
Blocking
- Parameters:
- Parameter spendingKey: `UnifiedSpendingKey` to spend the UTXOs
- Parameter memoBytes: containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Throws: a TransactionEncoderError
*/
/// Creates a transaction that will attempt to shield transparent funds that are present on the blocks cache .throwing
/// an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception,
/// we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things).
///
/// - Parameters:
/// - Parameter spendingKey: `UnifiedSpendingKey` to spend the UTXOs
/// - Parameter memoBytes: containing the memo (optional)
/// - Parameter accountIndex: index of the account that will be used to send the funds
/// - Throws: a TransactionEncoderError
func createShieldingTransaction(
spendingKey: UnifiedSpendingKey,
shieldingThreshold: Zatoshi,

View File

@ -1,5 +1,5 @@
//
// File.swift
// SyncSessionIDGenerator.swift
//
//
// Created by Francisco Gindre on 3/31/23.

View File

@ -1336,7 +1336,7 @@ class AdvancedReOrgTests: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 600)
let latestScannedHeight = coordinator.synchronizer.latestScannedHeight
let latestScannedHeight = await coordinator.synchronizer.latestBlocksDataProvider.latestScannedHeight
XCTAssertEqual(latestScannedHeight, birthday + fullSyncLength)
}

View File

@ -180,33 +180,42 @@ class SynchronizerDarksideTests: XCTestCase {
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .disconnected,
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 663189,
latestScannedTime: 1576821833
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 663189,
latestScannedTime: 1576821833
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
// shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 663150
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
transparentBalance: .zero,
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663150
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
transparentBalance: .zero,
syncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
@ -230,12 +239,14 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189
)
),
latestScannedHeight: 663150
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
transparentBalance: .zero,
syncStatus: .enhancing(
EnhancementProgress(
totalTransactions: 2,
@ -259,21 +270,27 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189
)
),
latestScannedHeight: 663150
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
transparentBalance: .zero,
syncStatus: .fetching,
latestScannedHeight: 663150
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
transparentBalance: .zero,
syncStatus: .synced,
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
)
]
@ -317,28 +334,36 @@ class SynchronizerDarksideTests: XCTestCase {
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .disconnected,
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
syncStatus: .syncing(BlockProgress(startHeight: 663150, targetHeight: 663189, progressHeight: 663189)),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
@ -367,7 +392,9 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189
)
),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
@ -396,21 +423,27 @@ class SynchronizerDarksideTests: XCTestCase {
range: 663150...663189
)
),
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching,
latestScannedHeight: 663150
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced,
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 0,
latestScannedTime: 0
)
]
@ -439,35 +472,45 @@ class SynchronizerDarksideTests: XCTestCase {
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)),
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .syncing(BlockProgress(startHeight: 663190, targetHeight: 663200, progressHeight: 663200)),
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .enhancing(EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0)),
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .fetching,
latestScannedHeight: 663189
latestScannedHeight: 663189,
latestBlockHeight: 0,
latestScannedTime: 0
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
syncStatus: .synced,
latestScannedHeight: 663200
latestScannedHeight: 663200,
latestBlockHeight: 0,
latestScannedTime: 0
)
]

View File

@ -125,6 +125,14 @@ class TransactionEnhancementTests: XCTestCase {
)
try! await storage.create()
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
downloader = BlockDownloaderServiceImpl(service: service, storage: storage)
processor = CompactBlockProcessor(
service: service,
@ -132,7 +140,8 @@ class TransactionEnhancementTests: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
)
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in

View File

@ -110,7 +110,8 @@ class BlockScanTests: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
@ -197,7 +198,8 @@ class BlockScanTests: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: metrics,
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in

View File

@ -96,7 +96,8 @@ class BlockStreamingTest: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
let cancelableTask = Task {
@ -160,7 +161,8 @@ class BlockStreamingTest: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
let date = Date()

View File

@ -90,13 +90,22 @@ class CompactBlockProcessorTests: XCTestCase {
try await storage.create()
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
processor = CompactBlockProcessor(
service: service,
storage: storage,
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
)
let dbInit = try await rustBackend.initDataDb(seed: nil)

View File

@ -104,13 +104,22 @@ class CompactBlockReorgTests: XCTestCase {
mockValidateCombinedChainFailureError: .invalidChain(upperBound: Int32(network.constants.saplingActivationHeight + 320))
)
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
processor = CompactBlockProcessor(
service: service,
storage: realCache,
rustBackend: rustBackendMockHelper.rustBackendMock,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
)
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")

View File

@ -61,7 +61,8 @@ class DownloadTests: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
do {

View File

@ -84,7 +84,8 @@ class BlockBatchValidationTests: XCTestCase {
rustBackend: mockBackend.rustBackendMock,
config: config,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
do {
@ -156,7 +157,8 @@ class BlockBatchValidationTests: XCTestCase {
rustBackend: mockBackend.rustBackendMock,
config: config,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
do {
@ -228,7 +230,8 @@ class BlockBatchValidationTests: XCTestCase {
rustBackend: mockBackend.rustBackendMock,
config: config,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
do {
@ -301,7 +304,8 @@ class BlockBatchValidationTests: XCTestCase {
rustBackend: mockBackend.rustBackendMock,
config: config,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
do {
@ -353,14 +357,6 @@ class BlockBatchValidationTests: XCTestCase {
network: network
)
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: expectedStoredLatestHeight,
network: network
)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
info.branch = "d34db33f"
@ -378,7 +374,10 @@ class BlockBatchValidationTests: XCTestCase {
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
service: service,
downloaderService: downloaderService,
transactionRepository: transactionRepository,
latestBlocksDataProvider: LatestBlocksDataProviderMock(
latestScannedHeight: expectedStoredLatestHeight,
latestBlockHeight: expectedLatestHeight
),
config: config,
rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: InternalSyncProgress(
@ -417,21 +416,21 @@ class BlockBatchValidationTests: XCTestCase {
latestBlockHeight: expectedLatestHeight,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let expectedStoreLatestHeight = BlockHeight(1220000)
let expectedStoredLatestHeight = BlockHeight(1220000)
let walletBirthday = BlockHeight(1210000)
let ranges = SyncRanges(
latestBlockHeight: expectedLatestHeight,
downloadedButUnscannedRange: nil,
downloadAndScanRange: expectedStoreLatestHeight + 1...expectedLatestHeight,
downloadAndScanRange: expectedStoredLatestHeight + 1...expectedLatestHeight,
enhanceRange: walletBirthday...expectedLatestHeight,
fetchUTXORange: walletBirthday...expectedLatestHeight,
latestScannedHeight: expectedStoreLatestHeight,
latestDownloadedBlockHeight: expectedStoreLatestHeight
latestScannedHeight: expectedStoredLatestHeight,
latestDownloadedBlockHeight: expectedStoredLatestHeight
)
let expectedResult = CompactBlockProcessor.NextState.processNewBlocks(ranges: ranges)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoredLatestHeight)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
@ -449,14 +448,6 @@ class BlockBatchValidationTests: XCTestCase {
network: network
)
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: expectedStoreLatestHeight,
network: network
)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
info.branch = "d34db33f"
@ -474,7 +465,10 @@ class BlockBatchValidationTests: XCTestCase {
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
service: service,
downloaderService: downloaderService,
transactionRepository: transactionRepository,
latestBlocksDataProvider: LatestBlocksDataProviderMock(
latestScannedHeight: expectedStoredLatestHeight,
latestBlockHeight: expectedLatestHeight
),
config: config,
rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: InternalSyncProgress(
@ -513,10 +507,10 @@ class BlockBatchValidationTests: XCTestCase {
latestBlockHeight: expectedLatestHeight,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let expectedStoreLatestHeight = BlockHeight(1230000)
let expectedStoredLatestHeight = BlockHeight(1230000)
let walletBirthday = BlockHeight(1210000)
let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoreLatestHeight)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoredLatestHeight)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoredLatestHeight)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
@ -539,16 +533,8 @@ class BlockBatchValidationTests: XCTestCase {
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
await internalSyncProgress.set(expectedStoreLatestHeight, .latestEnhancedHeight)
await internalSyncProgress.set(expectedStoreLatestHeight, .latestUTXOFetchedHeight)
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: expectedStoreLatestHeight,
network: network
)
await internalSyncProgress.set(expectedStoredLatestHeight, .latestEnhancedHeight)
await internalSyncProgress.set(expectedStoredLatestHeight, .latestUTXOFetchedHeight)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
@ -567,7 +553,10 @@ class BlockBatchValidationTests: XCTestCase {
nextBatch = try await CompactBlockProcessor.NextStateHelper.nextState(
service: service,
downloaderService: downloaderService,
transactionRepository: transactionRepository,
latestBlocksDataProvider: LatestBlocksDataProviderMock(
latestScannedHeight: expectedStoredLatestHeight,
latestBlockHeight: expectedLatestHeight
),
config: config,
rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: internalSyncProgress

View File

@ -39,13 +39,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
}
func testStateStreamEmitsAsExpected() {
let state = SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
latestScannedHeight: 111111
)
let state = SynchronizerState.mock
synchronizerMock.underlyingStateStream = Just(state).eraseToAnyPublisher()
let expectation = XCTestExpectation()
@ -70,13 +64,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
}
func testLatestStateIsAsExpected() {
let state = SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
latestScannedHeight: 111111
)
let state = SynchronizerState.mock
synchronizerMock.underlyingLatestState = state
XCTAssertEqual(synchronizer.latestState, state)

View File

@ -37,13 +37,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
}
func testStateStreamEmitsAsExpected() {
let state = SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
latestScannedHeight: 111111
)
let state = SynchronizerState.mock
synchronizerMock.underlyingStateStream = Just(state).eraseToAnyPublisher()
let expectation = XCTestExpectation()
@ -68,13 +62,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
}
func testLatestStateIsAsExpected() {
let state = SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
latestScannedHeight: 111111
)
let state = SynchronizerState.mock
synchronizerMock.underlyingLatestState = state
XCTAssertEqual(synchronizer.latestState, state)

View File

@ -56,7 +56,8 @@ class CompactBlockProcessorOfflineTests: XCTestCase {
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
logger: logger,
latestBlocksDataProvider: LatestBlocksDataProviderMock()
)
let fullRange = 0...1000

View File

@ -0,0 +1,38 @@
//
// LatestBlocksDataProviderMock.swift
//
//
// Created by Lukáš Korba on 12.04.2023.
//
import Foundation
@testable import ZcashLightClientKit
actor LatestBlocksDataProviderMock: LatestBlocksDataProvider {
private(set) var latestScannedHeight: BlockHeight = .zero
private(set) var latestScannedTime: TimeInterval = 0.0
private(set) var latestBlockHeight: BlockHeight = .zero
private(set) var walletBirthday: BlockHeight = .zero
init(
latestScannedHeight: BlockHeight = .zero,
latestScannedTime: TimeInterval = 0,
latestBlockHeight: BlockHeight = .zero,
walletBirthday: BlockHeight = .zero
) {
self.latestScannedHeight = latestScannedHeight
self.latestScannedTime = latestScannedTime
self.latestBlockHeight = latestBlockHeight
self.walletBirthday = walletBirthday
}
func updateScannedData() async { }
func updateBlockData() async { }
func updateWalletBirthday(_ walletBirthday: BlockHeight) async { }
func updateLatestScannedHeight(_ latestScannedHeight: BlockHeight) async { }
func updateLatestScannedTime(_ latestScannedTime: TimeInterval) async { }
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// MockSessionIDGenerator.swift
//
//
// Created by Francisco Gindre on 3/31/23.

View File

@ -100,7 +100,11 @@ extension MockTransactionRepository: TransactionRepository {
}
func lastScannedHeight() throws -> BlockHeight {
return scannedHeight
scannedHeight
}
func lastScannedBlock() throws -> Block? {
nil
}
func isInitialized() throws -> Bool {

View File

@ -1,5 +1,5 @@
//
// File.swift
// SDKSynchronizer+Utils.swift
//
//
// Created by Francisco Gindre on 3/31/23.
@ -11,6 +11,10 @@ import Foundation
extension SDKSynchronizer {
convenience init(initializer: Initializer, sessionGenerator: SyncSessionIDGenerator, sessionTicker: SessionTicker) {
let metrics = SDKMetrics()
let latestBlocksDataProvider = LatestBlocksDataProviderImpl(
service: initializer.lightWalletService,
transactionRepository: initializer.transactionRepository
)
self.init(
status: .unprepared,
initializer: initializer,
@ -21,11 +25,13 @@ extension SDKSynchronizer {
initializer: initializer,
metrics: metrics,
logger: initializer.logger,
latestBlocksDataProvider: latestBlocksDataProvider,
walletBirthdayProvider: { initializer.walletBirthday }
),
metrics: metrics,
syncSessionIDGenerator: sessionGenerator,
syncSessionTicker: sessionTicker
syncSessionTicker: sessionTicker,
latestBlocksDataProvider: latestBlocksDataProvider
)
}
}

View File

@ -200,3 +200,17 @@ extension CompactBlockProcessor.Configuration {
)
}
}
extension SynchronizerState {
static var mock: SynchronizerState {
SynchronizerState(
syncSessionID: .nullID,
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)),
syncStatus: .fetching,
latestScannedHeight: 111111,
latestBlockHeight: 222222,
latestScannedTime: 12345678
)
}
}