Merge pull request #1243 from zcash/feature/DAG-sync

Implement the ability to spend funds without complete wallet sync.
This commit is contained in:
Kris Nuttycombe 2023-09-13 11:34:44 -06:00 committed by GitHub
commit 552d31eb64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 3231 additions and 2417 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/grpc/grpc-swift.git",
"state" : {
"revision" : "130467153ff0acd642d2f098b69c1ac33373b24e",
"version" : "1.15.0"
"revision" : "84bac657e9930d26e9124bac082f26586dc2d209",
"version" : "1.19.1"
}
},
{
@ -104,8 +104,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "d1690f85419fdac8d54e350fb6d2ab9fd95afd75",
"version" : "2.51.1"
"revision" : "cf281631ff10ec6111f2761052aa81896a83a007",
"version" : "2.58.0"
}
},
{
@ -158,7 +158,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "1411d9a839a62523997dae113150b2beccd6b3fc"
"revision" : "8607dc26a637697e53e0be1fb09b81cba9d8475a",
"version" : "0.4.0-rc.2"
}
}
],

View File

@ -3,7 +3,7 @@
<device id="retina6_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<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"/>
@ -140,28 +140,8 @@
<segue destination="6mH-Rv-HBn" kind="show" id="2Og-dq-0lF"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="hfc-dh-b2p" style="IBUITableViewCellStyleDefault" id="U6y-0k-TWn">
<rect key="frame" x="0.0" y="312.00000762939453" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="U6y-0k-TWn" id="7aj-Lt-9o9">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Pending Transactions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hfc-dh-b2p">
<rect key="frame" x="20" y="0.0" width="350" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="Ya7-uX-3Bq" kind="show" identifier="Pending" id="snP-Bc-obL"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="oRc-aQ-3vN" style="IBUITableViewCellStyleDefault" id="rBT-Qj-4GX">
<rect key="frame" x="0.0" y="355.66667556762695" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="312.00000762939453" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rBT-Qj-4GX" id="7t0-PZ-KW4">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -181,7 +161,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="mo0-CA-kMx" style="IBUITableViewCellStyleDefault" id="cYW-no-fyA">
<rect key="frame" x="0.0" y="399.33334350585938" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="355.66667556762695" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="cYW-no-fyA" id="uu3-KF-LDB">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -201,7 +181,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="3ht-A5-ABT" style="IBUITableViewCellStyleDefault" id="Bvc-KS-SGJ">
<rect key="frame" x="0.0" y="443.0000114440918" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="399.33334350585938" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Bvc-KS-SGJ" id="Hsp-0f-x3X">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -221,7 +201,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Hl7-xV-yrM" style="IBUITableViewCellStyleDefault" id="6sF-sD-nYj">
<rect key="frame" x="0.0" y="486.66667938232422" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="443.0000114440918" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="6sF-sD-nYj" id="Uwh-62-xaj">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -241,7 +221,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="HyY-vt-5nR" style="IBUITableViewCellStyleDefault" id="KkJ-uD-G5q">
<rect key="frame" x="0.0" y="530.33334732055664" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="486.66667938232422" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KkJ-uD-G5q" id="VL6-QM-S0g">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -261,7 +241,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="XCC-pZ-eel" style="IBUITableViewCellStyleDefault" id="dgB-c9-cv9">
<rect key="frame" x="0.0" y="574.00001525878906" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="530.33334732055664" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dgB-c9-cv9" id="C46-42-3yU">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -281,7 +261,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="B3i-Nh-NA5" style="IBUITableViewCellStyleDefault" id="VPb-7U-IKD">
<rect key="frame" x="0.0" y="617.66668319702148" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="574.00001525878906" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VPb-7U-IKD" id="IWX-T9-THE">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -298,7 +278,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="qHq-xq-jFS" style="IBUITableViewCellStyleDefault" id="XHY-aU-r1N">
<rect key="frame" x="0.0" y="661.33335113525391" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="617.66668319702148" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XHY-aU-r1N" id="fbk-CU-wgr">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -1414,7 +1394,7 @@
</scenes>
<inferredMetricsTieBreakers>
<segue reference="oxP-eV-1Z2"/>
<segue reference="snP-Bc-obL"/>
<segue reference="scF-mQ-yRS"/>
</inferredMetricsTieBreakers>
<resources>
<image name="play.circle" catalog="system" width="128" height="123"/>

View File

@ -21,9 +21,14 @@ enum DemoAppConfig {
static let host = ZcashSDK.isMainnet ? "mainnet.lightwalletd.com" : "lightwalletd.testnet.electriccoin.co"
static let port: Int = 9067
static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
// static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
// 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 defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 1935000 : 2170000
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
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 otherSynchronizers: [SynchronizerInitData] = [

View File

@ -12,7 +12,6 @@ import ZcashLightClientKit
class TransactionsDataSource: NSObject {
enum TransactionType {
case pending
case sent
case received
case cleared
@ -32,17 +31,9 @@ class TransactionsDataSource: NSObject {
self.synchronizer = synchronizer
}
// swiftlint:disable:next cyclomatic_complexity
func load() async throws {
transactions = []
switch status {
case .pending:
let rawTransactions = await synchronizer.pendingTransactions
for pendingTransaction in rawTransactions {
let memos = try await synchronizer.getMemos(for: pendingTransaction)
transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos))
}
case .cleared:
let rawTransactions = await synchronizer.transactions
for transaction in rawTransactions {
@ -62,12 +53,6 @@ class TransactionsDataSource: NSObject {
transactions.append(TransactionDetailModel(sendTransaction: transaction, memos: memos))
}
case .all:
let rawPendingTransactions = await synchronizer.pendingTransactions
for pendingTransaction in rawPendingTransactions {
let memos = try await synchronizer.getMemos(for: pendingTransaction)
transactions.append(TransactionDetailModel(pendingTransaction: pendingTransaction, memos: memos))
}
let rawClearedTransactions = await synchronizer.transactions
for transaction in rawClearedTransactions {
let memos = try await synchronizer.getMemos(for: transaction)

View File

@ -45,7 +45,8 @@ class SendViewController: UIViewController {
closureSynchronizer.prepare(
with: DemoAppConfig.defaultSeed,
walletBirthday: DemoAppConfig.defaultBirthdayHeight
walletBirthday: DemoAppConfig.defaultBirthdayHeight,
for: .existingWallet
) { result in
loggerProxy.debug("Prepare result: \(result)")
}
@ -79,8 +80,7 @@ class SendViewController: UIViewController {
func setUp() {
Task { @MainActor in
balanceLabel.text = format(balance: (try? await synchronizer.getShieldedBalance(accountIndex: 0)) ?? .zero)
verifiedBalanceLabel.text = format(balance: (try? await synchronizer.getShieldedVerifiedBalance(accountIndex: 0)) ?? .zero)
await updateBalance()
await toggleSendButton()
}
memoField.text = ""
@ -93,12 +93,20 @@ class SendViewController: UIViewController {
.throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: true)
.sink(
receiveValue: { [weak self] state in
Task { @MainActor in
await self?.updateBalance()
}
self?.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: state.syncStatus)
}
)
.store(in: &cancellables)
}
func updateBalance() async {
balanceLabel.text = format(balance: (try? await synchronizer.getShieldedBalance(accountIndex: 0)) ?? .zero)
verifiedBalanceLabel.text = format(balance: (try? await synchronizer.getShieldedVerifiedBalance(accountIndex: 0)) ?? .zero)
}
func format(balance: Zatoshi = Zatoshi()) -> String {
"Zec \(balance.formattedString ?? "0.0")"
}
@ -300,7 +308,7 @@ extension SDKSynchronizer {
static func textFor(state: SyncStatus) -> String {
switch state {
case .syncing(let progress):
return "Syncing \(progress)"
return "Syncing \(progress * 100.0)%"
case .upToDate:
return "Up to Date 😎"

View File

@ -69,7 +69,8 @@ class SyncBlocksListViewController: UIViewController {
if syncStatus == .unprepared {
_ = try! await synchronizer.prepare(
with: synchronizerData.seed,
walletBirthday: synchronizerData.birthday
walletBirthday: synchronizerData.birthday,
for: .existingWallet
)
}

View File

@ -29,7 +29,6 @@ class SyncBlocksViewController: UIViewController {
guard let currentMetric else { return "" }
switch currentMetric {
case .downloadBlocks: return "download: "
case .validateBlocks: return "validate: "
case .scanBlocks: return "scan: "
case .enhancement: return "enhancement: "
case .fetchUTXOs: return "fetchUTXOs: "
@ -84,10 +83,7 @@ class SyncBlocksViewController: UIViewController {
progressBar.progress = progress
progressLabel.text = "\(floor(progress * 1000) / 10)%"
let syncedDate = dateFormatter.string(from: Date(timeIntervalSince1970: state.latestScannedTime))
let progressText = """
synced date \(syncedDate)
synced block \(state.latestScannedHeight)
latest block height \(state.latestBlockHeight)
"""
progressDataLabel.text = progressText
@ -120,7 +116,6 @@ class SyncBlocksViewController: UIViewController {
let cumulativeSummary = synchronizer.metrics.cumulativeSummary()
let downloadedBlocksReport = cumulativeSummary.downloadedBlocksReport ?? SDKMetrics.ReportSummary.zero
let validatedBlocksReport = cumulativeSummary.validatedBlocksReport ?? SDKMetrics.ReportSummary.zero
let scannedBlocksReport = cumulativeSummary.scannedBlocksReport ?? SDKMetrics.ReportSummary.zero
let enhancementReport = cumulativeSummary.enhancementReport ?? SDKMetrics.ReportSummary.zero
let fetchUTXOsReport = cumulativeSummary.fetchUTXOsReport ?? SDKMetrics.ReportSummary.zero
@ -130,7 +125,6 @@ class SyncBlocksViewController: UIViewController {
"""
Summary:
downloadedBlocks: min: \(downloadedBlocksReport.minTime) max: \(downloadedBlocksReport.maxTime) avg: \(downloadedBlocksReport.avgTime)
validatedBlocks: min: \(validatedBlocksReport.minTime) max: \(validatedBlocksReport.maxTime) avg: \(validatedBlocksReport.avgTime)
scannedBlocks: min: \(scannedBlocksReport.minTime) max: \(scannedBlocksReport.maxTime) avg: \(scannedBlocksReport.avgTime)
enhancement: min: \(enhancementReport.minTime) max: \(enhancementReport.maxTime) avg: \(enhancementReport.avgTime)
fetchUTXOs: min: \(fetchUTXOsReport.minTime) max: \(fetchUTXOsReport.maxTime) avg: \(fetchUTXOsReport.avgTime)
@ -153,7 +147,8 @@ class SyncBlocksViewController: UIViewController {
do {
_ = try await synchronizer.prepare(
with: DemoAppConfig.defaultSeed,
walletBirthday: DemoAppConfig.defaultBirthdayHeight
walletBirthday: DemoAppConfig.defaultBirthdayHeight,
for: .existingWallet
)
} catch {
loggerProxy.error(error.toZcashError().message)
@ -258,18 +253,10 @@ struct ProcessorMetrics {
static func accumulate(_ prev: ProcessorMetrics, current: SDKMetrics.BlockMetricReport) -> Self {
.init(
minHeight: min(prev.minHeight, current.startHeight),
maxHeight: max(prev.maxHeight, current.progressHeight),
maxDuration: compareDuration(
prev.maxDuration,
(current.duration, current.progressHeight - current.batchSize ... current.progressHeight),
max
),
minDuration: compareDuration(
prev.minDuration,
(current.duration, current.progressHeight - current.batchSize ... current.progressHeight),
min
),
minHeight: prev.minHeight,
maxHeight: prev.maxHeight,
maxDuration: prev.maxDuration,
minDuration: prev.minDuration,
cumulativeDuration: prev.cumulativeDuration + current.duration,
measuredCount: prev.measuredCount + 1
)
@ -315,8 +302,6 @@ extension SDKMetrics.BlockMetricReport: CustomDebugStringConvertible {
public var debugDescription: String {
"""
BlockMetric:
startHeight: \(self.progressHeight - self.batchSize)
endHeight: \(self.progressHeight)
batchSize: \(self.batchSize)
duration: \(self.duration)
"""

View File

@ -82,13 +82,7 @@ class MainTableViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? TransactionsTableViewController {
if let id = segue.identifier, id == "Pending" {
destination.datasource = TransactionsDataSource(
status: .pending,
synchronizer: AppDelegate.shared.sharedSynchronizer
)
destination.title = "Pending Transactions"
} else if let id = segue.identifier, id == "Sent" {
if let id = segue.identifier, id == "Sent" {
destination.datasource = TransactionsDataSource(
status: .sent,
synchronizer: AppDelegate.shared.sharedSynchronizer

View File

@ -113,7 +113,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "1411d9a839a62523997dae113150b2beccd6b3fc"
"revision" : "8607dc26a637697e53e0be1fb09b81cba9d8475a",
"version" : "0.4.0-rc.2"
}
}
],

View File

@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.14.0"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "1411d9a839a62523997dae113150b2beccd6b3fc")
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", from: "0.4.0-rc.2")
],
targets: [
.target(

View File

@ -87,6 +87,12 @@ For more details look the Sample App's `AppDelegate` code.
We don't like reinventing the wheel, so we gently borrowed swift lint rules from AirBnB which we find pretty cool and reasonable.
# Unstable features
## `Spend before Sync` synchronization algorithm
The CompactBlockProcessor is responsible for downloading and processing blocks from the lightwalletd. Since the inception of the SDK the blocks were processed in a linear order up to the chain tip. Latests SDK has introduced brand new algorithm for syncing of the blocks. It's called `Spend before Sync` and processes blocks in non-linear order so the spendable funds are discovered as soon as possible - allowing users to create a transaction while still syncing.
# Versioning
This project follows [semantic versioning](https://semver.org/) with pre-release versions. An example of a valid version number is `1.0.4-alpha11` denoting the `11th` iteration of the `alpha` pre-release of version `1.0.4`. Stable releases, such as `1.0.4` will not contain any pre-release identifiers. Pre-releases include the following, in order of stability: `alpha`, `beta`, `rc`. Version codes offer a numeric representation of the build name that always increases. The first six significant digits represent the major, minor and patch number (two digits each) and the last 3 significant digits represent the pre-release identifier. The first digit of the identifier signals the build type. Lastly, each new build has a higher version code than all previous builds. The following table breaks this down:

View File

@ -7,11 +7,36 @@
import Foundation
actor ActionContext {
protocol ActionContext {
var state: CBPState { get async }
var prevState: CBPState? { get async }
var syncControlData: SyncControlData { get async }
var requestedRewindHeight: BlockHeight? { get async }
var processedHeight: BlockHeight { get async }
var lastChainTipUpdateTime: TimeInterval { get async }
var lastScannedHeight: BlockHeight? { get async }
var lastEnhancedHeight: BlockHeight? { get async }
func update(state: CBPState) async
func update(syncControlData: SyncControlData) async
func update(processedHeight: BlockHeight) async
func update(lastChainTipUpdateTime: TimeInterval) async
func update(lastScannedHeight: BlockHeight) async
func update(lastDownloadedHeight: BlockHeight) async
func update(lastEnhancedHeight: BlockHeight?) async
func update(requestedRewindHeight: BlockHeight) async
}
actor ActionContextImpl: ActionContext {
var state: CBPState
var prevState: CBPState?
var syncControlData: SyncControlData
var totalProgressRange: CompactBlockRange = 0...0
var requestedRewindHeight: BlockHeight?
/// Amount of blocks that have been processed so far
var processedHeight: BlockHeight = 0
/// Update chain tip must be called repeatadly, this value stores the previous update and help to decide when to call it again
var lastChainTipUpdateTime: TimeInterval = 0.0
var lastScannedHeight: BlockHeight?
var lastDownloadedHeight: BlockHeight?
var lastEnhancedHeight: BlockHeight?
@ -25,16 +50,22 @@ actor ActionContext {
self.state = state
}
func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
func update(totalProgressRange: CompactBlockRange) async { self.totalProgressRange = totalProgressRange }
func update(processedHeight: BlockHeight) async { self.processedHeight = processedHeight }
func update(lastChainTipUpdateTime: TimeInterval) async { self.lastChainTipUpdateTime = lastChainTipUpdateTime }
func update(lastScannedHeight: BlockHeight) async { self.lastScannedHeight = lastScannedHeight }
func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
func update(requestedRewindHeight: BlockHeight) async { self.requestedRewindHeight = requestedRewindHeight }
}
enum CBPState: CaseIterable {
case idle
case migrateLegacyCacheDB
case validateServer
case computeSyncControlData
case updateSubtreeRoots
case updateChainTip
case processSuggestedScanRanges
case rewind
case download
case scan
case clearAlreadyScannedBlocks

View File

@ -21,7 +21,10 @@ extension ClearAlreadyScannedBlocksAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
guard let lastScannedHeight = await context.lastScannedHeight else {
throw ZcashError.compactBlockProcessorLastScannedHeight
}
try await storage.clear(upTo: lastScannedHeight)
await context.update(state: .enhance)

View File

@ -20,11 +20,9 @@ extension ClearCacheAction: Action {
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
try await storage.clear()
if await context.prevState == .idle {
await context.update(state: .migrateLegacyCacheDB)
} else {
await context.update(state: .finished)
}
await context.update(state: .processSuggestedScanRanges)
return context
}

View File

@ -1,82 +0,0 @@
//
// ComputeSyncControlDataAction.swift
//
//
// Created by Michal Fousek on 05.05.2023.
//
import Foundation
final class ComputeSyncControlDataAction {
let configProvider: CompactBlockProcessor.ConfigProvider
let downloaderService: BlockDownloaderService
let latestBlocksDataProvider: LatestBlocksDataProvider
let logger: Logger
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
downloaderService = container.resolve(BlockDownloaderService.self)
latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
logger = container.resolve(Logger.self)
}
}
extension ComputeSyncControlDataAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
let config = await configProvider.config
await latestBlocksDataProvider.updateScannedData()
await latestBlocksDataProvider.updateBlockData()
await latestBlocksDataProvider.updateUnenhancedData()
// Here we know:
// - latest scanned height from the DB, if none the wallet's birthday is automatically used
// - first unenhanced height from the DB, could be nil = up to latestScannedHeight nothing to enhance
// - latest downloaded height reported by downloaderService
// - latest block height on the blockchain
// - wallet birthday for the initial scan
let latestBlockHeight = await latestBlocksDataProvider.latestBlockHeight
let latestScannedHeightDB = await latestBlocksDataProvider.latestScannedHeight
let latestScannedHeight = latestScannedHeightDB < config.walletBirthday ? config.walletBirthday : latestScannedHeightDB
let firstUnenhancedHeight = await latestBlocksDataProvider.firstUnenhancedHeight
let enhanceStart: BlockHeight
if let firstUnenhancedHeight {
enhanceStart = min(firstUnenhancedHeight, latestScannedHeight)
} else {
enhanceStart = latestScannedHeight
}
logger.debug("""
Init numbers:
latestBlockHeight [BC]: \(latestBlockHeight)
latestScannedHeight [DB]: \(latestScannedHeight)
firstUnenhancedHeight [DB]: \(firstUnenhancedHeight ?? -1)
enhanceStart: \(enhanceStart)
walletBirthday: \(config.walletBirthday)
""")
let syncControlData = SyncControlData(
latestBlockHeight: latestBlockHeight,
latestScannedHeight: latestScannedHeight,
firstUnenhancedHeight: enhanceStart
)
await context.update(lastDownloadedHeight: latestScannedHeight)
await context.update(syncControlData: syncControlData)
await context.update(totalProgressRange: latestScannedHeight...latestBlockHeight)
// if there is nothing sync just switch to finished state
if latestBlockHeight < latestScannedHeight || latestBlockHeight == latestScannedHeight {
await context.update(state: .finished)
} else {
await context.update(state: .fetchUTXO)
}
return context
}
func stop() async { }
}

View File

@ -30,16 +30,15 @@ extension DownloadAction: Action {
var removeBlocksCacheWhenFailed: Bool { true }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
guard let lastScannedHeight = await context.lastScannedHeight else {
return await update(context: context)
}
let config = await configProvider.config
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
let latestBlockHeight = await context.syncControlData.latestBlockHeight
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
// So the right range for this batch must be computed.
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
let batchRangeStart = lastScannedHeight
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
guard batchRangeStart <= batchRangeEnd else {
@ -47,10 +46,12 @@ extension DownloadAction: Action {
}
let batchRange = batchRangeStart...batchRangeEnd
let downloadLimit = batchRange.upperBound + (2 * config.batchSize)
let potentialDownloadLimit = batchRange.upperBound + (2 * config.batchSize)
let downloadLimit = await context.syncControlData.latestBlockHeight >= potentialDownloadLimit ? potentialDownloadLimit : batchRangeEnd
logger.debug("Starting download with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
await downloader.update(latestDownloadedBlockHeight: batchRange.lowerBound)
logger.debug("Starting download with range: \(batchRangeStart)...\(batchRangeEnd)")
await downloader.update(latestDownloadedBlockHeight: batchRangeStart, force: true)
try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
await downloader.setDownloadLimit(downloadLimit)
await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)

View File

@ -11,13 +11,11 @@ final class EnhanceAction {
let blockEnhancer: BlockEnhancer
let configProvider: CompactBlockProcessor.ConfigProvider
let logger: Logger
let transactionRepository: TransactionRepository
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
blockEnhancer = container.resolve(BlockEnhancer.self)
self.configProvider = configProvider
logger = container.resolve(Logger.self)
transactionRepository = container.resolve(TransactionRepository.self)
}
func decideWhatToDoNext(context: ActionContext, lastScannedHeight: BlockHeight) async -> ActionContext {
@ -30,7 +28,7 @@ final class EnhanceAction {
if lastScannedHeight >= latestBlockHeight {
await context.update(state: .clearCache)
} else {
await context.update(state: .download)
await context.update(state: .updateChainTip)
}
return context
@ -49,7 +47,9 @@ extension EnhanceAction: Action {
// download and scan.
let config = await configProvider.config
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
guard let lastScannedHeight = await context.lastScannedHeight else {
throw ZcashError.compactBlockProcessorLastScannedHeight
}
guard let firstUnenhancedHeight = await context.syncControlData.firstUnenhancedHeight else {
return await decideWhatToDoNext(context: context, lastScannedHeight: lastScannedHeight)
@ -79,7 +79,6 @@ extension EnhanceAction: Action {
didEnhance: { progress in
if let foundTx = progress.lastFoundTransaction, progress.newlyMined {
await didUpdate(.minedTransaction(foundTx))
await didUpdate(.progressPartialUpdate(.enhance(progress)))
}
}
)

View File

@ -22,9 +22,7 @@ extension FetchUTXOsAction: Action {
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
logger.debug("Fetching UTXOs")
let result = try await utxoFetcher.fetch() { fetchProgress in
await didUpdate(.progressPartialUpdate(.fetch(fetchProgress)))
}
let result = try await utxoFetcher.fetch() { _ in }
await didUpdate(.storedUTXOs(result))
await context.update(state: .handleSaplingParams)

View File

@ -0,0 +1,59 @@
//
// ProcessSuggestedScanRangesAction.swift
//
//
// Created by Lukáš Korba on 02.08.2023.
//
import Foundation
final class ProcessSuggestedScanRangesAction {
let rustBackend: ZcashRustBackendWelding
let service: LightWalletService
let logger: Logger
init(container: DIContainer) {
service = container.resolve(LightWalletService.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
logger = container.resolve(Logger.self)
}
}
extension ProcessSuggestedScanRangesAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
logger.info("Getting the suggested scan ranges from the wallet database.")
let scanRanges = try await rustBackend.suggestScanRanges()
if let firstRange = scanRanges.first {
let rangeStartExclusive = firstRange.range.lowerBound - 1
let rangeEndInclusive = firstRange.range.upperBound - 1
let syncControlData = SyncControlData(
latestBlockHeight: rangeEndInclusive,
latestScannedHeight: rangeStartExclusive,
firstUnenhancedHeight: rangeStartExclusive + 1
)
logger.debug("""
Init numbers:
latestBlockHeight [BC]: \(rangeEndInclusive)
latestScannedHeight [DB]: \(rangeStartExclusive)
firstUnenhancedHeight [DB]: \(rangeStartExclusive + 1)
""")
await context.update(lastScannedHeight: rangeStartExclusive)
await context.update(lastDownloadedHeight: rangeStartExclusive)
await context.update(syncControlData: syncControlData)
await context.update(state: .download)
} else {
await context.update(state: .finished)
}
return context
}
func stop() async { }
}

View File

@ -0,0 +1,48 @@
//
// RewindAction.swift
//
//
// Created by Lukáš Korba on 09.08.2023.
//
import Foundation
final class RewindAction {
let downloader: BlockDownloader
let rustBackend: ZcashRustBackendWelding
let downloaderService: BlockDownloaderService
let logger: Logger
init(container: DIContainer) {
downloader = container.resolve(BlockDownloader.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
downloaderService = container.resolve(BlockDownloaderService.self)
logger = container.resolve(Logger.self)
}
private func update(context: ActionContext) async -> ActionContext {
await context.update(state: .processSuggestedScanRanges)
return context
}
}
extension RewindAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
guard let rewindHeight = await context.requestedRewindHeight else {
return await update(context: context)
}
logger.debug("Executing rewind.")
await downloader.rewind(latestDownloadedBlockHeight: rewindHeight)
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
// clear cache
try await downloaderService.rewind(to: rewindHeight)
return await update(context: context)
}
func stop() async { }
}

View File

@ -23,7 +23,9 @@ extension SaplingParamsAction: Action {
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
logger.debug("Fetching sapling parameters")
try await saplingParametersHandler.handleIfNeeded()
await context.update(state: .download)
await context.update(state: .updateSubtreeRoots)
return context
}

View File

@ -10,13 +10,13 @@ import Foundation
final class ScanAction {
let configProvider: CompactBlockProcessor.ConfigProvider
let blockScanner: BlockScanner
let rustBackend: ZcashRustBackendWelding
let logger: Logger
let transactionRepository: TransactionRepository
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
blockScanner = container.resolve(BlockScanner.self)
transactionRepository = container.resolve(TransactionRepository.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
logger = container.resolve(Logger.self)
}
@ -30,34 +30,51 @@ extension ScanAction: Action {
var removeBlocksCacheWhenFailed: Bool { true }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
guard let lastScannedHeight = await context.syncControlData.latestScannedHeight else {
guard let lastScannedHeight = await context.lastScannedHeight else {
return await update(context: context)
}
let config = await configProvider.config
let lastScannedHeightDB = try await transactionRepository.lastScannedHeight()
let latestBlockHeight = await context.syncControlData.latestBlockHeight
// This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `scanRange` are scanned.
// So the right range for this batch must be computed.
let batchRangeStart = max(lastScannedHeightDB, lastScannedHeight)
let batchRangeStart = lastScannedHeight
let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
guard batchRangeStart <= batchRangeEnd else {
return await update(context: context)
}
let batchRange = batchRangeStart...batchRangeStart + config.batchSize
let batchRange = batchRangeStart...batchRangeEnd
logger.debug("Starting scan blocks with range: \(batchRange.lowerBound)...\(batchRange.upperBound)")
let totalProgressRange = await context.totalProgressRange
try await blockScanner.scanBlocks(at: batchRange, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
let progress = BlockProgress(
startHeight: totalProgressRange.lowerBound,
targetHeight: totalProgressRange.upperBound,
progressHeight: lastScannedHeight
)
self?.logger.debug("progress: \(progress)")
await didUpdate(.progressPartialUpdate(.syncing(progress)))
do {
try await blockScanner.scanBlocks(at: batchRange) { [weak self] lastScannedHeight, increment in
let processedHeight = await context.processedHeight
let incrementedprocessedHeight = processedHeight + BlockHeight(increment)
await context.update(processedHeight: incrementedprocessedHeight)
// report scan progress only if it's available
if let scanProgress = try? await self?.rustBackend.getScanProgress() {
let progress = try scanProgress.progress()
self?.logger.debug("progress: \(progress)")
await didUpdate(.syncProgress(progress))
}
// ScanAction is controlled locally so it must report back the updated scanned height
await context.update(lastScannedHeight: lastScannedHeight)
}
} catch ZcashError.rustScanBlocks(let errorMsg) {
if isContinuityError(errorMsg) {
await context.update(requestedRewindHeight: batchRange.lowerBound - 10)
await context.update(state: .rewind)
return context
} else {
throw ZcashError.rustScanBlocks(errorMsg)
}
} catch {
throw error
}
return await update(context: context)
@ -65,3 +82,11 @@ extension ScanAction: Action {
func stop() async { }
}
private extension ScanAction {
func isContinuityError(_ errorMsg: String) -> Bool {
errorMsg.contains("The parent hash of proposed block does not correspond to the block hash at height")
|| errorMsg.contains("Block height discontinuity at height")
|| errorMsg.contains("note commitment tree size provided by a compact block did not match the expected size at height")
}
}

View File

@ -0,0 +1,52 @@
//
// UpdateChainTipAction.swift
//
//
// Created by Lukáš Korba on 01.08.2023.
//
import Foundation
final class UpdateChainTipAction {
let rustBackend: ZcashRustBackendWelding
let downloader: BlockDownloader
let service: LightWalletService
let logger: Logger
init(container: DIContainer) {
service = container.resolve(LightWalletService.self)
downloader = container.resolve(BlockDownloader.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
logger = container.resolve(Logger.self)
}
func updateChainTip(_ context: ActionContext, time: TimeInterval) async throws {
let latestBlockHeight = try await service.latestBlockHeight()
logger.info("Latest block height is \(latestBlockHeight)")
try await rustBackend.updateChainTip(height: Int32(latestBlockHeight))
await context.update(lastChainTipUpdateTime: time)
}
}
extension UpdateChainTipAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
let lastChainTipUpdateTime = await context.lastChainTipUpdateTime
let now = Date().timeIntervalSince1970
// Update chain tip can be called from different contexts
if await context.prevState == .updateSubtreeRoots || now - lastChainTipUpdateTime > 600 {
await downloader.stopDownload()
try await updateChainTip(context, time: now)
await context.update(state: .clearCache)
} else if await context.prevState == .enhance {
await context.update(state: .download)
}
return context
}
func stop() async { }
}

View File

@ -0,0 +1,58 @@
//
// UpdateSubtreeRootsAction.swift
//
//
// Created by Lukas Korba on 01.08.2023.
//
import Foundation
final class UpdateSubtreeRootsAction {
let configProvider: CompactBlockProcessor.ConfigProvider
let rustBackend: ZcashRustBackendWelding
let service: LightWalletService
let logger: Logger
init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
self.configProvider = configProvider
service = container.resolve(LightWalletService.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
logger = container.resolve(Logger.self)
}
}
extension UpdateSubtreeRootsAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
var request = GetSubtreeRootsArg()
request.shieldedProtocol = .sapling
logger.info("Attempt to get subtree roots, this may fail because lightwalletd may not support Spend before Sync.")
let stream = service.getSubtreeRoots(request)
var roots: [SubtreeRoot] = []
do {
for try await subtreeRoot in stream {
roots.append(subtreeRoot)
}
} catch ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut) {
throw ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut)
}
logger.info("Sapling tree has \(roots.count) subtrees")
do {
try await rustBackend.putSaplingSubtreeRoots(startIndex: UInt64(request.startIndex), roots: roots)
await context.update(state: .updateChainTip)
} catch {
logger.debug("putSaplingSubtreeRoots failed with error \(error.localizedDescription)")
throw ZcashError.compactBlockProcessorPutSaplingSubtreeRoots(error)
}
return context
}
func stop() async { }
}

View File

@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
}
await context.update(state: .computeSyncControlData)
await context.update(state: .fetchUTXO)
return context
}

View File

@ -175,7 +175,11 @@ actor CompactBlockProcessor {
)
}
init(container: DIContainer, config: Configuration, accountRepository: AccountRepository) {
init(
container: DIContainer,
config: Configuration,
accountRepository: AccountRepository
) {
Dependencies.setupCompactBlockProcessor(
in: container,
config: config,
@ -183,7 +187,7 @@ actor CompactBlockProcessor {
)
let configProvider = ConfigProvider(config: config)
context = ActionContext(state: .idle)
context = ActionContextImpl(state: .idle)
actions = Self.makeActions(container: container, configProvider: configProvider)
self.metrics = container.resolve(SDKMetrics.self)
@ -214,8 +218,14 @@ actor CompactBlockProcessor {
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
case .validateServer:
action = ValidateServerAction(container: container, configProvider: configProvider)
case .computeSyncControlData:
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
case .updateSubtreeRoots:
action = UpdateSubtreeRootsAction(container: container, configProvider: configProvider)
case .updateChainTip:
action = UpdateChainTipAction(container: container)
case .processSuggestedScanRanges:
action = ProcessSuggestedScanRangesAction(container: container)
case .rewind:
action = RewindAction(container: container)
case .download:
action = DownloadAction(container: container, configProvider: configProvider)
case .scan:
@ -307,7 +317,7 @@ extension CompactBlockProcessor {
private func doRewind(context: AfterSyncHooksManager.RewindContext) async throws {
logger.debug("Executing rewind.")
let lastDownloaded = await latestBlocksDataProvider.latestScannedHeight
let lastDownloaded = await latestBlocksDataProvider.maxScannedHeight
let height = Int32(context.height ?? lastDownloaded)
let nearestHeight: Int32
@ -421,7 +431,7 @@ extension CompactBlockProcessor {
case handledReorg(_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight)
/// Event sent when progress of some specific action happened.
case progressPartialUpdate(CompactBlockProgressUpdate)
case syncProgress(Float)
/// Event sent when progress of the sync process changes.
case progressUpdated(Float)
@ -474,7 +484,7 @@ extension CompactBlockProcessor {
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
await stopAllActions()
// Update state to the first state in state machine that can be handled by action.
await context.update(state: .clearCache)
await context.update(state: .migrateLegacyCacheDB)
await syncStarted()
if backoffTimer == nil {
@ -503,7 +513,7 @@ extension CompactBlockProcessor {
// Execute action.
context = try await action.run(with: context) { [weak self] event in
await self?.send(event: event)
if let progressChanged = await self?.compactBlockProgress.event(event), progressChanged {
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
if let progress = await self?.compactBlockProgress.progress {
await self?.send(event: .progressUpdated(progress))
}
@ -558,21 +568,10 @@ extension CompactBlockProcessor {
await ifTaskIsNotCanceledClearCompactBlockCache()
}
if case let ZcashError.rustValidateCombinedChainInvalidChain(height) = error {
logger.error("Sync failed because of validation error: \(error)")
do {
try await validationFailed(at: BlockHeight(height))
// Start sync all over again
return true
} catch {
await failure(error)
return false
}
} else {
logger.error("Sync failed with error: \(error)")
await failure(error)
return false
}
logger.error("Sync failed with error: \(error)")
await failure(error)
return false
}
// swiftlint:disable:next cyclomatic_complexity
@ -585,7 +584,13 @@ extension CompactBlockProcessor {
break
case .validateServer:
break
case .computeSyncControlData:
case .updateSubtreeRoots:
break
case .updateChainTip:
break
case .processSuggestedScanRanges:
break
case .rewind:
break
case .download:
break
@ -612,9 +617,8 @@ extension CompactBlockProcessor {
private func resetContext() async {
let lastEnhancedheight = await context.lastEnhancedHeight
context = ActionContext(state: .idle)
context = ActionContextImpl(state: .idle)
await context.update(lastEnhancedHeight: lastEnhancedheight)
await compactBlockProgress.reset()
}
private func syncStarted() async {
@ -634,7 +638,7 @@ extension CompactBlockProcessor {
retryAttempts = 0
consecutiveChainValidationErrors = 0
let lastScannedHeight = await latestBlocksDataProvider.latestScannedHeight
let lastScannedHeight = await latestBlocksDataProvider.maxScannedHeight
// Some actions may not run. For example there are no transactions to enhance and therefore there is no enhance progress. And in
// cases like this computation of final progress won't work properly. So let's fake 100% progress at the end of the sync process.
await send(event: .progressUpdated(1))
@ -650,25 +654,6 @@ extension CompactBlockProcessor {
}
}
private func validationFailed(at height: BlockHeight) async throws {
// rewind
let rewindHeight = determineLowerBound(
errorHeight: height,
consecutiveErrors: consecutiveChainValidationErrors,
walletBirthday: config.walletBirthday
)
consecutiveChainValidationErrors += 1
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
try await blockDownloaderService.rewind(to: rewindHeight)
try await rewindDownloadBlockAction(to: rewindHeight)
await send(event: .handledReorg(height, rewindHeight))
}
private func failure(_ error: Error) async {
await context.update(state: .failed)
@ -720,7 +705,6 @@ extension CompactBlockProcessor {
"""
Timer triggered: Starting compact Block processor!.
Processor State: \(await self.context.state)
latestHeight: \(try await self.transactionRepository.lastScannedHeight())
attempts: \(await self.retryAttempts)
"""
)

View File

@ -55,7 +55,7 @@ protocol BlockDownloader {
/// Updates the internal in memory value of latest downloaded block height. This way the `BlockDownloader` works with the current latest height and can
/// continue on parallel downloading of next batch.
func update(latestDownloadedBlockHeight: BlockHeight) async
func update(latestDownloadedBlockHeight: BlockHeight, force: Bool) async
/// Provides the value of latest downloaded height.
func latestDownloadedBlockHeight() async -> BlockHeight
/// In case rewind is needed, the latestDownloadedBlockHeight is rewritten forcefully.
@ -199,13 +199,8 @@ actor BlockDownloaderImpl {
var counter = 0
var lastDownloadedBlockHeight = -1
let pushMetrics: (BlockHeight, Date, Date) -> Void = { [metrics] lastDownloadedBlockHeight, startTime, finishTime in
let pushMetrics: (BlockHeight, Date, Date) -> Void = { [metrics] _, startTime, finishTime in
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: totalProgressRange.lowerBound,
targetHeight: totalProgressRange.upperBound,
progressHeight: Int(lastDownloadedBlockHeight)
),
start: startTime,
end: finishTime,
batchSize: maxBlockBufferSize,
@ -253,8 +248,8 @@ extension BlockDownloaderImpl: BlockDownloader {
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight ?? -1
}
func update(latestDownloadedBlockHeight: BlockHeight) async {
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight {
func update(latestDownloadedBlockHeight: BlockHeight, force: Bool = false) async {
if latestDownloadedBlockHeight >= self.latestDownloadedBlockHeight || force {
self.latestDownloadedBlockHeight = latestDownloadedBlockHeight
}
}

View File

@ -135,11 +135,6 @@ extension BlockEnhancerImpl: BlockEnhancer {
}
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: range.lowerBound,
targetHeight: range.upperBound,
progressHeight: range.upperBound
),
start: startTime,
end: Date(),
batchSize: range.count,

View File

@ -86,11 +86,6 @@ extension UTXOFetcherImpl: UTXOFetcher {
}
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: 0,
targetHeight: 1,
progressHeight: 1
),
start: startTime,
end: Date(),
batchSize: 1,

View File

@ -16,8 +16,7 @@ protocol BlockScanner {
@discardableResult
func scanBlocks(
at range: CompactBlockRange,
totalProgressRange: CompactBlockRange,
didScan: @escaping (BlockHeight) async -> Void
didScan: @escaping (BlockHeight, UInt32) async throws -> Void
) async throws -> BlockHeight
}
@ -27,20 +26,18 @@ struct BlockScannerImpl {
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let logger: Logger
let latestBlocksDataProvider: LatestBlocksDataProvider
}
extension BlockScannerImpl: BlockScanner {
@discardableResult
func scanBlocks(
at range: CompactBlockRange,
totalProgressRange: CompactBlockRange,
didScan: @escaping (BlockHeight) async -> Void
didScan: @escaping (BlockHeight, UInt32) async throws -> Void
) async throws -> BlockHeight {
logger.debug("Going to scan blocks in range: \(range)")
try Task.checkCancellation()
let scanStartHeight = try await transactionRepository.lastScannedHeight()
let scanStartHeight = range.lowerBound
let targetScanHeight = range.upperBound
var scannedNewBlocks = false
@ -65,24 +62,15 @@ extension BlockScannerImpl: BlockScanner {
let scanFinishTime = Date()
if let lastScannedBlock = try await transactionRepository.lastScannedBlock() {
lastScannedHeight = lastScannedBlock.height
await latestBlocksDataProvider.updateLatestScannedHeight(lastScannedHeight)
await latestBlocksDataProvider.updateLatestScannedTime(TimeInterval(lastScannedBlock.time))
}
// TODO: [#1259] potential bug when rustBackend.scanBlocks scan less blocks than batchSize,
// https://github.com/zcash/ZcashLightClientKit/issues/1259
lastScannedHeight = startHeight + Int(batchSize) - 1
scannedNewBlocks = previousScannedHeight != lastScannedHeight
if scannedNewBlocks {
await didScan(lastScannedHeight)
let progress = BlockProgress(
startHeight: totalProgressRange.lowerBound,
targetHeight: totalProgressRange.upperBound,
progressHeight: lastScannedHeight
)
try await didScan(lastScannedHeight, batchSize)
metrics.pushProgressReport(
progress: progress,
start: scanStartTime,
end: scanFinishTime,
batchSize: Int(batchSize),

View File

@ -9,56 +9,16 @@ import Foundation
final actor CompactBlockProgress {
static let zero = CompactBlockProgress()
enum Action: Equatable {
case enhance
case fetch
case scan
func weight() -> Float {
switch self {
case .enhance: return 0.08
case .fetch: return 0.02
case .scan: return 0.9
}
}
}
var actionProgresses: [Action: Float] = [:]
var progress: Float {
var overallProgress = Float(0)
actionProgresses.forEach { key, value in
overallProgress += value * key.weight()
}
return overallProgress
}
func event(_ event: CompactBlockProcessor.Event) -> Bool {
guard case .progressPartialUpdate(let update) = event else {
var progress: Float = 0.0
func hasProgressUpdated(_ event: CompactBlockProcessor.Event) -> Bool {
guard case .syncProgress(let update) = event else {
return false
}
switch update {
case .syncing(let progress):
actionProgresses[.scan] = progress.progress
case .enhance(let progress):
actionProgresses[.enhance] = progress.progress
case .fetch(let progress):
actionProgresses[.fetch] = progress
}
progress = update
return true
}
func reset() {
actionProgresses.removeAll()
}
}
enum CompactBlockProgressUpdate: Equatable {
case syncing(_ progress: BlockProgress)
case enhance(_ progress: EnhancementProgress)
case fetch(_ progress: Float)
}

View File

@ -25,6 +25,7 @@ public protocol ClosureSynchronizer {
func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight,
for walletMode: WalletInitMode,
completion: @escaping (Result<Initializer.InitializationResult, Error>) -> Void
)
@ -50,7 +51,6 @@ public protocol ClosureSynchronizer {
completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
)
func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func sentTranscations(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)

View File

@ -24,7 +24,8 @@ public protocol CombineSynchronizer {
func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight
walletBirthday: BlockHeight,
for walletMode: WalletInitMode
) -> SinglePublisher<Initializer.InitializationResult, Error>
func start(retry: Bool) -> CompletablePublisher<Error>
@ -48,7 +49,6 @@ public protocol CombineSynchronizer {
) -> SinglePublisher<ZcashTransaction.Overview, Error>
var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var pendingTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
@ -58,8 +58,6 @@ public protocol CombineSynchronizer {
func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never>
func allPendingTransactions() -> SinglePublisher<[ZcashTransaction.Overview], Error>
func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error>
func latestHeight() -> SinglePublisher<BlockHeight, Error>

View File

@ -1,195 +0,0 @@
//
// BlockDao.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
protocol BlockDao {
func latestBlockHeight() throws -> BlockHeight
func latestBlock() throws -> Block?
func block(at height: BlockHeight) throws -> Block?
}
struct Block: Codable {
enum CodingKeys: String, CodingKey {
case height
case hash
case time
case saplingTree = "sapling_tree"
}
enum TableStructure {
static let height = Expression<Int>(Block.CodingKeys.height.rawValue)
static let hash = Expression<Blob>(Block.CodingKeys.hash.rawValue)
static let time = Expression<Int>(Block.CodingKeys.time.rawValue)
static let saplingTree = Expression<Blob>(Block.CodingKeys.saplingTree.rawValue)
}
let height: BlockHeight
let hash: Data
let time: Int
let saplingTree: Data
static let table = Table("blocks")
}
struct VTransaction: Codable {
enum CodingKeys: String, CodingKey {
case accountId = "account_id"
case idTx = "id_tx"
case minedHeight = "mined_height"
case txIndex = "tx_index"
case txId = "txid"
case expiryHeight = "expiry_height"
case raw = "raw"
case accountBalanceDelta = "account_balance_delta"
case feePaid = "fee_paid"
case expiredUnmined = "expired_unmined"
case hasChange = "has_change"
case sentNoteCount = "sent_note_count"
case recievedNoteCount = "received_note_count"
case memoCount = "memo_count"
case blockTime = "block_time"
}
enum TableStructure {
static let accountId = Expression<Int>(VTransaction.CodingKeys.accountId.rawValue)
static let idTx = Expression<Int>(VTransaction.CodingKeys.idTx.rawValue)
static let minedHeight = Expression<Int>(VTransaction.CodingKeys.minedHeight.rawValue)
static let txIndex = Expression<Int>(VTransaction.CodingKeys.txIndex.rawValue)
static let txId = Expression<Data>(VTransaction.CodingKeys.txId.rawValue)
static let expiryHeight = Expression<Int?>(VTransaction.CodingKeys.expiryHeight.rawValue)
static let raw = Expression<Data?>(VTransaction.CodingKeys.raw.rawValue)
static let accountBalanceDelta = Expression<Int>(VTransaction.CodingKeys.accountBalanceDelta.rawValue)
static let feePaid = Expression<Int?>(VTransaction.CodingKeys.feePaid.rawValue)
static let expiredUnmined = Expression<Int>(VTransaction.CodingKeys.expiredUnmined.rawValue)
static let hasChange = Expression<Bool>(VTransaction.CodingKeys.hasChange.rawValue)
static let sentNoteCount = Expression<Int>(VTransaction.CodingKeys.sentNoteCount.rawValue)
static let recievedNoteCount = Expression<Int>(VTransaction.CodingKeys.recievedNoteCount.rawValue)
static let memoCount = Expression<Int>(VTransaction.CodingKeys.memoCount.rawValue)
static let blockTime = Expression<Int>(VTransaction.CodingKeys.blockTime.rawValue)
}
let accountId: Int
let idTx: Int
let minedHeight: Int
let txIndex: Int
let txId: Data
let expiryHeight: Int?
let raw: Data?
let accountBalanceDelta: Int
let feePaid: Int?
let expiredUnmined: Int
let hasChange: Bool
let sentNoteCount: Int
let recievedNoteCount: Int
let memoCount: Int
let blockTime: Int
static let table = Table("v_transactions")
}
class BlockSQLDAO: BlockDao {
let dbProvider: ConnectionProvider
let table: Table
let height = Expression<Int>("height")
let minedHeight = Expression<Int>("mined_height")
let raw = Expression<Data?>("raw")
init(dbProvider: ConnectionProvider) {
self.dbProvider = dbProvider
self.table = Table("Blocks")
}
/// - Throws:
/// - `blockDAOCantDecode` if block data loaded from DB can't be decoded to `Block` object.
/// - `blockDAOBlock` if sqlite query to load block metadata failed.
func block(at height: BlockHeight) throws -> Block? {
do {
return try dbProvider
.connection()
.prepare(Block.table.filter(Block.TableStructure.height == height).limit(1))
.map {
do {
return try $0.decode()
} catch {
throw ZcashError.blockDAOCantDecode(error)
}
}
.first
} catch {
if let error = error as? ZcashError {
throw error
} else {
throw ZcashError.blockDAOBlock(error)
}
}
}
/// - Throws: `blockDAOLatestBlockHeight` if sqlite to fetch height fails.
func latestBlockHeight() throws -> BlockHeight {
do {
return try dbProvider.connection().scalar(table.select(height.max)) ?? BlockHeight.empty()
} catch {
throw ZcashError.blockDAOLatestBlockHeight(error)
}
}
func latestBlock() throws -> Block? {
do {
return try dbProvider
.connection()
.prepare(Block.table.order(height.desc).limit(1))
.map {
do {
return try $0.decode()
} catch {
throw ZcashError.blockDAOLatestBlockCantDecode(error)
}
}
.first
} catch {
if let error = error as? ZcashError {
throw error
} else {
throw ZcashError.blockDAOLatestBlock(error)
}
}
}
func firstUnenhancedHeight(in range: CompactBlockRange? = nil) throws -> BlockHeight? {
do {
return try dbProvider
.connection()
.prepare(
VTransaction.table
.order(minedHeight.asc)
.filter(raw == nil)
.limit(1)
)
.map {
do {
let vTransaction: VTransaction = try $0.decode()
return vTransaction.minedHeight
} catch {
throw ZcashError.blockDAOFirstUnenhancedCantDecode(error)
}
}
.first
} catch {
throw ZcashError.blockDAOFirstUnenhancedHeight(error)
}
}
}
extension BlockSQLDAO: BlockRepository {
func lastScannedBlockHeight() -> BlockHeight {
(try? self.latestBlockHeight()) ?? BlockHeight.empty()
}
}

View File

@ -16,14 +16,12 @@ class TransactionSQLDAO: TransactionRepository {
let dbProvider: ConnectionProvider
private let blockDao: BlockSQLDAO
private let transactionsView = View("v_transactions")
private let txOutputsView = View("v_tx_outputs")
private let traceClosure: ((String) -> Void)?
init(dbProvider: ConnectionProvider, traceClosure: ((String) -> Void)? = nil) {
self.dbProvider = dbProvider
self.blockDao = BlockSQLDAO(dbProvider: dbProvider)
self.traceClosure = traceClosure
}
@ -37,22 +35,6 @@ class TransactionSQLDAO: TransactionRepository {
dbProvider.close()
}
func blockForHeight(_ height: BlockHeight) async throws -> Block? {
try blockDao.block(at: height)
}
func lastScannedHeight() async throws -> BlockHeight {
try blockDao.latestBlockHeight()
}
func lastScannedBlock() async throws -> Block? {
try blockDao.latestBlock()
}
func firstUnenhancedHeight() throws -> BlockHeight? {
try blockDao.firstUnenhancedHeight()
}
func isInitialized() async throws -> Bool {
true
}

View File

@ -1,24 +0,0 @@
//
// BlockProgress.swift
//
//
// Created by Michal Fousek on 03.02.2023.
//
import Foundation
public struct BlockProgress: Equatable {
public let startHeight: BlockHeight
public let targetHeight: BlockHeight
public let progressHeight: BlockHeight
public var progress: Float {
let overall = self.targetHeight - self.startHeight
return overall > 0 ? Float((self.progressHeight - self.startHeight)) / Float(overall) : 0
}
}
public extension BlockProgress {
static let nullProgress = BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)
}

View File

@ -3,7 +3,7 @@
scriptDir=${0:a:h}
cd "${scriptDir}"
sourcery_version=2.0.2
sourcery_version=2.0.3
if which sourcery >/dev/null; then
if [[ $(sourcery --version) != $sourcery_version ]]; then

View File

@ -1,4 +1,4 @@
// Generated using Sourcery 2.0.2 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.0.3 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
/*
@ -58,6 +58,9 @@ public enum ZcashError: Equatable, Error {
/// LightWalletService.blockStream failed.
/// ZSRVC0000
case serviceBlockStreamFailed(_ error: LightWalletServiceError)
/// LightWalletService.getSubtreeRoots failed.
/// ZSRVC0009
case serviceSubtreeRootsStreamFailed(_ error: LightWalletServiceError)
/// SimpleConnectionProvider init of Connection failed.
/// ZSCPC0001
case simpleConnectionProvider(_ error: Error)
@ -220,10 +223,6 @@ public enum ZcashError: Equatable, Error {
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0030
case rustValidateCombinedChainValidationFailed(_ rustError: String)
/// Error from rust layer which means that combined chain isn't valid.
/// - `upperBound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct).
/// ZRUST0031
case rustValidateCombinedChainInvalidChain(_ upperBound: Int32)
/// Error from rust layer when calling ZcashRustBackend.rewindToHeight
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0032
@ -277,21 +276,25 @@ public enum ZcashError: Equatable, Error {
/// ZRUST0045
case rustGetTransparentReceiverInvalidReceiver
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
/// sourcery: code="ZRUST0046"
/// ZRUST0046
case rustPutSaplingSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0047"
/// ZRUST0047
case rustPutSaplingSubtreeRoots(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0048"
/// ZRUST0048
case rustUpdateChainTip(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0049"
/// ZRUST0049
case rustSuggestScanRanges(_ rustError: String)
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes.
/// ZRUST0050
case rustGetMemoInvalidTxIdLength
/// Error from rust layer when calling ZcashRustBackend.getScanProgress
@ -310,6 +313,10 @@ public enum ZcashError: Equatable, Error {
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0054
case rustLatestCachedBlockHeight(_ rustError: String)
/// Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%.
/// - `progress` value reported
/// ZRUST0055
case rustScanProgressOutOfRange(_ progress: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
@ -546,6 +553,15 @@ public enum ZcashError: Equatable, Error {
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
/// ZCBPEO0018
case compactBlockProcessorDownloadBlockActionRewind
/// Put sapling subtree roots to the DB failed.
/// ZCBPEO0019
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
/// ZCBPEO0020
case compactBlockProcessorLastScannedHeight
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
/// ZCBPEO0021
case compactBlockProcessorSupportedSyncAlgorithm
/// The synchronizer is unprepared.
/// ZSYNCO0001
case synchronizerNotPrepared
@ -582,6 +598,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return "LightWalletService.fetchTransaction failed."
case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed."
case .serviceBlockStreamFailed: return "LightWalletService.blockStream failed."
case .serviceSubtreeRootsStreamFailed: return "LightWalletService.getSubtreeRoots failed."
case .simpleConnectionProvider: return "SimpleConnectionProvider init of Connection failed."
case .saplingParamsInvalidSpendParams: return "Downloaded file with sapling spending parameters isn't valid."
case .saplingParamsInvalidOutputParams: return "Downloaded file with sapling output parameters isn't valid."
@ -624,7 +641,6 @@ public enum ZcashError: Equatable, Error {
case .rustListTransparentReceiversInvalidAddress: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.listTransparentReceivers"
case .rustPutUnspentTransparentOutput: return "Error from rust layer when calling ZcashRustBackend.putUnspentTransparentOutput"
case .rustValidateCombinedChainValidationFailed: return "Error unrelated to chain validity from rust layer when calling ZcashRustBackend.validateCombinedChain"
case .rustValidateCombinedChainInvalidChain: return "Error from rust layer which means that combined chain isn't valid."
case .rustRewindToHeight: return "Error from rust layer when calling ZcashRustBackend.rewindToHeight"
case .rustRewindCacheToHeight: return "Error from rust layer when calling ZcashRustBackend.rewindCacheToHeight"
case .rustScanBlocks: return "Error from rust layer when calling ZcashRustBackend.scanBlocks"
@ -639,15 +655,16 @@ public enum ZcashError: Equatable, Error {
case .rustGetSaplingReceiverInvalidReceiver: return "Sapling receiver generated by rust layer is invalid when calling ZcashRustBackend.getSaplingReceiver"
case .rustGetTransparentReceiverInvalidAddress: return "Error from rust layer when calling ZcashRustBackend.getTransparentReceiver"
case .rustGetTransparentReceiverInvalidReceiver: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver"
case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to store subtree roots when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustPutSaplingSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip"
case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges"
case .rustGetMemoInvalidTxIdLength: return "txId must be 32 bytes"
case .rustGetMemoInvalidTxIdLength: return "Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes."
case .rustGetScanProgress: return "Error from rust layer when calling ZcashRustBackend.getScanProgress"
case .rustFullyScannedHeight: return "Error from rust layer when calling ZcashRustBackend.fullyScannedHeight"
case .rustMaxScannedHeight: return "Error from rust layer when calling ZcashRustBackend.maxScannedHeight"
case .rustLatestCachedBlockHeight: return "Error from rust layer when calling ZcashRustBackend.latestCachedBlockHeight"
case .rustScanProgressOutOfRange: return "Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%."
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
@ -723,6 +740,9 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorChainName: return "Chain name does not match. Expected either 'test' or 'main'. This is probably an API or programming error."
case .compactBlockProcessorConsensusBranchID: return "Consensus BranchIDs don't match this is probably an API or programming error."
case .compactBlockProcessorDownloadBlockActionRewind: return "Rewind of DownloadBlockAction failed as no action is possible to unwrapp."
case .compactBlockProcessorPutSaplingSubtreeRoots: return "Put sapling subtree roots to the DB failed."
case .compactBlockProcessorLastScannedHeight: return "Getting the `lastScannedHeight` failed but it's supposed to always provide some value."
case .compactBlockProcessorSupportedSyncAlgorithm: return "Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value."
case .synchronizerNotPrepared: return "The synchronizer is unprepared."
case .synchronizerSendMemoToTransparentAddress: return "Memos can't be sent to transparent addresses."
case .synchronizerShieldFundsInsuficientTransparentFunds: return "There is not enough transparent funds to cover fee for the shielding."
@ -749,6 +769,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return .serviceFetchTransactionFailed
case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed
case .serviceBlockStreamFailed: return .serviceBlockStreamFailed
case .serviceSubtreeRootsStreamFailed: return .serviceSubtreeRootsStreamFailed
case .simpleConnectionProvider: return .simpleConnectionProvider
case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams
case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams
@ -791,7 +812,6 @@ public enum ZcashError: Equatable, Error {
case .rustListTransparentReceiversInvalidAddress: return .rustListTransparentReceiversInvalidAddress
case .rustPutUnspentTransparentOutput: return .rustPutUnspentTransparentOutput
case .rustValidateCombinedChainValidationFailed: return .rustValidateCombinedChainValidationFailed
case .rustValidateCombinedChainInvalidChain: return .rustValidateCombinedChainInvalidChain
case .rustRewindToHeight: return .rustRewindToHeight
case .rustRewindCacheToHeight: return .rustRewindCacheToHeight
case .rustScanBlocks: return .rustScanBlocks
@ -815,6 +835,7 @@ public enum ZcashError: Equatable, Error {
case .rustFullyScannedHeight: return .rustFullyScannedHeight
case .rustMaxScannedHeight: return .rustMaxScannedHeight
case .rustLatestCachedBlockHeight: return .rustLatestCachedBlockHeight
case .rustScanProgressOutOfRange: return .rustScanProgressOutOfRange
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
@ -890,6 +911,9 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorChainName: return .compactBlockProcessorChainName
case .compactBlockProcessorConsensusBranchID: return .compactBlockProcessorConsensusBranchID
case .compactBlockProcessorDownloadBlockActionRewind: return .compactBlockProcessorDownloadBlockActionRewind
case .compactBlockProcessorPutSaplingSubtreeRoots: return .compactBlockProcessorPutSaplingSubtreeRoots
case .compactBlockProcessorLastScannedHeight: return .compactBlockProcessorLastScannedHeight
case .compactBlockProcessorSupportedSyncAlgorithm: return .compactBlockProcessorSupportedSyncAlgorithm
case .synchronizerNotPrepared: return .synchronizerNotPrepared
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds

View File

@ -1,4 +1,4 @@
// Generated using Sourcery 2.0.2 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.0.3 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
/*
@ -39,6 +39,8 @@ public enum ZcashErrorCode: String {
case serviceFetchUTXOsFailed = "ZSRVC0008"
/// LightWalletService.blockStream failed.
case serviceBlockStreamFailed = "ZSRVC0000"
/// LightWalletService.getSubtreeRoots failed.
case serviceSubtreeRootsStreamFailed = "ZSRVC0009"
/// SimpleConnectionProvider init of Connection failed.
case simpleConnectionProvider = "ZSCPC0001"
/// Downloaded file with sapling spending parameters isn't valid.
@ -123,8 +125,6 @@ public enum ZcashErrorCode: String {
case rustPutUnspentTransparentOutput = "ZRUST0029"
/// Error unrelated to chain validity from rust layer when calling ZcashRustBackend.validateCombinedChain
case rustValidateCombinedChainValidationFailed = "ZRUST0030"
/// Error from rust layer which means that combined chain isn't valid.
case rustValidateCombinedChainInvalidChain = "ZRUST0031"
/// Error from rust layer when calling ZcashRustBackend.rewindToHeight
case rustRewindToHeight = "ZRUST0032"
/// Error from rust layer when calling ZcashRustBackend.rewindCacheToHeight
@ -161,7 +161,7 @@ public enum ZcashErrorCode: String {
case rustUpdateChainTip = "ZRUST0048"
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
case rustSuggestScanRanges = "ZRUST0049"
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes.
case rustGetMemoInvalidTxIdLength = "ZRUST0050"
/// Error from rust layer when calling ZcashRustBackend.getScanProgress
case rustGetScanProgress = "ZRUST0051"
@ -171,6 +171,8 @@ public enum ZcashErrorCode: String {
case rustMaxScannedHeight = "ZRUST0053"
/// Error from rust layer when calling ZcashRustBackend.latestCachedBlockHeight
case rustLatestCachedBlockHeight = "ZRUST0054"
/// Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%.
case rustScanProgressOutOfRange = "ZRUST0055"
/// SQLite query failed when fetching all accounts from the database.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.
@ -321,6 +323,12 @@ public enum ZcashErrorCode: String {
case compactBlockProcessorConsensusBranchID = "ZCBPEO0017"
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
case compactBlockProcessorDownloadBlockActionRewind = "ZCBPEO0018"
/// Put sapling subtree roots to the DB failed.
case compactBlockProcessorPutSaplingSubtreeRoots = "ZCBPEO0019"
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
case compactBlockProcessorLastScannedHeight = "ZCBPEO0020"
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
case compactBlockProcessorSupportedSyncAlgorithm = "ZCBPEO0021"
/// The synchronizer is unprepared.
case synchronizerNotPrepared = "ZSYNCO0001"
/// Memos can't be sent to transparent addresses.

View File

@ -77,6 +77,9 @@ enum ZcashErrorDefinition {
/// LightWalletService.blockStream failed.
// sourcery: code="ZSRVC0000"
case serviceBlockStreamFailed(_ error: LightWalletServiceError)
/// LightWalletService.getSubtreeRoots failed.
// sourcery: code="ZSRVC0009"
case serviceSubtreeRootsStreamFailed(_ error: LightWalletServiceError)
// MARK: SQLite connection
@ -251,10 +254,6 @@ enum ZcashErrorDefinition {
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0030"
case rustValidateCombinedChainValidationFailed(_ rustError: String)
/// Error from rust layer which means that combined chain isn't valid.
/// - `upperBound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct).
// sourcery: code="ZRUST0031"
case rustValidateCombinedChainInvalidChain(_ upperBound: Int32)
/// Error from rust layer when calling ZcashRustBackend.rewindToHeight
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0032"
@ -307,6 +306,44 @@ enum ZcashErrorDefinition {
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
// sourcery: code="ZRUST0045"
case rustGetTransparentReceiverInvalidReceiver
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
/// sourcery: code="ZRUST0046"
case rustPutSaplingSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0047"
case rustPutSaplingSubtreeRoots(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0048"
case rustUpdateChainTip(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0049"
case rustSuggestScanRanges(_ rustError: String)
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes.
// sourcery: code="ZRUST0050"
case rustGetMemoInvalidTxIdLength
/// Error from rust layer when calling ZcashRustBackend.getScanProgress
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0051"
case rustGetScanProgress(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.fullyScannedHeight
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0052"
case rustFullyScannedHeight(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.maxScannedHeight
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0053"
case rustMaxScannedHeight(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.latestCachedBlockHeight
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0054"
case rustLatestCachedBlockHeight(_ rustError: String)
/// Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%.
/// - `progress` value reported
// sourcery: code="ZRUST0055"
case rustScanProgressOutOfRange(_ progress: String)
// MARK: - Account DAO
@ -592,6 +629,15 @@ enum ZcashErrorDefinition {
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
// sourcery: code="ZCBPEO0018"
case compactBlockProcessorDownloadBlockActionRewind
/// Put sapling subtree roots to the DB failed.
// sourcery: code="ZCBPEO0019"
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
/// Getting the `lastScannedHeight` failed but it's supposed to always provide some value.
// sourcery: code="ZCBPEO0020"
case compactBlockProcessorLastScannedHeight
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
// sourcery: code="ZCBPEO0021"
case compactBlockProcessorSupportedSyncAlgorithm
// MARK: - SDKSynchronizer

View File

@ -409,7 +409,7 @@ public class Initializer {
/// - Parameter seed: ZIP-32 Seed bytes for the wallet that will be initialized
/// - Throws: `InitializerError.dataDbInitFailed` if the creation of the dataDb fails
/// `InitializerError.accountInitFailed` if the account table can't be initialized.
func initialize(with seed: [UInt8]?, walletBirthday: BlockHeight) async throws -> InitializationResult {
func initialize(with seed: [UInt8]?, walletBirthday: BlockHeight, for walletMode: WalletInitMode) async throws -> InitializationResult {
try await storage.create()
if case .seedRequired = try await rustBackend.initDataDb(seed: seed) {
@ -420,7 +420,20 @@ public class Initializer {
self.walletBirthday = checkpoint.height
// TODO: Initialize accounts if desired.
// If there are no accounts it must be created, the default amount of accounts is 1
if let seed, try accountRepository.getAll().isEmpty {
var chainTip: UInt32?
if walletMode == .restoreWallet {
chainTip = UInt32(try await lightWalletService.latestBlockHeight())
}
_ = try await rustBackend.createAccount(
seed: seed,
treeState: checkpoint.treeState(),
recoverUntil: chainTip
)
}
return .success
}

View File

@ -32,9 +32,6 @@ import Foundation
/// We encourage you to check`SDKMetricsTests` and other tests in the Test/PerformanceTests/ folder.
public class SDKMetrics {
public struct BlockMetricReport: Equatable {
public let startHeight: BlockHeight
public let progressHeight: BlockHeight
public let targetHeight: BlockHeight
public let batchSize: Int
public let startTime: TimeInterval
public let endTime: TimeInterval
@ -43,7 +40,6 @@ public class SDKMetrics {
public enum Operation {
case downloadBlocks
case validateBlocks
case scanBlocks
case enhancement
case fetchUTXOs
@ -75,7 +71,6 @@ public class SDKMetrics {
/// `SDKMetrics` focuses deeply on sync process and metrics related to it. By default there are reports around
/// block operations like download, validate, etc. This method pushes data on a stack for the specific operation.
func pushProgressReport(
progress: BlockProgress,
start: Date,
end: Date,
batchSize: Int,
@ -84,9 +79,6 @@ public class SDKMetrics {
guard isEnabled else { return }
let blockMetricReport = BlockMetricReport(
startHeight: progress.startHeight,
progressHeight: progress.progressHeight,
targetHeight: progress.targetHeight,
batchSize: batchSize,
startTime: start.timeIntervalSinceReferenceDate,
endTime: end.timeIntervalSinceReferenceDate
@ -158,7 +150,6 @@ public class SDKMetrics {
extension SDKMetrics {
public struct CumulativeSummary: Equatable {
public let downloadedBlocksReport: ReportSummary?
public let validatedBlocksReport: ReportSummary?
public let scannedBlocksReport: ReportSummary?
public let enhancementReport: ReportSummary?
public let fetchUTXOsReport: ReportSummary?
@ -177,7 +168,6 @@ extension SDKMetrics {
/// independently. A `ReportSummary` is the result per `operation`, providing min, max and avg times.
public func cumulativeSummary() -> CumulativeSummary {
let downloadReport = summaryFor(reports: reports[.downloadBlocks])
let validateReport = summaryFor(reports: reports[.validateBlocks])
let scanReport = summaryFor(reports: reports[.scanBlocks])
let enhancementReport = summaryFor(reports: reports[.enhancement])
let fetchUTXOsReport = summaryFor(reports: reports[.fetchUTXOs])
@ -189,7 +179,6 @@ extension SDKMetrics {
return CumulativeSummary(
downloadedBlocksReport: downloadReport,
validatedBlocksReport: validateReport,
scannedBlocksReport: scanReport,
enhancementReport: enhancementReport,
fetchUTXOsReport: fetchUTXOsReport,
@ -217,7 +206,6 @@ extension SDKMetrics {
cumulativeSummaries.forEach { summary in
finalSummary = CumulativeSummary(
downloadedBlocksReport: accumulate(left: finalSummary?.downloadedBlocksReport, right: summary.downloadedBlocksReport),
validatedBlocksReport: accumulate(left: finalSummary?.validatedBlocksReport, right: summary.validatedBlocksReport),
scannedBlocksReport: accumulate(left: finalSummary?.scannedBlocksReport, right: summary.scannedBlocksReport),
enhancementReport: accumulate(left: finalSummary?.enhancementReport, right: summary.enhancementReport),
fetchUTXOsReport: accumulate(left: finalSummary?.fetchUTXOsReport, right: summary.fetchUTXOsReport),

View File

@ -7,7 +7,20 @@
import Foundation
struct ScanProgress {
struct ScanProgress: Equatable {
let numerator: UInt64
let denominator: UInt64
func progress() throws -> Float {
// division by 0 is not a concern here because `ZcashRustBackend.getScanProgress() -> ScanProgress?`
// handles the 0 and returns nil rather than returning nil progress or 0 value here
let value = Float(numerator) / Float(denominator)
// this shouldn't happen but if it does, we need to get notified by clients and work on a fix
if value > 1.0 {
throw ZcashError.rustScanProgressOutOfRange("\(value)")
}
return value
}
}

View File

@ -8,6 +8,24 @@
import Foundation
struct ScanRange {
enum Priority: UInt8 {
case ignored = 0
case scanned = 10
case historic = 20
case openAdjacent = 30
case foundNote = 40
case chainTip = 50
case verify = 60
init(_ value: UInt8) {
if let priority = Priority(rawValue: value) {
self = priority
} else {
fatalError("The value \(value) is out of the range of priorities.")
}
}
}
let range: Range<BlockHeight>
let priority: UInt8
let priority: Priority
}

View File

@ -262,6 +262,21 @@ extension LightWalletGRPCService: LightWalletService {
}
}
}
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error> {
let stream = compactTxStreamer.getSubtreeRoots(request)
var iterator = stream.makeAsyncIterator()
return AsyncThrowingStream() {
do {
guard let subtreeRoot = try await iterator.next() else { return nil }
return subtreeRoot
} catch {
let serviceError = error.mapToServiceError()
throw ZcashError.serviceSubtreeRootsStreamFailed(serviceError)
}
}
}
func closeConnection() {
_ = channel.close()

View File

@ -193,4 +193,11 @@ protocol LightWalletService: AnyObject {
) -> AsyncThrowingStream<ZcashCompactBlock, Error>
func closeConnection()
/// Returns a stream of information about roots of subtrees of the Sapling and Orchard
/// note commitment trees.
///
/// - Parameters:
/// - request: Request to send to GetSubtreeRoots.
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error>
}

View File

@ -8,49 +8,38 @@
import Foundation
protocol LatestBlocksDataProvider {
var latestScannedHeight: BlockHeight { get async }
var latestScannedTime: TimeInterval { get async }
var fullyScannedHeight: BlockHeight { get async }
var maxScannedHeight: BlockHeight { get async }
var latestBlockHeight: BlockHeight { get async }
var walletBirthday: BlockHeight { get async }
var firstUnenhancedHeight: BlockHeight? { get async }
func updateScannedData() async
func updateBlockData() async
func updateUnenhancedData() 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
let rustBackend: ZcashRustBackendWelding
// Valid values are stored here after Synchronizer's `prepare` is called.
private(set) var latestScannedHeight: BlockHeight = .zero
private(set) var latestScannedTime: TimeInterval = 0.0
private(set) var firstUnenhancedHeight: BlockHeight?
private(set) var fullyScannedHeight: BlockHeight = .zero
private(set) var maxScannedHeight: BlockHeight = .zero
// 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) {
init(service: LightWalletService, rustBackend: ZcashRustBackendWelding) {
self.service = service
self.transactionRepository = transactionRepository
self.rustBackend = rustBackend
}
/// 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)
}
fullyScannedHeight = (try? await rustBackend.fullyScannedHeight()) ?? walletBirthday
maxScannedHeight = (try? await rustBackend.maxScannedHeight()) ?? walletBirthday
}
func updateBlockData() async {
if let newLatestBlockHeight = try? await service.latestBlockHeight(),
latestBlockHeight < newLatestBlockHeight {
@ -58,19 +47,7 @@ actor LatestBlocksDataProviderImpl: LatestBlocksDataProvider {
}
}
func updateUnenhancedData() async {
firstUnenhancedHeight = try? transactionRepository.firstUnenhancedHeight()
}
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

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

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2180000",
"hash": "0000000000e4ccc66506119f466fc73bf444e67dbb36837dce20e5359285c5d1",
"time": 1691171949,
"saplingTree": "017c3a0e28e49d0167b3f00b20af65a97acf9ed8e5722d4ddfb9f746a6947a802901cba9695fd51403fb1b43df56160802d77fea30089897bab11f8b8ef47e071b481a00012b83799df1fdae5c05750403c99212aa99ff5cd6cda244ae2e06cf95b7e6043201026fb7385ce7dc10866511f126243a245b613e7b4599007b9e9c702a8f21355a019edefa4dc3174154cdbb49049c20c4412d51e0ffc9f57bd4fa7bde5acd21750401a4caa58083087d008c5504b3bf5a110485e642594c4d9ff28f1c11db4e896d5b000001fd8d242d6fe4f56cc961db7efb419c3f5adbbfc189a15ad695ed73cdcc0c5122000001d37b567c735b8ca833b4dfca1a9778b1e49601012bc1c241e0c9c703127e396e012caa02bfcb0a4a42c05654947def2e5334e3d2a86be2d5055599e818b8c4203201f907335382e184f41fe7d67b810de31664c9abcba337743e545424c31531305b000000000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "018dc1264af083d492e5194a135e6a52446e941b46aaf6b3677871aa2cc6b3f102001f000001a70641e2426cc4f0b84b46e6da920f47b8f0db14ffa55c7f2f5e17f18d508507000157504e873fac8b08f33eede7cb73c5e0130d03b7342bf5a64687580308a64826000001731f3ca7f1d71bf8c1fca458121b4945eadcb8ed72eb628fc41f0970d6bcab3e015d374c7e9b988ff2fd94f696e9cbf25d49ea0b0e63b460c0fd2c4b23c5afbe07019bf0e257d9fd2bea1ea9916c453e973e54776f3191db7bb92717f29405cacb0f01c96b61212e081be8a78cbbc0366307c4ccf6f62a40a4a329c54eab4d2ca08c1a00010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2182500",
"hash": "0000000001f02afbe3cfabf5ca66d1c9015902272d042859e63a02252f8a808f",
"time": 1691361773,
"saplingTree": "01f7c023939f34e22dc0bbe4070fc07a171b2daf97ce083d230767111bd50d1f65001a0149164f0dda6ee264c12b521c4c852ea1d7871f60da8ec55235f6564a6938202b01eb7c2bcaec56d4e0ab10c527b0c5f06189cfa44fc58dcfcc6cd446509f9c1311016ef018e4d38c5dd02bc3c11b88e537acccf6cd47b8872aa943af9c0ca578ce1c012ef3d75a96a99b613da46370130c1c34e4fc56c25b43c2eeaf4b0f05bd4e795d0001af1febcedbe8fde6d146214a9bf6cd30aeff767e52326f233927684dedadc062000001e5b89f59ae0931fb87c298079f00269dc4236e662c07806637d5ac05a456472201eb5197ee95ca2517cfc5a3286fa3656d2d1525779375b3c9397de0b337be980f01dc56bc03ca6744aa76624b4eaae01c57444507dd20af8a068a4f2a5814964d3100000001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01460512f2c839946a36daaa2f964d380c6f157ab0bb2123322ea2978d3cd58005001f0156fd30e2f6add5a31d5e2643974f5603d6dd44f2d2a3c6d86bc4d1ea6b61292101aaf44291f1f4f39031dcdac0e24a4949319e371b2f709320f490ec17ff79333600000108a7542ee3b4449be51e29e67a2e19a20180a0bc4d3b45454251907edeabca1a00000157cc0707c1a6b1ad894c7c3f2f00476333182b760f72270a369f9489c64dc30f01a58d2ba2e28de6e43054887c043b055305512eb5589f737a5b760e4e58f6ab1b000001202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2185000",
"hash": "0000000000f46791a13f2f880f4fce274d3e517906b14a841737e23c9bf82cd5",
"time": 1691549521,
"saplingTree": "01cfcc6c092b5045179d5e80fb9c82d42e2d9e1c491509db3191a0f15cbf211307001a00000001811ac687f965b2a12bc50e71ad5114c0462d149a9878ed356c64ad5a1bcf73560001e85d0f79714d2b13d1cae642b56b4215270a602b09cb0a297318383b9b5ae72100012b8263b471898cecdd78962397460aeb01eb93e2baad1db7e3322f7fe3c7795e00015572128e7cb9eea84008c565fb9b89c29b342c220846fb84a72920e009400830000001d79ae9580f8cbe4d7fa9039930f80f45710bb33ffc7fb90c171eb7e18091674d0001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01cb48de67532673ae95ec8de43dd7d409b4f07862302ab0feffffffe4534c032d001f0000000001acb335b0e92b325d35207bcdb98c719acb6fe7c4153c015842b2beaf45fe5c04017fc8e1f88f78e4027a4c7a4beda5f72e867bb6c822fcdfaf143fd7f6768cee2c00000000012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2187500",
"hash": "0000000001dead709f146a665218857fc89e62ba69fd1ced00af7391b8769c75",
"time": 1691737738,
"saplingTree": "01ed0c3ce7c8bf8328c20328474948ea00d91fc95994acdbaf4e50b04ec112c118013da7fb206c6ab6cb215c8fd51c88d3d05a3b13419c7aa9533cd2bf36dfd021711a0128422406ee752179045949f83476d4921052fc1dc7544920300aae0f05876d3701e86b2f970dff0e4b591e210aa7778ab4e2be5644b1e5d9a85137eddc92139e1301e6b5bc372a0c25dc90187733f55ed31e2f03fe2a75f59750f4c0febc15c57d230001f79bf2cb06f09ba4c898aabeff0f1b6c8feef1b492622489aa33420f4543dc4801d7c4cee663ecb21b9d4d4c5c08cf332f32607d486b5709c4ce77287fac33665f00018f3e51a0c2f5f0ef41c4d3d6157b5974d2930bbf8742f15f13227f828e260118015cb384776067bd110fe906466a6b906b0dc17771e5b17cb9d29df950b7854b0c00010bce04210ca5e813ab598ae007557ad6451c92d5af1b3e6fd6456fb23d435f1f01d688f2d4dd84b447afe34b49c8102ed28d146838766c92d249695ade4f9e1c2001d79ae9580f8cbe4d7fa9039930f80f45710bb33ffc7fb90c171eb7e18091674d0001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01c0ccedff83dd99dc9828ba3e9e3d4cb7aa215e936e28ad9746091349b115952d0163d75be8a40e41faf632e351b7ec51854eea3ac2eddde2a3aef98f91ada0f50f1f00017f73c1cb7c014374d83c17c7991fe6d454ca821d9504b27f8619ebb24a1eee2f0001f4ca7a69aac5e90396654afbc3b0ef3c63a03df635bd9e2388532a5ac24275340001b5b28578f0979722a177079fc818d4e89703fdbc7df127ebe7cce7736d3ee51f01e2b65da21849985ad32e25c180bf475786a943db09b6a4eba98b7ee7d5685f38000001bb088387ac8270e26a09f5774e254a32837e36a33b0bc52351be996855ba3e35012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2190000",
"hash": "0000000001305957a9d4b367057609295272411918f138779cf1439e274706aa",
"time": 1691926501,
"saplingTree": "01305faeee7b1ceddaff174e5270e24e510d0df8daee5e2f23e9a620b5d758112801530563cca6add8edabc0310254231415ae17005c004b872cf69928da90b2ba071a0001a686bd2976daa6f85dbc4004d43796a4decdd2746e620e49c223400557117f5e00000000015cc61388fab56ec55e778d79451485646e19481245a435c2e86612489970c366000000000167058878ac649f304908a893009ab494d207221849101397f9183c4b434ab4110001829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "0106149b481dd306fd9747b6052467fcdbf0e5fc6017e57767ff3f22ce71236c12001f014401081b63c6d812cdaf47ab52de22a9a89279233133234ff5b86ac0825d332f0001fa4473594618ee491fad27b24bf856180d0895103c50f4745527db35e559a3100001a808aa9b8b88ba8968efea6d6935dbac646a481fca2f8d0ff20c067a43454a3d0001a4ec093f56cb96649c27c2c703bff22f2272a79465ecb4c403e0a65369b46911015ff4341320c653bf7f70b8940e179dbd8ff7dae489d5f52665cad5cd27358f2d01f965708d56263270a5e554ad8e6e4dbd8fa8856410171907d3a14d752be56e3101bb088387ac8270e26a09f5774e254a32837e36a33b0bc52351be996855ba3e35012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2192500",
"hash": "000000000177c30430cb21a22754a42190496b3b3b19e3b16e18aee2a8e4ba30",
"time": 1692114678,
"saplingTree": "01a37f64e2650a6c6617e3cc7821f93d3ffc810e7d880204784d6ed43e68800256001a0000015a608221ce9a2a593a3f86908f0558331f022bbf313d3c40a0a306ab7acfdf570136b904ffe5ec03c3fb7c41577479ea401f43926bd22efd8c9cfc953c45d6e05d0000000001b6e30d87577551876f155db59fc6d01e1cf7e053f367977a3655d61c0854d50f01dee841dad7d7f556d247748102d17326963b5af5ef9e82ef4f91544bee028148000001a561675207ad96fa955da60fa3ba010436c5df2807d4b8e44e05bbeb2ac3ce4a01829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01a2863e4d5340d7375bf96ff16c3c9e471678abf157e1fb1e8fe9bdb4f39b3719001f0001265f945cce4b1e90db04d7e43e0626656ebd30a1107b7793a47ee610f3fa5f230000010c026e4027b2ebde9c33eb532fe4e941603e995ec5f5ac3f879bba46e03e8305011748f4925963ef979f4a88c8b654caf13c51ede92080ffe41cd742965529551801087b845cc6ce8a2c850f7bcfec4902599b79203d548d091df445154fa6bccd260001a956002888967ac1e19fed83584ab640f47c7d52811412964bb5a5af0ce50d26000000000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2195000",
"hash": "0000000000093cab6eab6ce5208492cee91186a6ce9f08e7d494ae921b1a8091",
"time": 1692302661,
"saplingTree": "01d1a0b94beb1d1049d9274f9d7546f5993e777eab6b75ab5584b615e6be1ffc540100051d31d8f444cf45b6677e9fe56d1ac79f2a245e63e04b58e5e09ebbf3c8231a0166f3cad95b7b81c55ae49709fb3e52d3714b72edf134903c675f708dd32e2b0200000157ea366b72fa44859e4fc6054d1087ae16f31ba11f775f2fc933439ee13d52510000015182161b8b449c60ff88a8865e022cfcfed36ab29fd63a785f4855057d8af731013e487066d124c2d77e861baa9acd6ac43340df222d369dcb4667e5b8bdad29520163ff0d183c5074455cd003245bb120e272f5f5341013f8b71a17ca29a22722000001b80c2ef8eca5372714de6c51ad56674cc4cd2eba28fd74e5ff34bf515e4a373401a4e3918c0121a67a3e4cf87970529dcccaab894613d23d400a4ed90c6a087b5801a561675207ad96fa955da60fa3ba010436c5df2807d4b8e44e05bbeb2ac3ce4a01829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "015d00fb4568aa38aa383947e7794c3eac8bbb3ef3c094f4cba5f40869d7322f12018019fa57281954625066b347ef0986f8d7c085186b4286c781252d530140d83f1f0000000161bc6b552e66c1541a4a6c066568fb22efe5af15462ba327fe171a673be24e3400015d4be5832e35b11d6fc52b62aad4b3d2deb807a47e395c819c2a4ba4dde10620013fe92332846f22c13930110bd5f8a78662a64e3a125abd42322619856fda4e1101c388b18fbc10553d0561fad21854c889ff0a51e9778a73797e4221e7b8dd7f330001e2439222c74ca0ccf3e8e959cf01b28f97b2c395f9d2f5a5ef4f21ef314614230000000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2197500",
"hash": "000000000184479e9cb5ee4ff718197791cab798ce72a48a723d3333fad0548d",
"time": 1692491813,
"saplingTree": "0166a186574d6445cee7a7291c0c777050f1b42f6f5f893c437652f4b9adc8fc08015fed069e6d62e8c1e0154a291ce89b20565c78ef42e01b068c5bd276449193591a0001dfa7ad86f0394ff819571bc51df0e228e4b53459b5a9413b8ec45a6c08117a3d01f4887408b534ff5c910db09f825a2f2d8742fded2cf90dadf62724e4886bd13801663e461e45e72845b1460642853ef99298195ada4e03249277f0cc31a8b7f72d01d93fffda754f08c28f2474d7bfa7124306343ebdd7bdfda10e0ad783a39f772901bb3c0c73ad81cf3af0259767e074c47128b29606029407ec9539c6daf0a8070b0197364747700f77becf2cd59be1efb0e68747b7e650c69aff24a270376248ef49000000000169e55175fd24dcd2230eb8dbc83e4868ba002bb61d3e3e28bb77a75fd9a9816d00000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01effed431ad939b98e4e16c17e0fc80ad056f4e985bc4ff8c5bc70850f15ab732001f000001962af92932d5fc477176442a033db3a5d505459765d813f54b438f6b038ed6180001a4ddd74ae340ff1c7b4ab4a683471d4e7764f7622aea196eb1b61045989fba0f0001e901ff930dea1433e9de3d9293ddfdde1ca2b8ce1cd2d7d35e630b80687b660f00000001a6e633d09d16d5fa57ed0eccb16e1cf2cc673b648db397899b70289e56b8920100000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2200000",
"hash": "0000000001a0139c4c4d0e8f68cc562227c6003f4b1b640a3d921aeb8c3d2e3d",
"time": 1692680380,
"saplingTree": "0124fd6a5c9f61e2c5e66eb47873077ece71dd41317c0b7cf7d3f1980c97a2b404001a01372a57e57f8f948fd22443cb49039e067f74c5bbf5e8fa359ca1c4e8937d8017000000000001c9d9f4aef7b919b8f3155a5c45bc5896c88921c54aefbaeb9db2d09b6bef72530001d4fb2a693773b53320fad63d6b683e181518d8ace0e45279ba76aed72a0a2641016e25b96f1952ebe60a92d85af13392d2ed30567d34b369e22c817639d2ffd31f00000192155a9c4bbe0c5d0ee36e26e9826b27927b471c17751bf88763fedbe6415561000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "012ef2b38328fe639b9ad22eaf4df2db0c1f4f9e1f1ae4d9f46be2487f4d462718001f000000011e761b7f64374248922ed29d050430fbbc83084ab1e4622819822442bdc23b2d014230b066e6902fb899bcc745ec111dfd1badb49086734842950294e9512599390000000001a3275f1adee92868bf05dd71490df8a7ddf174224cf9fe3e39acea2029ea643201a6e633d09d16d5fa57ed0eccb16e1cf2cc673b648db397899b70289e56b8920100000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2202500",
"hash": "000000000037a9d20057d61a40e29f272f77668804df40cc30aaaa86a2756f6b",
"time": 1692869287,
"saplingTree": "010867e9be1e8087ebcf6b3956be7da442443a17ee32decc67a7c4d80ee1c04f2b001a0001b95e3fd30660cfad778571e036bddeb5f262066a85b725c8a9b3ed2b72a1ee1f0145330fd67ce31ffa08aa2826276cdfef861e06a69c84a716f10525dfd91cc4060001d1aadaa7869736dac8cfa6cc47fc8b5f8e3a10b8645d78b254411071217bae1101c5c9bcc4381ea5472d5ebd2b9aa310886ff49928879887cf7e01cd4f79cf7e2101868127dc5d8d4c972cfb1642b968b310c70f0bb0d0034d9314fb0fce0608163801622a5d4d2dc7004d17803e6fe93a63a00d4ce5fb7ffb686843f9f810503a1672013dcebf35b856d9de87fb1e77b03e9d272c08ce39ecd47430bd0f8c468b146a3e0001c110157c0bb0f38632e3bf111c7f3da91373d8d407cda71a795abfe4b437695e01cf0773c6eef9e2dfcc6567971cb1f3a2ece4f1e61061e09e78c20455cf2f07050192155a9c4bbe0c5d0ee36e26e9826b27927b471c17751bf88763fedbe6415561000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01d2c9116c0d4e34d037dec90f6865ee8c546ee5237ce89f44e28c28b0ad16e416018fd673eae1ae9b46badb1d0a25c76a037487793d0e964e869f64a73ec81021351f0001f7fb3070e4866fce7fe13f30f69f55aeeb1947a0c4870a9febc7a1d6b66cc8270001d982d243bd0578f6ce748723b1d0544935fdc6ee09af2e9cd97044dd70bb3c3100000108fb7a4b7d48e456cc89a79d98b1d28cde03d56780947e5c5015baa051cdee2e011692e7fd01ba52a2f857999ea44b387be3e49d61079999df2fe66740f895301601b46c2b6035a150b945d03287a24a4e8f978527484798488439348e6094ad7b020000012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2205000",
"hash": "0000000000ce31da261e370d941b842f293c57db03fcef8bf4770785a4fbac6e",
"time": 1693057204,
"saplingTree": "01ff26e71c0d39a1a6a0716a77324c3ec9c687721f8eac76c18918958b897d2c41001a01ddd790321c638005422572a2c7f7e0de9187b5af26d3557739645e1d7f8e6e1901ef54f6b7ccf090de188e2b55d379df10fbc681df47e44fe40374202a4e41933601ba9437003fdc8511f7d9ececdce9de832854808bf665e629e26f053f6ec9ca280000019641cac56e94365d5c6658456ae8ed333a6b1163c07eef893c1500d0f190306b0001810cc92e11afa88275ec901d1ab67bf9c6e105b216204bb4d3f3f4c4667d3e1000000001c0216916756804a8c3f9fcf751f7807608a04a8a6f754acf0a7f0f553fd9b12400012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01381c984235114e6d81ae00d44eeca4685c09540cf21e537d845df58e9fb7130f013073246134d44af4e3bd072d311a7e14e2ed2d9ca1781cabff34f5d40fe8dd221f016423f87be23b410c2e604db4c608c77b9063bcf4cfddc95f20133d11b398dd2200000001a4deff81f8fa06cc2b014ac875e2e5b3751d39002dc653920c6469369f48722301744441539851d5c68365948b6583529eefde092b83c377e584c913bea895640b00019f7c1e3978b795d2bc5dbfb78291944f75c9aad1637e1317e8185a281d080e1d000001f7c6dd5eaf5175eaafde1de044d62b37bc9d1b0299a43e1ae6e97f38e3f4ae2a012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2207500",
"hash": "00000000000ec0f112a92bd16ef5b3a2368894f6c3d5032a77ebc3aea03d18ce",
"time": 1693245695,
"saplingTree": "0173a9b518212d5c1889fd1e38035673245c44f23a3990dfed2809d2c2c9ebf705001a016d5ce596abf1b0f2ab353a7e9e310b80da33b7e35b9c1ca930dcb4ede19b06270001cf14e701750ec012d70b0b109e6e54892959ac8c396cf8712e80f0857721851c01df43c721f681b634073095cacab7421c4957bb508f8e7d8ac218bd44b2f0e02601e4aa56067a58b5dad7e9f1b1e2db7baa2fb6658439e74ff0365abe3b1faa024a01dc955c1f7a3eb206b45d7a9acce0188f6493a6598f16882efc047ce7cc33d73400000100f7d78956d94aca8e287ea45faf7b52d3d1bb04c3396de14cee82b9a0d39a6801b035afd4c8314478c5397180bfa9a160388cc45c9b09c089bae1c2bcbd07d141000001a6efac374c5ef75e7c7c742150f6ffd7852c9d0641570c8f7dad318aa7162741012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "0158459484681fba0516801018e8ec5c219cdefcf24cadddd032097c62c955de00012764914ae23394cf0e867e9c230e2e4c93f0ab5ec091b2c745244fad9f7e36261f0170221cb1f864b92366bbb441e7f300a4f7544fd713921d6bf4c0349ab0d60717016a8f5c1110837ceddc613c648dba174a03290c57435663e003558062fa2f6829000177704fd90b1999887453dd1c31ce6270008768d5d19100faf5d4863f582aa81b01d473980b4c9980ffefe5d8098b5ceb43774c02195e1ae6435aac0692689c7d1801e4003bed9d4e89e0b0235dc60d2be1fe60f1f50e15e9a9d2c18bd2406b8abd14000001558a2081b4d2988432f40395c72886a0ce6abc23c1c6061869ad44b6583275080138aee02e5f0b1370556dc7ad927411fa1e62e15367f90073091ccc677079c11a01f7c6dd5eaf5175eaafde1de044d62b37bc9d1b0299a43e1ae6e97f38e3f4ae2a012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2210000",
"hash": "0000000001355c3493575fd882ce1efe7fea8d4e979e1f9c8665af8db296823a",
"time": 1693435343,
"saplingTree": "0161b72f49f0b0dd8a3e2bcb85262884a5969085f6e611f60ca73940e29238e733001a014c3601f3ebd57bc6d270c684390e3d980fc7570a0cca06f4bc3c97e24d93be5a00000000000000000188e1f654b2317621d8ec0d8aabab051752056bc85ce601ee7b2efc85a9076e2001b70de51176166ae4f6110e2726339e5659d043df716ec5426ee4fb3e3e47ef2801739355d442687c9b1cdbe89bdf1c83bb0e05df09123c6dc8ac46264332372a5b01a6efac374c5ef75e7c7c742150f6ffd7852c9d0641570c8f7dad318aa7162741012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01275579cb64febfc92000492e10f910065c9571d6b94f6434d7b7e6c913779c3f001f01f5253a74bc6d283fbe31e6b2dafa5d4db91fe81abcec50de59c872065502cc0b0000018ac3e61ce99ecf9e3c1bced344b606dd7ae24f02b1448628ad91b163d3982e28000001fe353c0f219693c370cbe61398f479030c4cb70e0234e4f7b30e8d5072f4af3e00012a75037ed0051a079beadf00c146c505a7761f53f46ae9814e34bb4e954b0224000000011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2212500",
"hash": "0000000001486c47f3460f7a46e5d06d559a69bc5ad58fcd5c98221dd6df9fd0",
"time": 1693622394,
"saplingTree": "01fb0b656dc4c627c59bba1642f073ac28c131a02371c5961b45bb7d4ea836b452019907a87d715d596eb4e0ccdbfcb3835071802a4a961bd70463d35c77504db5441a000000000153053505431b8f8ffa0a7252415f5d29ce51250bf40df4708c84b03c4cda6311011303d58561df2d2e7acf1148efdb33e85a56bf70005b81921ba81e35668f562d01e5e495006de9e65e1a2fcc24390e48b8ffbf0800c8621be19e855017c07c1d130141361a3762e41c0dce03032cd0420cdabaead8ebd8e1221e411d9701459e6e1900000001f7226a1253c057302203aaa5c7752967c577d9a952f50338f5a632a7a52c3414000001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "018c5e93594000a700fef7e55494d35a9d3a5d4442e54dcdc64ed1ebf3f917f60c011702b514a00f103e277396f64d56b94f3a60cce6d66bfa2e71e4561c4a7c111a1f01387e552daeceb2c272d18f411143fe74c7323ba3b4f0ecde2fdc5be74194dd25000001fa2d3584d563111d294727805890abb66eda4e6d851a6a4d060b7aaae2cd9e0e017ba1387f63ab6460e9cc82f7bb95afc39f71890616ea5bc24cd59ed96bcd80290001ac3449ca9cf912130aaf4cec2d1137a3981e4b88537778534dcaf332e228d10100000001c065cd9302b671e6b216fd4e35c893c35ac6cf3998f6d69185f14140ab96282c00011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2215000",
"hash": "00000000011c3ba6392f069d59d0272e8467f0a655822aa5b33292fd78363445",
"time": 1693811957,
"saplingTree": "01051715c1e72270bc99cfafdee126677a43f6db2097955e2d1da8f50999364206018fe601319b6965a5b78ec5ce6922b62151c1fdef6b65361b8abf033e44518e061a0001b909dfc9caed7c414948809189e0b27b28dc141d1b7ff275fca06686d741af6001ac51dc8a652f68434aac270fa4bc76e5d969a961d2f23bcddb4eaa59110f9a40012ffaef49ced1ff4a30ab44a9f74d75d2cd121e138eadd65723d39557bedafb2900000155e7894bcf717fc9537b2b3c09ef59a8525a3bca846a173328092c2f58e94440000170685bee2dc27257972e2012d8c0cb8c826207e0e24100e8cf94779c512c806e0125538ced2d3de8b1836b9253a476d74e4478e28cfd83b9b0625973366bda215200000199e451c57fb0edef48700c409c3781e7dd114ff6c0030501f0766dd03549c9360001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "019afb92f9fd0a550932e32aff7caffc380f388aa92ff60c7c82061e89056fc5250141aa6fe2658f2e45797ebca3651868671945180dfb2fa7dbc915b8dfc3979a381f01a80f6025387f49e9e28facc759bde114621c9beaa7e7af584e211afc5691573401e8ae62ce5a426ac574643ed4e492b1c9e0538f93d915d4fae3e8631113f909130188a80f2232ff5447158985aa28309076ac7ccade5f2e1262472ebb2414fcd31f01dce62be3991b93a1bd81df04f61f07c926f54e9e69bf58a73035b49c48a65b030000014c0e495688bc0bcc7aa266ef9851468336d6807047505d3c3d4f90effc312a2b01da8332bdff0ec0ee5273ef11a3cc86d7b28e005abfd687592d23af984b31293b000105627a115c6a93ef03e846fb954e860f18e8934f04b8c4e363e7a63a7395100001c065cd9302b671e6b216fd4e35c893c35ac6cf3998f6d69185f14140ab96282c00011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2217500",
"hash": "00000000002028029990e5591264643017107d193c0705624f3a2d190f1d97e9",
"time": 1693999951,
"saplingTree": "01ed0875728e56dd4c36e71c09c51efebfe35c591d1a3b20da54589b00c90b88330154f06a0b2e1ac618848c2a3059ac2227ab6f3de600b59d96c8466e8a698d95071a01a2b44ef7193abedc4970ce40bb93df74da399cd656faf79b5cd67a7a0b85424d010945f64972d27570d621844cb07338ce81de32c73944ed9be170176420afa7220001291ef2618c0f3d7721e7dfe04f541383df1b01d2f107c68a9c45fc1e1e711e27000000000001c9f1d8ad0530357ae6bbd2db9a0af2c050175c36df6c243e964e102f7630105d01b700b64abfa177f24c0500ca3b3e3c6570a06e677be096771b149d249151886f01eb565941c38472195168f8f2099bfa2852eb56467e04dc337895e2105c7460700199e451c57fb0edef48700c409c3781e7dd114ff6c0030501f0766dd03549c9360001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "015b24b161887c608536a41fef3f10cb9eb513cf8942d99f13d200ca2f0726ed16015873ccea6d03227fbd55ea1c9a53b2832652e2aedee70c34df469ae31df945291f01cc99f99724bf5478b4de071599ef84e16b038fe807ba3781be3f4964a6d49a05017a8605d9978643a1bc1285eba1f6c253e1f6892d8dc4a5e545fc0a436748fe230001905f2723f2dcc0583f0362cafa51a522b4a32271f43634e6718496337f90c938000000000000000177fe87edaf3267ef297756a3b204b4920001f2db983b35a9a122e79b4a90bb2c011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "2220000",
"hash": "000000000172b493b91a2bf8da492818f469abaf367f0a34c6088d7384bf438d",
"time": 1694189265,
"saplingTree": "01e1fdb4fbf39470312e2ee698bbadaae6a6f600630704ce828f624092d419eb2b01bb2e754503afcbeba2f5f351cde44316015799935827d50086a09347b9aa72281a019cc2cee08afd6df6cbc2d89a3a7f5381c466fc8718a52e6db6e6fa44063db24000000000017e8553f04eb329f93a9882e2656a7d9772a7d12098932e9fc10b094844d4675701de3681f6095e049db159b1b3f7058a80bf8178e7dcc36de3e3eb11a2abc3584001fc25e0cec13e3f59891b069f75fd51f2caa60f501e23b3afdf6209faf71edf1b00000001d177df23a29c4acfa477b94c55bf3baa5b4f43dc65bf94ed65cf3ac7b2d753300001055bb26ddf3a7a7efa91af3ba8c040cf4f373d366b3bb01a778cc54fc31ab32a01cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01e9654f2f2065ea7222b509f367053c8c60335abd1fef4d4a8cc8f1b6e4c2a133011093acb8712c698913d117410839280fe234942db8fec4830c6544b3d97da0361f0000015cfab442f440115599554959ca560451d1da80cfa1fac8031be6c091c9eb79110001bf4c6e2c88cb1b29df8cbe2ab268c40e3d2a2d26155a3ebfc3a9d450954c6d050001b8c991defe192f04816e611cbeff67ba630aba4e68cb9a01d8a15861d6d5460001463c8512a48687e5827657cefbc11b0495302ca787760ef02346bdb91922c10801a23a492db19a8b6962c4f58d478dcc81a3b22f59914a8dbaced764f53e923e3700000177fe87edaf3267ef297756a3b204b4920001f2db983b35a9a122e79b4a90bb2c011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2460000",
"hash": "0009d9631de2a23970526299576cec163ad78052d42fa0663ed702fc54de97cc",
"time": 1691548876,
"saplingTree": "012a6b48d681dd51a8a1459128234ac62021e648d051327a20046b9199a19b426e001000017f5c7aafef68bdc253a8bbbe42aa31eae64f1bd41d6b12ce6d3cf9e9f9dea94b0144a41737b6eeb7602f3c0d934867da3c93e0fda389bd9038bc94b121ca20472101b305821622d49b13ca61f1daa7e1bfe0416e10540a2e66966e82217f29682b1d0107667bbf959254b11065a34a18aaeebf5d2d454a92e74642da01b517373f970e01a1189f53f8e689114d04c4762671355fc10730d4b6d2ce33d21d82329d368b4c01a0c35d0c00236839d6bff3ab9c2d80e46b7e58505e65a28dbb212bc1261f1d600001bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01ca16bf908aa97229537c7fbf28645e9badb56ccfc77def82896cf60298e69c26012d592df066e039007ae2f44955f1178839742d07bea95ccab04f97c07f5899231f0000000000015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2470000",
"hash": "00121d226a54c97df305952211884f0ec30a946c27dc19413f1bb1aa018a0453",
"time": 1692203956,
"saplingTree": "01512e2ed4e4700f5f5efbdca083edac1c84c85ce7af5097a9dc4ca3268cdaec32001001148e57e2488fe489b96650e2a101fc03c125917a2f5e53730a6161e5d9ba3a3f0001f011635dacf249b3f02cc7cb93cf9a9cd7647822c872d3770425315f2249ac360113aec83ad64a9fa33e5d3fbd86707e08ec9b78e75eb3de0bd28b40fa33877e300000000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2480000",
"hash": "001bfafbaceebd7a1c345f50e5e6cb7b1b407367c6c34dca1e4a8c2e2cc8a454",
"time": 1692819217,
"saplingTree": "01d4ea948eca1394127f74f6ab5ab04eeb27ac14f861f9a8557856bc2a117fb42d0010011071503fda1c0a2e4f144674354e79ff3ba41a7ffb55f39eab0b36fdea0fd66401e96af949aa669ee122d4323bb8c2a1796dc19329d74e55a91e859db941773e2a00000001aef1b7b2d9b9f3e2812e17fa7d795ad84b60d9fd06595b9894ce98756f4d7a6b000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2490000",
"hash": "002ad48693ddf02fb78f4bfc90fd9f4d8e2e42774d4bfbc397b377d6c63d2a91",
"time": 1693445978,
"saplingTree": "01e2448ca682786985da2e41fbe10f2f7d9cbf84d23a4459d65e9eee26d126652200100167cfb6ece69f4aca04044e6a5951ebd80565f90b60abb94b7bfc42b3d5e12c2200000001fd827e648ed7b0395b82aa4320658f173aab4081f0d247849aa51c16aed7271501aef1b7b2d9b9f3e2812e17fa7d795ad84b60d9fd06595b9894ce98756f4d7a6b000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2500000",
"hash": "0027edd1f6961b523961a1cd56838f9bbe802089c2611926c0e7921ff0cf1a7e",
"time": 1694114813,
"saplingTree": "011fc7d5b799f4cadb4858413ebbdbed68d70c01ef9b7e843c56beb69e8624a30a017dca56a3b2a449cf5e04774d5a1b68f5fcc293090d65b2f517e6135195c181281001843d2b87c6a97e9b51379342fa9d83ec3606529ee751b1ce38538a5eb42e2e090000000000017b1f428de6cb942a20c1529e0aa66245e3991b7f21f50d34516c1fa5c3d3133d0154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "011697b177a3a10f36dfaa186c740924a7380e0b370232b8ae827b1034f0bc8a1e019b5d03643d9f7836adabe446f6bac21a153cbfbf2a1ad336601edd42a31add111f01b0c73b0fd3a20b4a20aa61bdc190afea942e6fee5c9c3dbe6cc8d51c7bdf2d1301e5ff33a9e5a1f277f5c34ffe5df13dd6a84006baed346f7faee6cd40b2347c05013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
}

View File

@ -47,15 +47,23 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
}
}
func createAccount(seed: [UInt8], treeState: [UInt8], recoverUntil: UInt32?) async throws -> UnifiedSpendingKey {
func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey {
var rUntil: Int64 = -1
if let recoverUntil {
rUntil = Int64(recoverUntil)
}
let treeStateBytes = try treeState.serializedData(partial: false).bytes
let ffiBinaryKeyPtr = zcashlc_create_account(
dbData.0,
dbData.1,
seed,
UInt(seed.count),
treeState,
UInt(treeState.count),
recoverUntil != nil ? Int64(recoverUntil!) : -1,
treeStateBytes,
UInt(treeStateBytes.count),
rUntil,
networkType.networkId
)
@ -521,7 +529,7 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
if result.denominator == 0 {
switch result.numerator {
case 0:
return nil;
return nil
default:
throw ZcashError.rustGetScanProgress(lastErrorMessage(fallback: "`getScanProgress` failed with unknown error"))
}
@ -550,7 +558,7 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
BlockHeight(scanRange.start),
BlockHeight(scanRange.end)
)),
priority: scanRange.priority
priority: ScanRange.Priority(scanRange.priority)
)
)
}

View File

@ -35,11 +35,11 @@ protocol ZcashRustBackendWelding {
/// have been received by the currently-available account (in order to enable
/// automated account recovery).
/// - parameter seed: byte array of the zip32 seed
/// - parameter treeState: byte array containing the TreeState Protobuf object for the height prior to the account birthday
/// - parameter treeState: The TreeState Protobuf object for the height prior to the account birthday
/// - parameter recoverUntil: the fully-scanned height up to which the account will be treated as "being recovered"
/// - Returns: The `UnifiedSpendingKey` structs for the number of accounts created
/// - Throws: `rustCreateAccount`.
func createAccount(seed: [UInt8], treeState: [UInt8], recoverUntil: UInt32?) async throws -> UnifiedSpendingKey
func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey
/// Creates a transaction to the given address from the given account
/// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent.
@ -143,6 +143,8 @@ protocol ZcashRustBackendWelding {
/// - Throws: `rustRewindCacheToHeight` if rust layer returns error.
func rewindCacheToHeight(height: Int32) async throws
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws
/// Updates the wallet's view of the blockchain.
///
/// This method is used to provide the wallet with information about the state of the blockchain,

View File

@ -42,13 +42,8 @@ public struct SynchronizerState: Equatable {
/// status of the whole sync process
var internalSyncStatus: InternalSyncStatus
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 {
@ -57,9 +52,7 @@ public struct SynchronizerState: Equatable {
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: .unprepared,
latestScannedHeight: .zero,
latestBlockHeight: .zero,
latestScannedTime: 0
latestBlockHeight: .zero
)
}
@ -68,17 +61,13 @@ public struct SynchronizerState: Equatable {
shieldedBalance: WalletBalance,
transparentBalance: WalletBalance,
internalSyncStatus: InternalSyncStatus,
latestScannedHeight: BlockHeight,
latestBlockHeight: BlockHeight,
latestScannedTime: TimeInterval
latestBlockHeight: BlockHeight
) {
self.syncSessionID = syncSessionID
self.shieldedBalance = shieldedBalance
self.transparentBalance = transparentBalance
self.internalSyncStatus = internalSyncStatus
self.latestScannedHeight = latestScannedHeight
self.latestBlockHeight = latestBlockHeight
self.latestScannedTime = latestScannedTime
self.syncStatus = internalSyncStatus.mapToSyncStatus()
}
}
@ -119,7 +108,7 @@ public protocol Synchronizer: AnyObject {
/// An object that when enabled collects mertrics from the synchronizer
var metrics: SDKMetrics { get }
/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
/// database migrations. most of the times the seed won't be needed. If they do and are
/// not provided this will fail with `InitializationResult.seedRequired`. It could
@ -133,6 +122,9 @@ public protocol Synchronizer: AnyObject {
/// - Parameters:
/// - seed: ZIP-32 Seed bytes for the wallet that will be initialized
/// - walletBirthday: Birthday of wallet.
/// - for: [walletMode] Set `.newWallet` when preparing synchronizer for a brand new generated wallet,
/// `.restoreWallet` when wallet is about to be restored from a seed
/// and `.existingWallet` for all other scenarios.
/// - Throws:
/// - `aliasAlreadyInUse` if the Alias used to create this instance is already used by other instance.
/// - `cantUpdateURLWithAlias` if the updating of paths in `Initilizer` according to alias fails. When this happens it means that
@ -141,7 +133,8 @@ public protocol Synchronizer: AnyObject {
/// - Some other `ZcashError` thrown by lower layer of the SDK.
func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight
walletBirthday: BlockHeight,
for walletMode: WalletInitMode
) async throws -> Initializer.InitializationResult
/// Starts this synchronizer within the given scope.
@ -197,9 +190,6 @@ public protocol Synchronizer: AnyObject {
shieldingThreshold: Zatoshi
) async throws -> ZcashTransaction.Overview
/// all outbound pending transactions that have been sent but are awaiting confirmations
var pendingTransactions: [ZcashTransaction.Overview] { get async }
/// all the transactions that are on the blockchain
var transactions: [ZcashTransaction.Overview] { get async }
@ -240,10 +230,6 @@ public protocol Synchronizer: AnyObject {
/// - Returns: an array with the given Transactions or an empty array
func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview]
/// Fetch all pending transactions
/// - Returns: an array of transactions which are considered pending confirmation. can be empty
func allPendingTransactions() async throws -> [ZcashTransaction.Overview]
/// Returns the latest block height from the provided Lightwallet endpoint
func latestHeight() async throws -> BlockHeight
@ -425,6 +411,16 @@ enum InternalSyncStatus: Equatable {
}
}
/// Mode of the Synchronizer's initialization for the wallet.
public enum WalletInitMode: Equatable {
/// For brand new wallet - typically when users creates a new wallet.
case newWallet
/// For a wallet that is about to be restored. Typically when a user wants to restore a wallet from a seed.
case restoreWallet
/// All other cases - typically when clients just start the process e.g. every regular app start for mobile apps.
case existingWallet
}
/// Kind of transactions handled by a Synchronizer
public enum TransactionKind {
case sent

View File

@ -34,10 +34,11 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
public func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight,
for walletMode: WalletInitMode,
completion: @escaping (Result<Initializer.InitializationResult, Error>) -> Void
) {
AsyncToClosureGateway.executeThrowingAction(completion) {
return try await self.synchronizer.prepare(with: seed, walletBirthday: walletBirthday)
return try await self.synchronizer.prepare(with: seed, walletBirthday: walletBirthday, for: walletMode)
}
}
@ -92,12 +93,6 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
}
}
public func pendingTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.pendingTransactions
}
}
public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.transactions

View File

@ -33,10 +33,11 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
public func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight
walletBirthday: BlockHeight,
for walletMode: WalletInitMode
) -> SinglePublisher<Initializer.InitializationResult, Error> {
AsyncToCombineGateway.executeThrowingAction() {
return try await self.synchronizer.prepare(with: seed, walletBirthday: walletBirthday)
return try await self.synchronizer.prepare(with: seed, walletBirthday: walletBirthday, for: walletMode)
}
}
@ -89,12 +90,6 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
}
}
public var pendingTransactions: AnyPublisher<[ZcashTransaction.Overview], Never> {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.pendingTransactions
}
}
public var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.transactions
@ -127,12 +122,6 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
}
}
public func allPendingTransactions() -> AnyPublisher<[ZcashTransaction.Overview], Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.allPendingTransactions()
}
}
public func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.allTransactions(from: transaction, limit: limit)

View File

@ -80,9 +80,9 @@ enum Dependencies {
container.register(type: LatestBlocksDataProvider.self, isSingleton: true) { di in
let service = di.resolve(LightWalletService.self)
let transactionRepository = di.resolve(TransactionRepository.self)
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
return LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
return LatestBlocksDataProviderImpl(service: service, rustBackend: rustBackend)
}
container.register(type: SyncSessionIDGenerator.self, isSingleton: false) { _ in
@ -120,7 +120,6 @@ enum Dependencies {
let transactionRepository = di.resolve(TransactionRepository.self)
let metrics = di.resolve(SDKMetrics.self)
let logger = di.resolve(Logger.self)
let latestBlocksDataProvider = di.resolve(LatestBlocksDataProvider.self)
let blockScannerConfig = BlockScannerConfig(
networkType: config.network.networkType,
@ -132,8 +131,7 @@ enum Dependencies {
rustBackend: rustBackend,
transactionRepository: transactionRepository,
metrics: metrics,
logger: logger,
latestBlocksDataProvider: latestBlocksDataProvider
logger: logger
)
}

View File

@ -24,7 +24,7 @@ public class SDKSynchronizer: Synchronizer {
public let metrics: SDKMetrics
public let logger: Logger
// Don't read this variable directly. Use `status` instead. And don't update this variable directly use `updateStatus()` methods instead.
private var underlyingStatus: GenericActor<InternalSyncStatus>
var status: InternalSyncStatus {
@ -127,7 +127,8 @@ public class SDKSynchronizer: Synchronizer {
public func prepare(
with seed: [UInt8]?,
walletBirthday: BlockHeight
walletBirthday: BlockHeight,
for walletMode: WalletInitMode
) async throws -> Initializer.InitializationResult {
guard await status == .unprepared else { return .success }
@ -137,10 +138,10 @@ public class SDKSynchronizer: Synchronizer {
try await utxoRepository.initialise()
if case .seedRequired = try await self.initializer.initialize(with: seed, walletBirthday: walletBirthday) {
if case .seedRequired = try await self.initializer.initialize(with: seed, walletBirthday: walletBirthday, for: walletMode) {
return .seedRequired
}
await latestBlocksDataProvider.updateWalletBirthday(initializer.walletBirthday)
await latestBlocksDataProvider.updateScannedData()
@ -215,7 +216,7 @@ public class SDKSynchronizer: Synchronizer {
case let .progressUpdated(progress):
await self?.progressUpdated(progress: progress)
case .progressPartialUpdate:
case .syncProgress:
break
case let .storedUTXOs(utxos):
@ -310,8 +311,8 @@ public class SDKSynchronizer: Synchronizer {
let accountIndex = Int(spendingKey.account)
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: await self.latestBlocksDataProvider.latestScannedHeight) else {
// 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: await self.latestBlocksDataProvider.maxScannedHeight) else {
throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds
}
@ -364,12 +365,6 @@ public class SDKSynchronizer: Synchronizer {
try await transactionRepository.findReceived(offset: 0, limit: Int.max)
}
public func allPendingTransactions() async throws -> [ZcashTransaction.Overview] {
let latestScannedHeight = self.latestState.latestScannedHeight
return try await transactionRepository.findPendingTransactions(latestHeight: latestScannedHeight, offset: 0, limit: .max)
}
public func allTransactions() async throws -> [ZcashTransaction.Overview] {
return try await transactionRepository.find(offset: 0, limit: Int.max, kind: .all)
}
@ -541,7 +536,7 @@ public class SDKSynchronizer: Synchronizer {
return subject.eraseToAnyPublisher()
}
// MARK: notify state
private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState {
@ -553,9 +548,7 @@ public class SDKSynchronizer: Synchronizer {
),
transparentBalance: (try? await blockProcessor.getTransparentBalance(accountIndex: 0)) ?? .zero,
internalSyncStatus: status,
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
latestScannedTime: latestBlocksDataProvider.latestScannedTime
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight
)
}
@ -619,12 +612,6 @@ extension SDKSynchronizer {
(try? await allReceivedTransactions()) ?? []
}
}
public var pendingTransactions: [ZcashTransaction.Overview] {
get async {
(try? await allPendingTransactions()) ?? []
}
}
}
extension InternalSyncStatus {

View File

@ -59,7 +59,13 @@ class SDKSynchronizerAliasDarksideTests: ZcashTestCase {
endpoint: endpoint
)
try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName)
try await coordinator.reset(
saplingActivation: birthday,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: branchID,
chainName: chainName
)
coordinators.append(coordinator)
}

View File

@ -27,13 +27,21 @@ class AdvancedReOrgTests: ZcashTestCase {
override func setUp() async throws {
try await super.setUp()
// don't use an exact birthday, users never do.
self.coordinator = try await TestCoordinator(
container: mockContainer,
walletBirthday: birthday + 50,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
}
override func tearDown() async throws {
@ -359,25 +367,26 @@ class AdvancedReOrgTests: ZcashTestCase {
sleep(2)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
/*
7. sync to sentTxHeight + 1
*/
let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
XCTAssertEqual(pMinedHeight, sentTxHeight)
sentTxSyncExpectation.fulfill()
},
error: self.handleError
)
} catch {
await handleError(error)
}
await fulfillment(of: [sentTxSyncExpectation], timeout: 5)
// let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
//
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
// XCTAssertEqual(pMinedHeight, sentTxHeight)
// sentTxSyncExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// await handleError(error)
// }
//
// await fulfillment(of: [sentTxSyncExpectation], timeout: 5)
/*
8. stage sentTx and otherTx at sentTxheight
@ -393,28 +402,29 @@ class AdvancedReOrgTests: ZcashTestCase {
sleep(2)
print("Starting after reorg sync")
let afterReOrgExpectation = XCTestExpectation(description: "after ReOrg Expectation")
do {
try await coordinator.sync(
completion: { synchronizer in
/*
11. verify that the sent tx is mined and balance is correct
*/
let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
XCTAssertEqual(pMinedHeight, sentTxHeight)
// fee change on this branch
let expectedBalance = try await synchronizer.getShieldedBalance()
XCTAssertEqual(initialTotalBalance - sendAmount - Zatoshi(1000), expectedBalance)
afterReOrgExpectation.fulfill()
},
error: self.handleError
)
} catch {
await handleError(error)
}
await fulfillment(of: [afterReOrgExpectation], timeout: 5)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// print("Starting after reorg sync")
// let afterReOrgExpectation = XCTestExpectation(description: "after ReOrg Expectation")
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// /*
// 11. verify that the sent tx is mined and balance is correct
// */
// let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
// XCTAssertEqual(pMinedHeight, sentTxHeight)
// // fee change on this branch
// let expectedBalance = try await synchronizer.getShieldedBalance()
// XCTAssertEqual(initialTotalBalance - sendAmount - Zatoshi(1000), expectedBalance)
// afterReOrgExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// await handleError(error)
// }
//
// await fulfillment(of: [afterReOrgExpectation], timeout: 5)
/*
12. applyStaged(sentTx + 10)
@ -441,8 +451,9 @@ class AdvancedReOrgTests: ZcashTestCase {
let expectedVerifiedBalance = initialTotalBalance + pendingTx.value
let currentVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedPendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(expectedPendingTransactionsCount, 0)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let expectedPendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
// XCTAssertEqual(expectedPendingTransactionsCount, 0)
XCTAssertEqual(expectedVerifiedBalance, currentVerifiedBalance)
let resultingBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
@ -793,7 +804,7 @@ class AdvancedReOrgTests: ZcashTestCase {
await fulfillment(of: [firstSyncExpectation], timeout: 5)
sleep(1)
let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
// let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: ZcashTransaction.Overview?
@ -867,16 +878,17 @@ class AdvancedReOrgTests: ZcashTestCase {
await fulfillment(of: [secondSyncExpectation], timeout: 5)
var pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(pendingTransactionsCount, 1)
guard let afterStagePendingTx = await coordinator.synchronizer.pendingTransactions.first else {
return
}
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// var pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
// XCTAssertEqual(pendingTransactionsCount, 1)
// guard let afterStagePendingTx = await coordinator.synchronizer.pendingTransactions.first else {
// return
// }
/*
6a. verify that there's a pending transaction with a mined height of sentTxHeight
*/
XCTAssertEqual(afterStagePendingTx.minedHeight, sentTxHeight)
// XCTAssertEqual(afterStagePendingTx.minedHeight, sentTxHeight)
/*
7. stage 20 blocks from sentTxHeight
@ -912,17 +924,18 @@ class AdvancedReOrgTests: ZcashTestCase {
}
await fulfillment(of: [reorgExpectation, afterReorgExpectation], timeout: 5)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
/*
10. verify that there's a pending transaction with -1 mined height
*/
guard let newPendingTx = await coordinator.synchronizer.pendingTransactions.first else {
XCTFail("No pending transaction")
try await coordinator.stop()
return
}
XCTAssertNil(newPendingTx.minedHeight)
// guard let newPendingTx = await coordinator.synchronizer.pendingTransactions.first else {
// XCTFail("No pending transaction")
// try await coordinator.stop()
// return
// }
//
// XCTAssertNil(newPendingTx.minedHeight)
/*
11. applyHeight(sentTxHeight + 2)
@ -947,19 +960,20 @@ class AdvancedReOrgTests: ZcashTestCase {
}
await fulfillment(of: [yetAnotherExpectation], timeout: 5)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
/*
12. verify that there's a pending transaction with a mined height of sentTxHeight + 2
*/
pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(pendingTransactionsCount, 1)
guard let newlyPendingTx = try await coordinator.synchronizer.allPendingTransactions().first(where: { $0.isSentTransaction }) else {
XCTFail("no pending transaction")
try await coordinator.stop()
return
}
XCTAssertEqual(newlyPendingTx.minedHeight, sentTxHeight + 2)
// pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
// XCTAssertEqual(pendingTransactionsCount, 1)
// guard let newlyPendingTx = try await coordinator.synchronizer.allPendingTransactions().first(where: { $0.isSentTransaction }) else {
// XCTFail("no pending transaction")
// try await coordinator.stop()
// return
// }
//
// XCTAssertEqual(newlyPendingTx.minedHeight, sentTxHeight + 2)
/*
13. apply height(sentTxHeight + 25)
@ -989,38 +1003,39 @@ class AdvancedReOrgTests: ZcashTestCase {
/*
15. verify that there's no pending transaction and that the tx is displayed on the sentTransactions collection
*/
let pendingTranscationsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(pendingTranscationsCount, 0)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let pendingTranscationsCount = await coordinator.synchronizer.pendingTransactions.count
// XCTAssertEqual(pendingTranscationsCount, 0)
let sentTransactions = await coordinator.synchronizer.sentTransactions
.first(
where: { transaction in
return transaction.rawID == newlyPendingTx.rawID
}
)
XCTAssertNotNil(
sentTransactions,
"Sent Tx is not on sent transactions"
)
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
XCTAssertEqual(
initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
expectedBalance
)
XCTAssertEqual(
initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
expectedVerifiedBalance
)
let txRecipients = await coordinator.synchronizer.getRecipients(for: newPendingTx)
XCTAssertEqual(txRecipients.count, 2)
XCTAssertNotNil(txRecipients.first(where: { $0 == .internalAccount(0) }))
XCTAssertNotNil(txRecipients.first(where: { $0 == .address(recipient) }))
// let sentTransactions = await coordinator.synchronizer.sentTransactions
// .first(
// where: { transaction in
// return transaction.rawID == newlyPendingTx.rawID
// }
// )
//
// XCTAssertNotNil(
// sentTransactions,
// "Sent Tx is not on sent transactions"
// )
//
// let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
// let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
//
// XCTAssertEqual(
// initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
// expectedBalance
// )
//
// XCTAssertEqual(
// initialTotalBalance + newlyPendingTx.value, // Note: sent transactions have negative values
// expectedVerifiedBalance
// )
//
// let txRecipients = await coordinator.synchronizer.getRecipients(for: newPendingTx)
// XCTAssertEqual(txRecipients.count, 2)
// XCTAssertNotNil(txRecipients.first(where: { $0 == .internalAccount(0) }))
// XCTAssertNotNil(txRecipients.first(where: { $0 == .address(recipient) }))
}
/// Uses the zcash-hackworks data set.
@ -1037,11 +1052,20 @@ class AdvancedReOrgTests: ZcashTestCase {
/// 8. sync to latest height
/// 9. verify that the balance is equal to the one before the reorg
func testReOrgChangesInboundMinedHeight() async throws {
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName)
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: branchID,
chainName: chainName
)
sleep(2)
try coordinator.resetBlocks(dataset: .predefined(dataset: .txHeightReOrgBefore))
sleep(2)
try coordinator.applyStaged(blockheight: 663195)
sleep(2)
let firstSyncExpectation = XCTestExpectation(description: "first sync")
@ -1102,7 +1126,14 @@ class AdvancedReOrgTests: ZcashTestCase {
// FIXME [#644]: Test works with lightwalletd v0.4.13 but is broken when using newer lightwalletd. More info is in #644.
func testReOrgRemovesIncomingTxForever() async throws {
await hookToReOrgNotification()
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName)
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: branchID,
chainName: chainName
)
try coordinator.resetBlocks(dataset: .predefined(dataset: .txReOrgRemovesInboundTxBefore))
@ -1306,13 +1337,14 @@ class AdvancedReOrgTests: ZcashTestCase {
}
await fulfillment(of: [reorgExpectation, reorgSyncExpectation], timeout: 5)
guard let pendingTx = await coordinator.synchronizer.pendingTransactions.first else {
XCTFail("no pending transaction after reorg sync")
return
}
XCTAssertNil(pendingTx.minedHeight)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// guard let pendingTx = await coordinator.synchronizer.pendingTransactions.first else {
// XCTFail("no pending transaction after reorg sync")
// return
// }
//
// XCTAssertNil(pendingTx.minedHeight)
LoggerProxy.info("applyStaged(blockheight: \(sentTxHeight + extraBlocks - 1))")
try coordinator.applyStaged(blockheight: sentTxHeight + extraBlocks - 1)
@ -1369,9 +1401,10 @@ class AdvancedReOrgTests: ZcashTestCase {
}
await fulfillment(of: [firstSyncExpectation], timeout: 600)
let latestScannedHeight = await coordinator.synchronizer.latestBlocksDataProvider.latestScannedHeight
XCTAssertEqual(latestScannedHeight, birthday + fullSyncLength)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let latestScannedHeight = await coordinator.synchronizer.latestBlocksDataProvider.latestScannedHeight
// XCTAssertEqual(latestScannedHeight, birthday + fullSyncLength)
}
func handleError(_ error: Error?) async {

View File

@ -25,12 +25,20 @@ class BalanceTests: ZcashTestCase {
override func setUp() async throws {
try await super.setUp()
self.coordinator = try await TestCoordinator(
container: mockContainer,
walletBirthday: birthday,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main")
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: "e9ff75a6",
chainName: "main"
)
}
override func tearDown() async throws {
@ -136,25 +144,27 @@ class BalanceTests: ZcashTestCase {
try coordinator.stageTransaction(rawTx, at: sentTxHeight)
try coordinator.applyStaged(blockheight: sentTxHeight)
sleep(2) // add enhance breakpoint here
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertNotNil(pendingEntity?.minedHeight)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill()
},
error: self.handleError
)
} catch {
handleError(error)
}
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
// XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
// XCTAssertNotNil(pendingEntity?.minedHeight)
// XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
// mineExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// handleError(error)
// }
//
// await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
//
// 7 advance to confirmation
try coordinator.applyStaged(blockheight: sentTxHeight + 10)
@ -181,11 +191,12 @@ class BalanceTests: ZcashTestCase {
}
await fulfillment(of: [confirmExpectation], timeout: 5)
let confirmedPending = try await coordinator.synchronizer.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let confirmedPending = try await coordinator.synchronizer.allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
//
// XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
@ -284,24 +295,26 @@ class BalanceTests: ZcashTestCase {
try coordinator.stageTransaction(rawTx, at: sentTxHeight)
try coordinator.applyStaged(blockheight: sentTxHeight)
sleep(2) // add enhance breakpoint here
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertNotNil(pendingEntity?.minedHeight)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill()
},
error: self.handleError
)
} catch {
handleError(error)
}
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
// XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
// XCTAssertNotNil(pendingEntity?.minedHeight)
// XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
// mineExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// handleError(error)
// }
//
// await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation
@ -331,11 +344,12 @@ class BalanceTests: ZcashTestCase {
await fulfillment(of: [confirmExpectation], timeout: 5)
let confirmedPending = try await coordinator.synchronizer
.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let confirmedPending = try await coordinator.synchronizer
// .allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
//
// XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
@ -493,24 +507,26 @@ class BalanceTests: ZcashTestCase {
try coordinator.stageTransaction(rawTx, at: sentTxHeight)
try coordinator.applyStaged(blockheight: sentTxHeight)
sleep(2) // add enhance breakpoint here
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
XCTAssertTrue(pendingEntity?.minedHeight != nil)
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
mineExpectation.fulfill()
},
error: self.handleError
)
} catch {
handleError(error)
}
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pendingEntity = try await synchronizer.allPendingTransactions().first(where: { $0.rawID == pendingTx.rawID })
// XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
// XCTAssertTrue(pendingEntity?.minedHeight != nil)
// XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
// mineExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// handleError(error)
// }
//
// await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation
let advanceToConfirmation = sentTxHeight + 10
@ -539,12 +555,13 @@ class BalanceTests: ZcashTestCase {
}
await fulfillment(of: [confirmExpectation], timeout: 5)
let confirmedPending = try await coordinator.synchronizer
.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let confirmedPending = try await coordinator.synchronizer
// .allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
//
// XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()

View File

@ -33,7 +33,14 @@ class DarksideSanityCheckTests: ZcashTestCase {
network: network
)
try await coordinator.reset(saplingActivation: self.birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: self.birthday,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
try self.coordinator.resetBlocks(dataset: .default)
}
@ -48,7 +55,7 @@ class DarksideSanityCheckTests: ZcashTestCase {
}
func testDarkside() async throws {
let expectedFirstBlock = (height: BlockHeight(663150), hash: "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc")
// let expectedFirstBlock = (height: BlockHeight(663150), hash: "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc")
let expectedLastBlock = (height: BlockHeight(663200), hash: "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a")
try coordinator.applyStaged(blockheight: expectedLastBlock.height)
@ -72,13 +79,14 @@ class DarksideSanityCheckTests: ZcashTestCase {
)
await fulfillment(of: [syncExpectation], timeout: 5)
let blocksDao = BlockSQLDAO(dbProvider: SimpleConnectionProvider(path: coordinator.databases.dataDB.absoluteString, readonly: false))
let firstBlock = try blocksDao.block(at: expectedFirstBlock.height)
let lastBlock = try blocksDao.block(at: expectedLastBlock.height)
XCTAssertEqual(firstBlock?.hash.toHexStringTxId(), expectedFirstBlock.hash)
XCTAssertEqual(lastBlock?.hash.toHexStringTxId(), expectedLastBlock.hash)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let blocksDao = BlockSQLDAO(dbProvider: SimpleConnectionProvider(path: coordinator.databases.dataDB.absoluteString, readonly: false))
//
// let firstBlock = try blocksDao.block(at: expectedFirstBlock.height)
// let lastBlock = try blocksDao.block(at: expectedLastBlock.height)
//
// XCTAssertEqual(firstBlock?.hash.toHexStringTxId(), expectedFirstBlock.hash)
// XCTAssertEqual(lastBlock?.hash.toHexStringTxId(), expectedLastBlock.hash)
}
}

View File

@ -30,7 +30,14 @@ class PendingTransactionUpdatesTest: ZcashTestCase {
walletBirthday: birthday,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main")
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: "e9ff75a6",
chainName: "main"
)
}
override func tearDown() async throws {
@ -163,19 +170,20 @@ class PendingTransactionUpdatesTest: ZcashTestCase {
await fulfillment(of: [secondSyncExpectation], timeout: 5)
let pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
XCTAssertEqual(pendingTransactionsCount, 1)
guard let afterStagePendingTx = await coordinator.synchronizer.pendingTransactions.first else {
return
}
/*
6a. verify that there's a pending transaction with a mined height of sentTxHeight
*/
LoggerProxy.info("6a. verify that there's a pending transaction with a mined height of \(sentTxHeight)")
XCTAssertEqual(afterStagePendingTx.minedHeight, sentTxHeight)
XCTAssertNotNil(afterStagePendingTx.minedHeight, "pending transaction shown as unmined when it has been mined")
XCTAssertTrue(afterStagePendingTx.isPending(currentHeight: sentTxHeight))
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let pendingTransactionsCount = await coordinator.synchronizer.pendingTransactions.count
// XCTAssertEqual(pendingTransactionsCount, 1)
// guard let afterStagePendingTx = await coordinator.synchronizer.pendingTransactions.first else {
// return
// }
//
// /*
// 6a. verify that there's a pending transaction with a mined height of sentTxHeight
// */
// LoggerProxy.info("6a. verify that there's a pending transaction with a mined height of \(sentTxHeight)")
// XCTAssertEqual(afterStagePendingTx.minedHeight, sentTxHeight)
// XCTAssertNotNil(afterStagePendingTx.minedHeight, "pending transaction shown as unmined when it has been mined")
// XCTAssertTrue(afterStagePendingTx.isPending(currentHeight: sentTxHeight))
/*
7. stage 15 blocks from sentTxHeight
@ -206,16 +214,17 @@ class PendingTransactionUpdatesTest: ZcashTestCase {
await handleError(error)
}
await fulfillment(of: [syncToConfirmExpectation], timeout: 6)
let supposedlyPendingUnexistingTransaction = try await coordinator.synchronizer.allPendingTransactions().first
let clearedTransactions = await coordinator.synchronizer
.transactions
let clearedTransaction = clearedTransactions.first(where: { $0.rawID == afterStagePendingTx.rawID })
XCTAssertEqual(clearedTransaction!.value.amount, afterStagePendingTx.value.amount)
XCTAssertNil(supposedlyPendingUnexistingTransaction)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// await fulfillment(of: [syncToConfirmExpectation], timeout: 6)
// let supposedlyPendingUnexistingTransaction = try await coordinator.synchronizer.allPendingTransactions().first
//
// let clearedTransactions = await coordinator.synchronizer
// .transactions
//
// let clearedTransaction = clearedTransactions.first(where: { $0.rawID == afterStagePendingTx.rawID })
//
// XCTAssertEqual(clearedTransaction!.value.amount, afterStagePendingTx.value.amount)
// XCTAssertNil(supposedlyPendingUnexistingTransaction)
}
func handleError(_ error: Error?) async {

View File

@ -50,7 +50,13 @@ class ReOrgTests: ZcashTestCase {
network: self.network
)
try await coordinator.reset(saplingActivation: self.birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: self.birthday,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
try self.coordinator.resetBlocks(dataset: .default)
@ -128,7 +134,14 @@ class ReOrgTests: ZcashTestCase {
targetHeight: BlockHeight
) async throws {
do {
try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName)
try await coordinator.reset(
saplingActivation: birthday,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: branchID,
chainName: chainName
)
try coordinator.resetBlocks(dataset: .predefined(dataset: .beforeReOrg))
try coordinator.applyStaged(blockheight: firstLatestHeight)
sleep(1)

View File

@ -35,7 +35,14 @@ class RewindRescanTests: ZcashTestCase {
walletBirthday: birthday,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main")
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: "e9ff75a6",
chainName: "main"
)
}
override func tearDown() async throws {
@ -111,8 +118,9 @@ class RewindRescanTests: ZcashTestCase {
await fulfillment(of: [rewindExpectation], timeout: 2)
// assert that after the new height is
let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
XCTAssertEqual(lastScannedHeight, self.birthday)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
// XCTAssertEqual(lastScannedHeight, self.birthday)
// check that the balance is cleared
var expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
@ -303,8 +311,9 @@ class RewindRescanTests: ZcashTestCase {
// assert that after the new height is lower or same as transaction, rewind doesn't have to be make exactly to transaction height, it can
// be done to nearest height provided by rust
let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
XCTAssertLessThanOrEqual(lastScannedHeight, transaction.anchor(network: network) ?? -1)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
// XCTAssertLessThanOrEqual(lastScannedHeight, transaction.anchor(network: network) ?? -1)
let secondScanExpectation = XCTestExpectation(description: "rescan")
@ -413,24 +422,25 @@ class RewindRescanTests: ZcashTestCase {
try coordinator.applyStaged(blockheight: sentTxHeight)
sleep(2)
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pendingTransaction = try await synchronizer.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now")
XCTAssertNotNil(pendingTransaction?.minedHeight)
XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight)
mineExpectation.fulfill()
}, error: self.handleError
)
} catch {
handleError(error)
}
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pendingTransaction = try await synchronizer.allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
// XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now")
// XCTAssertNotNil(pendingTransaction?.minedHeight)
// XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight)
// mineExpectation.fulfill()
// }, error: self.handleError
// )
// } catch {
// handleError(error)
// }
//
// await fulfillment(of: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
// 7 advance to confirmation
let advanceToConfirmationHeight = sentTxHeight + 10
@ -464,15 +474,16 @@ class RewindRescanTests: ZcashTestCase {
await fulfillment(of: [rewindExpectation], timeout: 2)
guard
let pendingEntity = try await coordinator.synchronizer.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
else {
XCTFail("sent pending transaction not found after rewind")
return
}
XCTAssertNil(pendingEntity.minedHeight)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// guard
// let pendingEntity = try await coordinator.synchronizer.allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
// else {
// XCTFail("sent pending transaction not found after rewind")
// return
// }
//
// XCTAssertNil(pendingEntity.minedHeight)
let confirmExpectation = XCTestExpectation(description: "confirm expectation")
notificationHandler.transactionsFound = { txs in
@ -500,11 +511,12 @@ class RewindRescanTests: ZcashTestCase {
}
await fulfillment(of: [confirmExpectation], timeout: 10)
let confirmedPending = try await coordinator.synchronizer.allPendingTransactions()
.first(where: { $0.rawID == pendingTx.rawID })
XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let confirmedPending = try await coordinator.synchronizer.allPendingTransactions()
// .first(where: { $0.rawID == pendingTx.rawID })
//
// XCTAssertNil(confirmedPending, "pending, now confirmed transaction found")
let expectedVerifiedbalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()

View File

@ -30,7 +30,15 @@ class ShieldFundsTests: ZcashTestCase {
walletBirthday: birthday,
network: network
)
try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 1120954, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: birthday,
startSaplingTreeSize: 1120954,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
try coordinator.service.clearAddedUTXOs()
}

View File

@ -40,7 +40,13 @@ class SynchronizerDarksideTests: ZcashTestCase {
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main")
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: "e9ff75a6",
chainName: "main"
)
}
override func tearDown() async throws {
@ -187,45 +193,35 @@ class SynchronizerDarksideTests: ZcashTestCase {
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: .unprepared,
latestScannedHeight: 0,
latestBlockHeight: 0,
latestScannedTime: 0
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: .syncing(0),
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
internalSyncStatus: .syncing(0.9),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
internalSyncStatus: .syncing(1.0),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
internalSyncStatus: .synced,
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
)
]
@ -274,45 +270,35 @@ class SynchronizerDarksideTests: ZcashTestCase {
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: .unprepared,
latestScannedHeight: 0,
latestBlockHeight: 0,
latestScannedTime: 0
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: .zero,
transparentBalance: .zero,
internalSyncStatus: .syncing(0),
latestScannedHeight: 663150,
latestBlockHeight: 0,
latestScannedTime: 1576821833.0
latestBlockHeight: 0
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
internalSyncStatus: .syncing(0.9),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: .zero,
internalSyncStatus: .syncing(1.0),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[0],
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
internalSyncStatus: .synced,
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1
latestBlockHeight: 663189
)
]
@ -346,36 +332,28 @@ class SynchronizerDarksideTests: ZcashTestCase {
shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
internalSyncStatus: .syncing(0),
latestScannedHeight: 663189,
latestBlockHeight: 663189,
latestScannedTime: 1.0
latestBlockHeight: 663189
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
internalSyncStatus: .syncing(0.9),
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
latestBlockHeight: 663200
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
internalSyncStatus: .syncing(1.0),
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
latestBlockHeight: 663200
),
SynchronizerState(
syncSessionID: uuids[1],
shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)),
transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)),
internalSyncStatus: .synced,
latestScannedHeight: 663200,
latestBlockHeight: 663200,
latestScannedTime: 1
latestBlockHeight: 663200
)
]

View File

@ -33,7 +33,14 @@ final class SynchronizerTests: ZcashTestCase {
walletBirthday: birthday + 50,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
switch event {
@ -320,8 +327,9 @@ final class SynchronizerTests: ZcashTestCase {
await fulfillment(of: [rewindExpectation], timeout: 5)
// assert that after the new height is
let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
XCTAssertEqual(lastScannedHeight, self.birthday)
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
// let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
// XCTAssertEqual(lastScannedHeight, self.birthday)
// check that the balance is cleared
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()

View File

@ -77,14 +77,10 @@ class TransactionEnhancementTests: ZcashTestCase {
let dbInit = try await rustBackend.initDataDb(seed: nil)
let derivationTool = DerivationTool(networkType: network.networkType)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: Environment.seedBytes, accountIndex: 0)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
do {
_ = try await rustBackend.createAccount(
seed: Environment.seedBytes,
treeState: birthday.treeState().serializedData(partial: false).bytes,
treeState: birthday.treeState(),
recoverUntil: nil
)
} catch {
@ -113,14 +109,6 @@ class TransactionEnhancementTests: ZcashTestCase {
)
try! await storage.create()
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
downloader = BlockDownloaderServiceImpl(service: service, storage: storage)
Dependencies.setup(
@ -138,8 +126,8 @@ class TransactionEnhancementTests: ZcashTestCase {
loggingPolicy: .default(.debug)
)
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in
LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { [self] _ in
LatestBlocksDataProviderImpl(service: service, rustBackend: self.rustBackend)
}
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackend }

View File

@ -33,7 +33,14 @@ class Z2TReceiveTests: ZcashTestCase {
walletBirthday: birthday,
network: network
)
try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName)
try await coordinator.reset(
saplingActivation: 663150,
startSaplingTreeSize: 128607,
startOrchardTreeSize: 0,
branchID: self.branchID,
chainName: self.chainName
)
}
override func tearDown() async throws {
@ -188,26 +195,27 @@ class Z2TReceiveTests: ZcashTestCase {
sleep(2)
self.foundTransactionsExpectation = XCTestExpectation(description: "inbound expectation")
// TODO: [#1247] needs to review this to properly solve, https://github.com/zcash/ZcashLightClientKit/issues/1247
/*
7. sync to sentTxHeight + 1
*/
let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
// let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
do {
try await coordinator.sync(
completion: { synchronizer in
let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
XCTAssertEqual(pMinedHeight, sentTxHeight)
sentTxSyncExpectation.fulfill()
},
error: self.handleError
)
} catch {
await handleError(error)
}
await fulfillment(of: [sentTxSyncExpectation, foundTransactionsExpectation], timeout: 5)
// do {
// try await coordinator.sync(
// completion: { synchronizer in
// let pMinedHeight = await synchronizer.pendingTransactions.first?.minedHeight
// XCTAssertEqual(pMinedHeight, sentTxHeight)
//
// sentTxSyncExpectation.fulfill()
// },
// error: self.handleError
// )
// } catch {
// await handleError(error)
// }
//
// await fulfillment(of: [sentTxSyncExpectation, foundTransactionsExpectation], timeout: 5)
}
func handleError(_ error: Error?) async {

View File

@ -100,7 +100,6 @@ class BlockStreamingTest: ZcashTestCase {
}
let transactionRepositoryMock = TransactionRepositoryMock()
transactionRepositoryMock.lastScannedHeightReturnValue = startHeight
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
let blockDownloader = BlockDownloaderImpl(
@ -135,13 +134,9 @@ class BlockStreamingTest: ZcashTestCase {
let action = DownloadAction(container: mockContainer, configProvider: CompactBlockProcessor.ConfigProvider(config: processorConfig))
let blockDownloader = mockContainer.resolve(BlockDownloader.self)
let syncControlData = SyncControlData(
latestBlockHeight: latestBlockHeight,
latestScannedHeight: startHeight,
firstUnenhancedHeight: nil
)
let context = ActionContext(state: .download)
await context.update(syncControlData: syncControlData)
let context = ActionContextMock()
context.updateStateClosure = { _ in }
let expectation = XCTestExpectation()
@ -175,8 +170,10 @@ class BlockStreamingTest: ZcashTestCase {
latestScannedHeight: startHeight,
firstUnenhancedHeight: nil
)
let context = ActionContext(state: .download)
await context.update(syncControlData: syncControlData)
let context = ActionContextMock()
context.updateStateClosure = { _ in }
context.underlyingSyncControlData = syncControlData
context.lastScannedHeight = startHeight
let date = Date()

View File

@ -66,14 +66,6 @@ class CompactBlockProcessorTests: ZcashTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
})
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
Dependencies.setup(
in: mockContainer,
urls: Initializer.URLs(
@ -89,8 +81,8 @@ class CompactBlockProcessorTests: ZcashTestCase {
loggingPolicy: .default(.debug)
)
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in
LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { [self] _ in
LatestBlocksDataProviderImpl(service: service, rustBackend: self.rustBackend)
}
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackend }
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }

View File

@ -87,20 +87,7 @@ class CompactBlockReorgTests: ZcashTestCase {
return
}
rustBackendMockHelper = await RustBackendMockHelper(
rustBackend: rustBackend,
mockValidateCombinedChainFailAfterAttempts: 3,
mockValidateCombinedChainKeepFailing: false,
mockValidateCombinedChainFailureError: .rustValidateCombinedChainInvalidChain(Int32(network.constants.saplingActivationHeight + 320))
)
let transactionRepository = MockTransactionRepository(
unminedCount: 0,
receivedCount: 0,
sentCount: 0,
scannedHeight: 0,
network: network
)
rustBackendMockHelper = await RustBackendMockHelper(rustBackend: rustBackend)
Dependencies.setup(
in: mockContainer,
@ -117,8 +104,11 @@ class CompactBlockReorgTests: ZcashTestCase {
loggingPolicy: .default(.debug)
)
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in
LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
await self.rustBackendMockHelper.rustBackendMock.setPutSaplingSubtreeRootsStartIndexRootsClosure { _, _ in }
await self.rustBackendMockHelper.rustBackendMock.setUpdateChainTipHeightClosure { _ in }
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { [self] _ in
LatestBlocksDataProviderImpl(service: service, rustBackend: self.rustBackend)
}
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackendMockHelper.rustBackendMock }
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }

View File

@ -101,7 +101,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
}
func testPrepareSucceed() throws {
synchronizerMock.prepareWithWalletBirthdayClosure = { receivedSeed, receivedWalletBirthday in
synchronizerMock.prepareWithWalletBirthdayForClosure = { receivedSeed, receivedWalletBirthday, _ in
XCTAssertEqual(receivedSeed, self.data.seed)
XCTAssertEqual(receivedWalletBirthday, self.data.birthday)
return .success
@ -109,7 +109,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday) { result in
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday, for: .newWallet) { result in
switch result {
case let .success(status):
XCTAssertEqual(status, .success)
@ -123,13 +123,13 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
}
func testPrepareThrowsError() throws {
synchronizerMock.prepareWithWalletBirthdayClosure = { _, _ in
synchronizerMock.prepareWithWalletBirthdayForClosure = { _, _, _ in
throw "Some error"
}
let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday) { result in
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday, for: .newWallet) { result in
switch result {
case .success:
XCTFail("Error should be thrown.")
@ -408,20 +408,6 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5)
}
func testPendingTransactionsSucceed() {
synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity]
let expectation = XCTestExpectation()
synchronizer.pendingTransactions() { transactions in
XCTAssertEqual(transactions.count, 1)
XCTAssertEqual(transactions[0].id, self.data.pendingTransactionEntity.id)
expectation.fulfill()
}
wait(for: [expectation], timeout: 0.5)
}
func testClearedTransactionsSucceed() {
synchronizerMock.underlyingTransactions = [data.clearedTransaction]

View File

@ -101,8 +101,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
}
func testPrepareSucceed() throws {
let mockedViewingKey = self.data.viewingKey
synchronizerMock.prepareWithWalletBirthdayClosure = { receivedSeed, receivedWalletBirthday in
synchronizerMock.prepareWithWalletBirthdayForClosure = { receivedSeed, receivedWalletBirthday, _ in
XCTAssertEqual(receivedSeed, self.data.seed)
XCTAssertEqual(receivedWalletBirthday, self.data.birthday)
return .success
@ -110,7 +109,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday)
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday, for: .newWallet)
.sink(
receiveCompletion: { result in
switch result {
@ -130,14 +129,13 @@ class CombineSynchronizerOfflineTests: XCTestCase {
}
func testPrepareThrowsError() throws {
let mockedViewingKey = self.data.viewingKey
synchronizerMock.prepareWithWalletBirthdayClosure = { _, _ in
synchronizerMock.prepareWithWalletBirthdayForClosure = { _, _, _ in
throw "Some error"
}
let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday)
synchronizer.prepare(with: data.seed, walletBirthday: data.birthday, for: .newWallet)
.sink(
receiveCompletion: { result in
switch result {
@ -430,30 +428,6 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5)
}
func testPendingTransactionsSucceed() {
synchronizerMock.underlyingPendingTransactions = [data.pendingTransactionEntity]
let expectation = XCTestExpectation()
synchronizer.pendingTransactions
.sink(
receiveCompletion: { result in
switch result {
case .finished:
expectation.fulfill()
case let .failure(error):
XCTFail("Unpected failure with error: \(error)")
}
},
receiveValue: { value in
XCTAssertEqual(value.map { $0.id }, [self.data.pendingTransactionEntity.id])
}
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 0.5)
}
func testClearedTransactionsSucceed() {
synchronizerMock.underlyingTransactions = [data.clearedTransaction]

View File

@ -11,7 +11,7 @@ import XCTest
final class ActionContextStateTests: XCTestCase {
func testPreviousState() async throws {
let syncContext: ActionContext = .init(state: .idle)
let syncContext = ActionContextImpl(state: .idle)
await syncContext.update(state: .clearCache)

View File

@ -12,29 +12,57 @@ import XCTest
final class ClearAlreadyScannedBlocksActionTests: ZcashTestCase {
func testClearAlreadyScannedBlocksAction_NextAction() async throws {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let transactionRepositoryMock = TransactionRepositoryMock()
compactBlockRepositoryMock.clearUpToClosure = { _ in }
transactionRepositoryMock.lastScannedHeightReturnValue = 1
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
let clearAlreadyScannedBlocksAction = ClearAlreadyScannedBlocksAction(
container: mockContainer
)
let clearAlreadyScannedBlocksAction = setupAction(compactBlockRepositoryMock)
do {
let nextContext = try await clearAlreadyScannedBlocksAction.run(with: .init(state: .clearAlreadyScannedBlocks)) { _ in }
let context = ActionContextMock.default()
context.lastScannedHeight = -1
let nextContext = try await clearAlreadyScannedBlocksAction.run(with: context) { _ in }
XCTAssertTrue(compactBlockRepositoryMock.clearUpToCalled, "storage.clear(upTo:) is expected to be called.")
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .enhance,
"nextContext after .clearAlreadyScannedBlocks is expected to be .enhance but received \(nextState)"
)
let acResult = nextContext.checkStateIs(.enhance)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testClearAlreadyScannedBlocksAction_NextAction is not expected to fail. \(error)")
}
}
func testClearAlreadyScannedBlocksAction_LastScanHeightZcashError() async throws {
let clearAlreadyScannedBlocksAction = setupAction()
do {
let context = ActionContextMock()
_ = try await clearAlreadyScannedBlocksAction.run(with: context) { _ in }
XCTFail("testClearAlreadyScannedBlocksAction_LastScanHeightZcashError should throw an error so fail here is unexpected.")
} catch ZcashError.compactBlockProcessorLastScannedHeight {
// it's expected to end up here because we test that error is a specific one and Swift automatically catched it up for us
} catch {
XCTFail(
"""
testClearAlreadyScannedBlocksAction_NextAction is expected to fail
with ZcashError.compactBlockProcessorLastScannedHeight but received \(error)
"""
)
}
}
private func setupAction(
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock()
) -> ClearAlreadyScannedBlocksAction {
let transactionRepositoryMock = TransactionRepositoryMock()
compactBlockRepositoryMock.clearUpToClosure = { _ in }
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock }
return ClearAlreadyScannedBlocksAction(
container: mockContainer
)
}
}

View File

@ -10,27 +10,35 @@ import XCTest
@testable import ZcashLightClientKit
final class ClearCacheActionTests: ZcashTestCase {
func testClearCacheAction_NextAction() async throws {
func testClearCacheAction_MigrationLegacyCacheDB() async throws {
let compactBlockRepositoryMock = CompactBlockRepositoryMock()
let clearCacheAction = setupAction(compactBlockRepositoryMock)
do {
let context = ActionContextMock.default()
context.prevState = .idle
let nextContext = try await clearCacheAction.run(with: context) { _ in }
XCTAssertTrue(compactBlockRepositoryMock.clearCalled, "storage.clear() is expected to be called.")
let acResult = nextContext.checkStateIs(.processSuggestedScanRanges)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testClearCacheAction_MigrationLegacyCacheDB is not expected to fail. \(error)")
}
}
private func setupAction(
_ compactBlockRepositoryMock: CompactBlockRepositoryMock = CompactBlockRepositoryMock()
) -> ClearCacheAction {
compactBlockRepositoryMock.clearClosure = { }
mockContainer.mock(type: CompactBlockRepository.self, isSingleton: true) { _ in compactBlockRepositoryMock }
let clearCacheAction = ClearCacheAction(
return ClearCacheAction(
container: mockContainer
)
do {
let nextContext = try await clearCacheAction.run(with: .init(state: .clearCache)) { _ in }
XCTAssertTrue(compactBlockRepositoryMock.clearCalled, "storage.clear() is expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .finished,
"nextContext after .clearCache is expected to be .finished but received \(nextState)"
)
} catch {
XCTFail("testClearCacheAction_NextAction is not expected to fail. \(error)")
}
}
}

View File

@ -1,147 +0,0 @@
//
// ComputeSyncControlDataActionTests.swift
//
//
// Created by Lukáš Korba on 22.05.2023.
//
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
final class ComputeSyncControlDataActionTests: ZcashTestCase {
var underlyingDownloadRange: CompactBlockRange?
var underlyingScanRange: CompactBlockRange?
override func setUp() {
super.setUp()
underlyingDownloadRange = nil
underlyingScanRange = nil
}
func testComputeSyncControlDataAction_finishProcessingCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let loggerMock = LoggerMock()
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(
latestBlocksDataProviderMock.updateBlockDataCalled,
"latestBlocksDataProvider.updateBlockData() is expected to be called."
)
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .finished,
"nextContext after .computeSyncControlData is expected to be .finished but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncControlDataAction_finishProcessingCase is not expected to fail. \(error)")
}
}
func testComputeSyncControlDataAction_fetchUTXOsCase() async throws {
let blockDownloaderServiceMock = BlockDownloaderServiceMock()
let latestBlocksDataProviderMock = LatestBlocksDataProviderMock()
let loggerMock = LoggerMock()
let computeSyncControlDataAction = setupDefaultMocksAndReturnAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 10
let syncContext = await setupActionContext()
do {
let nextContext = try await computeSyncControlDataAction.run(with: syncContext) { _ in }
XCTAssertTrue(
latestBlocksDataProviderMock.updateScannedDataCalled,
"latestBlocksDataProvider.updateScannedData() is expected to be called."
)
XCTAssertTrue(latestBlocksDataProviderMock.updateBlockDataCalled, "latestBlocksDataProvider.updateBlockData() is expected to be called.")
XCTAssertFalse(loggerMock.infoFileFunctionLineCalled, "logger.info() is not expected to be called.")
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .fetchUTXO,
"nextContext after .computeSyncControlData is expected to be .fetchUTXO but received \(nextState)"
)
} catch {
XCTFail("testComputeSyncControlDataAction_checksBeforeSyncCase is not expected to fail. \(error)")
}
}
private func setupSyncControlData() -> SyncControlData {
SyncControlData(
latestBlockHeight: 0,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
}
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .computeSyncControlData)
await syncContext.update(syncControlData: setupSyncControlData())
await syncContext.update(totalProgressRange: CompactBlockRange(uncheckedBounds: (1000, 2000)))
return syncContext
}
private func setupAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncControlDataAction {
mockContainer.mock(type: BlockDownloaderService.self, isSingleton: true) { _ in blockDownloaderServiceMock }
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in latestBlocksDataProviderMock }
mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock }
let config: CompactBlockProcessor.Configuration = .standard(
for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0
)
return ComputeSyncControlDataAction(
container: mockContainer,
configProvider: CompactBlockProcessor.ConfigProvider(config: config)
)
}
private func setupDefaultMocksAndReturnAction(
_ blockDownloaderServiceMock: BlockDownloaderServiceMock = BlockDownloaderServiceMock(),
_ latestBlocksDataProviderMock: LatestBlocksDataProviderMock = LatestBlocksDataProviderMock(),
_ loggerMock: LoggerMock = LoggerMock()
) -> ComputeSyncControlDataAction {
blockDownloaderServiceMock.lastDownloadedBlockHeightReturnValue = 1
latestBlocksDataProviderMock.underlyingLatestBlockHeight = 1
latestBlocksDataProviderMock.underlyingLatestScannedHeight = 1
latestBlocksDataProviderMock.updateScannedDataClosure = { }
latestBlocksDataProviderMock.updateBlockDataClosure = { }
latestBlocksDataProviderMock.updateUnenhancedDataClosure = { }
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
return setupAction(
blockDownloaderServiceMock,
latestBlocksDataProviderMock,
loggerMock
)
}
}

View File

@ -13,16 +13,15 @@ final class DownloadActionTests: ZcashTestCase {
var underlyingDownloadRange: CompactBlockRange?
var underlyingScanRange: CompactBlockRange?
func testDownloadAction_NextAction() async throws {
func testDownloadAction_FullPass() async throws {
let blockDownloaderMock = BlockDownloaderMock()
let transactionRepositoryMock = TransactionRepositoryMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 1000
blockDownloaderMock.setSyncRangeBatchSizeClosure = { _, _ in }
blockDownloaderMock.setDownloadLimitClosure = { _ in }
blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in }
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in }
blockDownloaderMock.updateLatestDownloadedBlockHeightClosure = { _ in }
blockDownloaderMock.updateLatestDownloadedBlockHeightForceClosure = { _, _ in }
let downloadAction = setupAction(
blockDownloaderMock,
@ -32,25 +31,69 @@ final class DownloadActionTests: ZcashTestCase {
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
let syncContext = await setupActionContext()
let syncContext = ActionContextMock.default()
syncContext.lastScannedHeight = 1000
syncContext.underlyingSyncControlData = SyncControlData(
latestBlockHeight: 2000,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
do {
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertTrue(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is expected to be called.")
XCTAssertTrue(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is expected to be called.")
XCTAssertTrue(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is expected to be called.")
XCTAssertTrue(
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled,
"downloader.waitUntilRequestedBlocksAreDownloaded() is expected to be called."
blockDownloaderMock.setSyncRangeBatchSizeCallsCount == 1,
"downloader.setSyncRange() is expected to be called exatcly once."
)
XCTAssertTrue(blockDownloaderMock.setDownloadLimitCallsCount == 1, "downloader.setDownloadLimit() is expected to be called exatcly once.")
XCTAssertTrue(
blockDownloaderMock.startDownloadMaxBlockBufferSizeCallsCount == 1,
"downloader.startDownload() is expected to be called exatcly once."
)
XCTAssertTrue(
blockDownloaderMock.updateLatestDownloadedBlockHeightForceCallsCount == 1,
"downloader.update(latestDownloadedBlockHeight:) expected to be called exactly once."
)
XCTAssertTrue(
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCallsCount == 1,
"downloader.waitUntilRequestedBlocksAreDownloaded() is expected to be called exatcly once."
)
let nextState = await nextContext.state
let acResult = nextContext.checkStateIs(.scan)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
}
}
func testDownloadAction_LastScanHeightNil() async throws {
let blockDownloaderMock = BlockDownloaderMock()
let downloadAction = setupAction(blockDownloaderMock)
let syncContext = ActionContextMock.default()
do {
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
XCTAssertTrue(blockDownloaderMock.setSyncRangeBatchSizeCallsCount == 0, "downloader.setSyncRange() is not expected to be called.")
XCTAssertTrue(blockDownloaderMock.setDownloadLimitCallsCount == 0, "downloader.setDownloadLimit() is not expected to be called.")
XCTAssertTrue(
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
blockDownloaderMock.startDownloadMaxBlockBufferSizeCallsCount == 0,
"downloader.startDownload() is not expected to be called."
)
XCTAssertTrue(
blockDownloaderMock.updateLatestDownloadedBlockHeightForceCallsCount == 0,
"downloader.update(latestDownloadedBlockHeight:) is not expected to be called."
)
XCTAssertTrue(
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCallsCount == 0,
"downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called."
)
let acResult = nextContext.checkStateIs(.scan)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)")
}
@ -65,15 +108,17 @@ final class DownloadActionTests: ZcashTestCase {
transactionRepositoryMock
)
let syncContext = await setupActionContext()
let syncContext = ActionContextMock.default()
syncContext.lastScannedHeight = 1000
syncContext.underlyingSyncControlData = SyncControlData(
latestBlockHeight: 999,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
do {
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
XCTAssertFalse(
transactionRepositoryMock.lastScannedHeightCalled,
"transactionRepository.lastScannedHeight() is not expected to be called."
)
XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.")
XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.")
XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.")
@ -82,54 +127,13 @@ final class DownloadActionTests: ZcashTestCase {
"downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called."
)
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
)
let acResult = nextContext.checkStateIs(.scan)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)")
}
}
func testDownloadAction_NothingMoreToDownload() async throws {
let blockDownloaderMock = BlockDownloaderMock()
let transactionRepositoryMock = TransactionRepositoryMock()
transactionRepositoryMock.lastScannedHeightReturnValue = 2001
let downloadAction = setupAction(
blockDownloaderMock,
transactionRepositoryMock
)
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
let syncContext = await setupActionContext()
do {
let nextContext = try await downloadAction.run(with: syncContext) { _ in }
XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.")
XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.")
XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.")
XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.")
XCTAssertFalse(
blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled,
"downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called."
)
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .scan,
"nextContext after .download is expected to be .scan but received \(nextState)"
)
} catch {
XCTFail("testDownloadAction_NothingMoreToDownload is not expected to fail. \(error)")
}
}
func testDownloadAction_DownloadStops() async throws {
let blockDownloaderMock = BlockDownloaderMock()
@ -143,21 +147,7 @@ final class DownloadActionTests: ZcashTestCase {
XCTAssertTrue(blockDownloaderMock.stopDownloadCalled, "downloader.stopDownload() is expected to be called.")
}
private func setupActionContext() async -> ActionContext {
let syncContext: ActionContext = .init(state: .download)
let syncControlData = SyncControlData(
latestBlockHeight: 2000,
latestScannedHeight: underlyingScanRange?.lowerBound,
firstUnenhancedHeight: nil
)
await syncContext.update(syncControlData: syncControlData)
return syncContext
}
private func setupAction(
_ blockDownloaderMock: BlockDownloaderMock = BlockDownloaderMock(),
_ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(),

Some files were not shown because too many files have changed in this diff Show More