Merge pull request #1243 from zcash/feature/DAG-sync
Implement the ability to spend funds without complete wallet sync.
This commit is contained in:
commit
552d31eb64
980
CHANGELOG.md
980
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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] = [
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 😎"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 { }
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 { }
|
||||
}
|
|
@ -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 { }
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { }
|
||||
}
|
|
@ -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 { }
|
||||
}
|
|
@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
|
|||
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
|
||||
}
|
||||
|
||||
await context.update(state: .computeSyncControlData)
|
||||
await context.update(state: .fetchUTXO)
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -86,11 +86,6 @@ extension UTXOFetcherImpl: UTXOFetcher {
|
|||
}
|
||||
|
||||
metrics.pushProgressReport(
|
||||
progress: BlockProgress(
|
||||
startHeight: 0,
|
||||
targetHeight: 1,
|
||||
progressHeight: 1
|
||||
),
|
||||
start: startTime,
|
||||
end: Date(),
|
||||
batchSize: 1,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2180000",
|
||||
"hash": "0000000000e4ccc66506119f466fc73bf444e67dbb36837dce20e5359285c5d1",
|
||||
"time": 1691171949,
|
||||
"saplingTree": "017c3a0e28e49d0167b3f00b20af65a97acf9ed8e5722d4ddfb9f746a6947a802901cba9695fd51403fb1b43df56160802d77fea30089897bab11f8b8ef47e071b481a00012b83799df1fdae5c05750403c99212aa99ff5cd6cda244ae2e06cf95b7e6043201026fb7385ce7dc10866511f126243a245b613e7b4599007b9e9c702a8f21355a019edefa4dc3174154cdbb49049c20c4412d51e0ffc9f57bd4fa7bde5acd21750401a4caa58083087d008c5504b3bf5a110485e642594c4d9ff28f1c11db4e896d5b000001fd8d242d6fe4f56cc961db7efb419c3f5adbbfc189a15ad695ed73cdcc0c5122000001d37b567c735b8ca833b4dfca1a9778b1e49601012bc1c241e0c9c703127e396e012caa02bfcb0a4a42c05654947def2e5334e3d2a86be2d5055599e818b8c4203201f907335382e184f41fe7d67b810de31664c9abcba337743e545424c31531305b000000000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "018dc1264af083d492e5194a135e6a52446e941b46aaf6b3677871aa2cc6b3f102001f000001a70641e2426cc4f0b84b46e6da920f47b8f0db14ffa55c7f2f5e17f18d508507000157504e873fac8b08f33eede7cb73c5e0130d03b7342bf5a64687580308a64826000001731f3ca7f1d71bf8c1fca458121b4945eadcb8ed72eb628fc41f0970d6bcab3e015d374c7e9b988ff2fd94f696e9cbf25d49ea0b0e63b460c0fd2c4b23c5afbe07019bf0e257d9fd2bea1ea9916c453e973e54776f3191db7bb92717f29405cacb0f01c96b61212e081be8a78cbbc0366307c4ccf6f62a40a4a329c54eab4d2ca08c1a00010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2182500",
|
||||
"hash": "0000000001f02afbe3cfabf5ca66d1c9015902272d042859e63a02252f8a808f",
|
||||
"time": 1691361773,
|
||||
"saplingTree": "01f7c023939f34e22dc0bbe4070fc07a171b2daf97ce083d230767111bd50d1f65001a0149164f0dda6ee264c12b521c4c852ea1d7871f60da8ec55235f6564a6938202b01eb7c2bcaec56d4e0ab10c527b0c5f06189cfa44fc58dcfcc6cd446509f9c1311016ef018e4d38c5dd02bc3c11b88e537acccf6cd47b8872aa943af9c0ca578ce1c012ef3d75a96a99b613da46370130c1c34e4fc56c25b43c2eeaf4b0f05bd4e795d0001af1febcedbe8fde6d146214a9bf6cd30aeff767e52326f233927684dedadc062000001e5b89f59ae0931fb87c298079f00269dc4236e662c07806637d5ac05a456472201eb5197ee95ca2517cfc5a3286fa3656d2d1525779375b3c9397de0b337be980f01dc56bc03ca6744aa76624b4eaae01c57444507dd20af8a068a4f2a5814964d3100000001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01460512f2c839946a36daaa2f964d380c6f157ab0bb2123322ea2978d3cd58005001f0156fd30e2f6add5a31d5e2643974f5603d6dd44f2d2a3c6d86bc4d1ea6b61292101aaf44291f1f4f39031dcdac0e24a4949319e371b2f709320f490ec17ff79333600000108a7542ee3b4449be51e29e67a2e19a20180a0bc4d3b45454251907edeabca1a00000157cc0707c1a6b1ad894c7c3f2f00476333182b760f72270a369f9489c64dc30f01a58d2ba2e28de6e43054887c043b055305512eb5589f737a5b760e4e58f6ab1b000001202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2185000",
|
||||
"hash": "0000000000f46791a13f2f880f4fce274d3e517906b14a841737e23c9bf82cd5",
|
||||
"time": 1691549521,
|
||||
"saplingTree": "01cfcc6c092b5045179d5e80fb9c82d42e2d9e1c491509db3191a0f15cbf211307001a00000001811ac687f965b2a12bc50e71ad5114c0462d149a9878ed356c64ad5a1bcf73560001e85d0f79714d2b13d1cae642b56b4215270a602b09cb0a297318383b9b5ae72100012b8263b471898cecdd78962397460aeb01eb93e2baad1db7e3322f7fe3c7795e00015572128e7cb9eea84008c565fb9b89c29b342c220846fb84a72920e009400830000001d79ae9580f8cbe4d7fa9039930f80f45710bb33ffc7fb90c171eb7e18091674d0001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01cb48de67532673ae95ec8de43dd7d409b4f07862302ab0feffffffe4534c032d001f0000000001acb335b0e92b325d35207bcdb98c719acb6fe7c4153c015842b2beaf45fe5c04017fc8e1f88f78e4027a4c7a4beda5f72e867bb6c822fcdfaf143fd7f6768cee2c00000000012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2187500",
|
||||
"hash": "0000000001dead709f146a665218857fc89e62ba69fd1ced00af7391b8769c75",
|
||||
"time": 1691737738,
|
||||
"saplingTree": "01ed0c3ce7c8bf8328c20328474948ea00d91fc95994acdbaf4e50b04ec112c118013da7fb206c6ab6cb215c8fd51c88d3d05a3b13419c7aa9533cd2bf36dfd021711a0128422406ee752179045949f83476d4921052fc1dc7544920300aae0f05876d3701e86b2f970dff0e4b591e210aa7778ab4e2be5644b1e5d9a85137eddc92139e1301e6b5bc372a0c25dc90187733f55ed31e2f03fe2a75f59750f4c0febc15c57d230001f79bf2cb06f09ba4c898aabeff0f1b6c8feef1b492622489aa33420f4543dc4801d7c4cee663ecb21b9d4d4c5c08cf332f32607d486b5709c4ce77287fac33665f00018f3e51a0c2f5f0ef41c4d3d6157b5974d2930bbf8742f15f13227f828e260118015cb384776067bd110fe906466a6b906b0dc17771e5b17cb9d29df950b7854b0c00010bce04210ca5e813ab598ae007557ad6451c92d5af1b3e6fd6456fb23d435f1f01d688f2d4dd84b447afe34b49c8102ed28d146838766c92d249695ade4f9e1c2001d79ae9580f8cbe4d7fa9039930f80f45710bb33ffc7fb90c171eb7e18091674d0001ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01c0ccedff83dd99dc9828ba3e9e3d4cb7aa215e936e28ad9746091349b115952d0163d75be8a40e41faf632e351b7ec51854eea3ac2eddde2a3aef98f91ada0f50f1f00017f73c1cb7c014374d83c17c7991fe6d454ca821d9504b27f8619ebb24a1eee2f0001f4ca7a69aac5e90396654afbc3b0ef3c63a03df635bd9e2388532a5ac24275340001b5b28578f0979722a177079fc818d4e89703fdbc7df127ebe7cce7736d3ee51f01e2b65da21849985ad32e25c180bf475786a943db09b6a4eba98b7ee7d5685f38000001bb088387ac8270e26a09f5774e254a32837e36a33b0bc52351be996855ba3e35012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2190000",
|
||||
"hash": "0000000001305957a9d4b367057609295272411918f138779cf1439e274706aa",
|
||||
"time": 1691926501,
|
||||
"saplingTree": "01305faeee7b1ceddaff174e5270e24e510d0df8daee5e2f23e9a620b5d758112801530563cca6add8edabc0310254231415ae17005c004b872cf69928da90b2ba071a0001a686bd2976daa6f85dbc4004d43796a4decdd2746e620e49c223400557117f5e00000000015cc61388fab56ec55e778d79451485646e19481245a435c2e86612489970c366000000000167058878ac649f304908a893009ab494d207221849101397f9183c4b434ab4110001829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "0106149b481dd306fd9747b6052467fcdbf0e5fc6017e57767ff3f22ce71236c12001f014401081b63c6d812cdaf47ab52de22a9a89279233133234ff5b86ac0825d332f0001fa4473594618ee491fad27b24bf856180d0895103c50f4745527db35e559a3100001a808aa9b8b88ba8968efea6d6935dbac646a481fca2f8d0ff20c067a43454a3d0001a4ec093f56cb96649c27c2c703bff22f2272a79465ecb4c403e0a65369b46911015ff4341320c653bf7f70b8940e179dbd8ff7dae489d5f52665cad5cd27358f2d01f965708d56263270a5e554ad8e6e4dbd8fa8856410171907d3a14d752be56e3101bb088387ac8270e26a09f5774e254a32837e36a33b0bc52351be996855ba3e35012ac0dc5cf6f74027bcfed481d5a698e063b806084b997dea0d229cc63326832b01202e0378617edb0787de75625783f77e30451cbabeee3290aeb4c8a9d329fc03010d70b8cd41ae0690cdcd3a61ae5f2a65d828aa9e153195166f609cadda1c4d2300000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2192500",
|
||||
"hash": "000000000177c30430cb21a22754a42190496b3b3b19e3b16e18aee2a8e4ba30",
|
||||
"time": 1692114678,
|
||||
"saplingTree": "01a37f64e2650a6c6617e3cc7821f93d3ffc810e7d880204784d6ed43e68800256001a0000015a608221ce9a2a593a3f86908f0558331f022bbf313d3c40a0a306ab7acfdf570136b904ffe5ec03c3fb7c41577479ea401f43926bd22efd8c9cfc953c45d6e05d0000000001b6e30d87577551876f155db59fc6d01e1cf7e053f367977a3655d61c0854d50f01dee841dad7d7f556d247748102d17326963b5af5ef9e82ef4f91544bee028148000001a561675207ad96fa955da60fa3ba010436c5df2807d4b8e44e05bbeb2ac3ce4a01829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01a2863e4d5340d7375bf96ff16c3c9e471678abf157e1fb1e8fe9bdb4f39b3719001f0001265f945cce4b1e90db04d7e43e0626656ebd30a1107b7793a47ee610f3fa5f230000010c026e4027b2ebde9c33eb532fe4e941603e995ec5f5ac3f879bba46e03e8305011748f4925963ef979f4a88c8b654caf13c51ede92080ffe41cd742965529551801087b845cc6ce8a2c850f7bcfec4902599b79203d548d091df445154fa6bccd260001a956002888967ac1e19fed83584ab640f47c7d52811412964bb5a5af0ce50d26000000000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2195000",
|
||||
"hash": "0000000000093cab6eab6ce5208492cee91186a6ce9f08e7d494ae921b1a8091",
|
||||
"time": 1692302661,
|
||||
"saplingTree": "01d1a0b94beb1d1049d9274f9d7546f5993e777eab6b75ab5584b615e6be1ffc540100051d31d8f444cf45b6677e9fe56d1ac79f2a245e63e04b58e5e09ebbf3c8231a0166f3cad95b7b81c55ae49709fb3e52d3714b72edf134903c675f708dd32e2b0200000157ea366b72fa44859e4fc6054d1087ae16f31ba11f775f2fc933439ee13d52510000015182161b8b449c60ff88a8865e022cfcfed36ab29fd63a785f4855057d8af731013e487066d124c2d77e861baa9acd6ac43340df222d369dcb4667e5b8bdad29520163ff0d183c5074455cd003245bb120e272f5f5341013f8b71a17ca29a22722000001b80c2ef8eca5372714de6c51ad56674cc4cd2eba28fd74e5ff34bf515e4a373401a4e3918c0121a67a3e4cf87970529dcccaab894613d23d400a4ed90c6a087b5801a561675207ad96fa955da60fa3ba010436c5df2807d4b8e44e05bbeb2ac3ce4a01829a0f79f1269a65c0aac80a4ac7dc8d18421ec2b6c7e60f6d13fa51f644a33c01ad3776bf273fb5ceaed1ec692ce0f8d05e9fca1d04bf804d29487b129a57f93b00000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "015d00fb4568aa38aa383947e7794c3eac8bbb3ef3c094f4cba5f40869d7322f12018019fa57281954625066b347ef0986f8d7c085186b4286c781252d530140d83f1f0000000161bc6b552e66c1541a4a6c066568fb22efe5af15462ba327fe171a673be24e3400015d4be5832e35b11d6fc52b62aad4b3d2deb807a47e395c819c2a4ba4dde10620013fe92332846f22c13930110bd5f8a78662a64e3a125abd42322619856fda4e1101c388b18fbc10553d0561fad21854c889ff0a51e9778a73797e4221e7b8dd7f330001e2439222c74ca0ccf3e8e959cf01b28f97b2c395f9d2f5a5ef4f21ef314614230000000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2197500",
|
||||
"hash": "000000000184479e9cb5ee4ff718197791cab798ce72a48a723d3333fad0548d",
|
||||
"time": 1692491813,
|
||||
"saplingTree": "0166a186574d6445cee7a7291c0c777050f1b42f6f5f893c437652f4b9adc8fc08015fed069e6d62e8c1e0154a291ce89b20565c78ef42e01b068c5bd276449193591a0001dfa7ad86f0394ff819571bc51df0e228e4b53459b5a9413b8ec45a6c08117a3d01f4887408b534ff5c910db09f825a2f2d8742fded2cf90dadf62724e4886bd13801663e461e45e72845b1460642853ef99298195ada4e03249277f0cc31a8b7f72d01d93fffda754f08c28f2474d7bfa7124306343ebdd7bdfda10e0ad783a39f772901bb3c0c73ad81cf3af0259767e074c47128b29606029407ec9539c6daf0a8070b0197364747700f77becf2cd59be1efb0e68747b7e650c69aff24a270376248ef49000000000169e55175fd24dcd2230eb8dbc83e4868ba002bb61d3e3e28bb77a75fd9a9816d00000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01effed431ad939b98e4e16c17e0fc80ad056f4e985bc4ff8c5bc70850f15ab732001f000001962af92932d5fc477176442a033db3a5d505459765d813f54b438f6b038ed6180001a4ddd74ae340ff1c7b4ab4a683471d4e7764f7622aea196eb1b61045989fba0f0001e901ff930dea1433e9de3d9293ddfdde1ca2b8ce1cd2d7d35e630b80687b660f00000001a6e633d09d16d5fa57ed0eccb16e1cf2cc673b648db397899b70289e56b8920100000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2200000",
|
||||
"hash": "0000000001a0139c4c4d0e8f68cc562227c6003f4b1b640a3d921aeb8c3d2e3d",
|
||||
"time": 1692680380,
|
||||
"saplingTree": "0124fd6a5c9f61e2c5e66eb47873077ece71dd41317c0b7cf7d3f1980c97a2b404001a01372a57e57f8f948fd22443cb49039e067f74c5bbf5e8fa359ca1c4e8937d8017000000000001c9d9f4aef7b919b8f3155a5c45bc5896c88921c54aefbaeb9db2d09b6bef72530001d4fb2a693773b53320fad63d6b683e181518d8ace0e45279ba76aed72a0a2641016e25b96f1952ebe60a92d85af13392d2ed30567d34b369e22c817639d2ffd31f00000192155a9c4bbe0c5d0ee36e26e9826b27927b471c17751bf88763fedbe6415561000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "012ef2b38328fe639b9ad22eaf4df2db0c1f4f9e1f1ae4d9f46be2487f4d462718001f000000011e761b7f64374248922ed29d050430fbbc83084ab1e4622819822442bdc23b2d014230b066e6902fb899bcc745ec111dfd1badb49086734842950294e9512599390000000001a3275f1adee92868bf05dd71490df8a7ddf174224cf9fe3e39acea2029ea643201a6e633d09d16d5fa57ed0eccb16e1cf2cc673b648db397899b70289e56b8920100000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2202500",
|
||||
"hash": "000000000037a9d20057d61a40e29f272f77668804df40cc30aaaa86a2756f6b",
|
||||
"time": 1692869287,
|
||||
"saplingTree": "010867e9be1e8087ebcf6b3956be7da442443a17ee32decc67a7c4d80ee1c04f2b001a0001b95e3fd30660cfad778571e036bddeb5f262066a85b725c8a9b3ed2b72a1ee1f0145330fd67ce31ffa08aa2826276cdfef861e06a69c84a716f10525dfd91cc4060001d1aadaa7869736dac8cfa6cc47fc8b5f8e3a10b8645d78b254411071217bae1101c5c9bcc4381ea5472d5ebd2b9aa310886ff49928879887cf7e01cd4f79cf7e2101868127dc5d8d4c972cfb1642b968b310c70f0bb0d0034d9314fb0fce0608163801622a5d4d2dc7004d17803e6fe93a63a00d4ce5fb7ffb686843f9f810503a1672013dcebf35b856d9de87fb1e77b03e9d272c08ce39ecd47430bd0f8c468b146a3e0001c110157c0bb0f38632e3bf111c7f3da91373d8d407cda71a795abfe4b437695e01cf0773c6eef9e2dfcc6567971cb1f3a2ece4f1e61061e09e78c20455cf2f07050192155a9c4bbe0c5d0ee36e26e9826b27927b471c17751bf88763fedbe6415561000001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01d2c9116c0d4e34d037dec90f6865ee8c546ee5237ce89f44e28c28b0ad16e416018fd673eae1ae9b46badb1d0a25c76a037487793d0e964e869f64a73ec81021351f0001f7fb3070e4866fce7fe13f30f69f55aeeb1947a0c4870a9febc7a1d6b66cc8270001d982d243bd0578f6ce748723b1d0544935fdc6ee09af2e9cd97044dd70bb3c3100000108fb7a4b7d48e456cc89a79d98b1d28cde03d56780947e5c5015baa051cdee2e011692e7fd01ba52a2f857999ea44b387be3e49d61079999df2fe66740f895301601b46c2b6035a150b945d03287a24a4e8f978527484798488439348e6094ad7b020000012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2205000",
|
||||
"hash": "0000000000ce31da261e370d941b842f293c57db03fcef8bf4770785a4fbac6e",
|
||||
"time": 1693057204,
|
||||
"saplingTree": "01ff26e71c0d39a1a6a0716a77324c3ec9c687721f8eac76c18918958b897d2c41001a01ddd790321c638005422572a2c7f7e0de9187b5af26d3557739645e1d7f8e6e1901ef54f6b7ccf090de188e2b55d379df10fbc681df47e44fe40374202a4e41933601ba9437003fdc8511f7d9ececdce9de832854808bf665e629e26f053f6ec9ca280000019641cac56e94365d5c6658456ae8ed333a6b1163c07eef893c1500d0f190306b0001810cc92e11afa88275ec901d1ab67bf9c6e105b216204bb4d3f3f4c4667d3e1000000001c0216916756804a8c3f9fcf751f7807608a04a8a6f754acf0a7f0f553fd9b12400012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01381c984235114e6d81ae00d44eeca4685c09540cf21e537d845df58e9fb7130f013073246134d44af4e3bd072d311a7e14e2ed2d9ca1781cabff34f5d40fe8dd221f016423f87be23b410c2e604db4c608c77b9063bcf4cfddc95f20133d11b398dd2200000001a4deff81f8fa06cc2b014ac875e2e5b3751d39002dc653920c6469369f48722301744441539851d5c68365948b6583529eefde092b83c377e584c913bea895640b00019f7c1e3978b795d2bc5dbfb78291944f75c9aad1637e1317e8185a281d080e1d000001f7c6dd5eaf5175eaafde1de044d62b37bc9d1b0299a43e1ae6e97f38e3f4ae2a012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2207500",
|
||||
"hash": "00000000000ec0f112a92bd16ef5b3a2368894f6c3d5032a77ebc3aea03d18ce",
|
||||
"time": 1693245695,
|
||||
"saplingTree": "0173a9b518212d5c1889fd1e38035673245c44f23a3990dfed2809d2c2c9ebf705001a016d5ce596abf1b0f2ab353a7e9e310b80da33b7e35b9c1ca930dcb4ede19b06270001cf14e701750ec012d70b0b109e6e54892959ac8c396cf8712e80f0857721851c01df43c721f681b634073095cacab7421c4957bb508f8e7d8ac218bd44b2f0e02601e4aa56067a58b5dad7e9f1b1e2db7baa2fb6658439e74ff0365abe3b1faa024a01dc955c1f7a3eb206b45d7a9acce0188f6493a6598f16882efc047ce7cc33d73400000100f7d78956d94aca8e287ea45faf7b52d3d1bb04c3396de14cee82b9a0d39a6801b035afd4c8314478c5397180bfa9a160388cc45c9b09c089bae1c2bcbd07d141000001a6efac374c5ef75e7c7c742150f6ffd7852c9d0641570c8f7dad318aa7162741012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "0158459484681fba0516801018e8ec5c219cdefcf24cadddd032097c62c955de00012764914ae23394cf0e867e9c230e2e4c93f0ab5ec091b2c745244fad9f7e36261f0170221cb1f864b92366bbb441e7f300a4f7544fd713921d6bf4c0349ab0d60717016a8f5c1110837ceddc613c648dba174a03290c57435663e003558062fa2f6829000177704fd90b1999887453dd1c31ce6270008768d5d19100faf5d4863f582aa81b01d473980b4c9980ffefe5d8098b5ceb43774c02195e1ae6435aac0692689c7d1801e4003bed9d4e89e0b0235dc60d2be1fe60f1f50e15e9a9d2c18bd2406b8abd14000001558a2081b4d2988432f40395c72886a0ce6abc23c1c6061869ad44b6583275080138aee02e5f0b1370556dc7ad927411fa1e62e15367f90073091ccc677079c11a01f7c6dd5eaf5175eaafde1de044d62b37bc9d1b0299a43e1ae6e97f38e3f4ae2a012727fb8329f636d37df0be8782480f408e2654513a8d9ea260e4df47ab6fa32a000162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2210000",
|
||||
"hash": "0000000001355c3493575fd882ce1efe7fea8d4e979e1f9c8665af8db296823a",
|
||||
"time": 1693435343,
|
||||
"saplingTree": "0161b72f49f0b0dd8a3e2bcb85262884a5969085f6e611f60ca73940e29238e733001a014c3601f3ebd57bc6d270c684390e3d980fc7570a0cca06f4bc3c97e24d93be5a00000000000000000188e1f654b2317621d8ec0d8aabab051752056bc85ce601ee7b2efc85a9076e2001b70de51176166ae4f6110e2726339e5659d043df716ec5426ee4fb3e3e47ef2801739355d442687c9b1cdbe89bdf1c83bb0e05df09123c6dc8ac46264332372a5b01a6efac374c5ef75e7c7c742150f6ffd7852c9d0641570c8f7dad318aa7162741012d24bb5e99f9ee9d19e144c12ff81507116946215cd7a6f28764662c8a5caa4a0001ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01275579cb64febfc92000492e10f910065c9571d6b94f6434d7b7e6c913779c3f001f01f5253a74bc6d283fbe31e6b2dafa5d4db91fe81abcec50de59c872065502cc0b0000018ac3e61ce99ecf9e3c1bced344b606dd7ae24f02b1448628ad91b163d3982e28000001fe353c0f219693c370cbe61398f479030c4cb70e0234e4f7b30e8d5072f4af3e00012a75037ed0051a079beadf00c146c505a7761f53f46ae9814e34bb4e954b0224000000011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2212500",
|
||||
"hash": "0000000001486c47f3460f7a46e5d06d559a69bc5ad58fcd5c98221dd6df9fd0",
|
||||
"time": 1693622394,
|
||||
"saplingTree": "01fb0b656dc4c627c59bba1642f073ac28c131a02371c5961b45bb7d4ea836b452019907a87d715d596eb4e0ccdbfcb3835071802a4a961bd70463d35c77504db5441a000000000153053505431b8f8ffa0a7252415f5d29ce51250bf40df4708c84b03c4cda6311011303d58561df2d2e7acf1148efdb33e85a56bf70005b81921ba81e35668f562d01e5e495006de9e65e1a2fcc24390e48b8ffbf0800c8621be19e855017c07c1d130141361a3762e41c0dce03032cd0420cdabaead8ebd8e1221e411d9701459e6e1900000001f7226a1253c057302203aaa5c7752967c577d9a952f50338f5a632a7a52c3414000001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "018c5e93594000a700fef7e55494d35a9d3a5d4442e54dcdc64ed1ebf3f917f60c011702b514a00f103e277396f64d56b94f3a60cce6d66bfa2e71e4561c4a7c111a1f01387e552daeceb2c272d18f411143fe74c7323ba3b4f0ecde2fdc5be74194dd25000001fa2d3584d563111d294727805890abb66eda4e6d851a6a4d060b7aaae2cd9e0e017ba1387f63ab6460e9cc82f7bb95afc39f71890616ea5bc24cd59ed96bcd80290001ac3449ca9cf912130aaf4cec2d1137a3981e4b88537778534dcaf332e228d10100000001c065cd9302b671e6b216fd4e35c893c35ac6cf3998f6d69185f14140ab96282c00011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2215000",
|
||||
"hash": "00000000011c3ba6392f069d59d0272e8467f0a655822aa5b33292fd78363445",
|
||||
"time": 1693811957,
|
||||
"saplingTree": "01051715c1e72270bc99cfafdee126677a43f6db2097955e2d1da8f50999364206018fe601319b6965a5b78ec5ce6922b62151c1fdef6b65361b8abf033e44518e061a0001b909dfc9caed7c414948809189e0b27b28dc141d1b7ff275fca06686d741af6001ac51dc8a652f68434aac270fa4bc76e5d969a961d2f23bcddb4eaa59110f9a40012ffaef49ced1ff4a30ab44a9f74d75d2cd121e138eadd65723d39557bedafb2900000155e7894bcf717fc9537b2b3c09ef59a8525a3bca846a173328092c2f58e94440000170685bee2dc27257972e2012d8c0cb8c826207e0e24100e8cf94779c512c806e0125538ced2d3de8b1836b9253a476d74e4478e28cfd83b9b0625973366bda215200000199e451c57fb0edef48700c409c3781e7dd114ff6c0030501f0766dd03549c9360001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "019afb92f9fd0a550932e32aff7caffc380f388aa92ff60c7c82061e89056fc5250141aa6fe2658f2e45797ebca3651868671945180dfb2fa7dbc915b8dfc3979a381f01a80f6025387f49e9e28facc759bde114621c9beaa7e7af584e211afc5691573401e8ae62ce5a426ac574643ed4e492b1c9e0538f93d915d4fae3e8631113f909130188a80f2232ff5447158985aa28309076ac7ccade5f2e1262472ebb2414fcd31f01dce62be3991b93a1bd81df04f61f07c926f54e9e69bf58a73035b49c48a65b030000014c0e495688bc0bcc7aa266ef9851468336d6807047505d3c3d4f90effc312a2b01da8332bdff0ec0ee5273ef11a3cc86d7b28e005abfd687592d23af984b31293b000105627a115c6a93ef03e846fb954e860f18e8934f04b8c4e363e7a63a7395100001c065cd9302b671e6b216fd4e35c893c35ac6cf3998f6d69185f14140ab96282c00011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2217500",
|
||||
"hash": "00000000002028029990e5591264643017107d193c0705624f3a2d190f1d97e9",
|
||||
"time": 1693999951,
|
||||
"saplingTree": "01ed0875728e56dd4c36e71c09c51efebfe35c591d1a3b20da54589b00c90b88330154f06a0b2e1ac618848c2a3059ac2227ab6f3de600b59d96c8466e8a698d95071a01a2b44ef7193abedc4970ce40bb93df74da399cd656faf79b5cd67a7a0b85424d010945f64972d27570d621844cb07338ce81de32c73944ed9be170176420afa7220001291ef2618c0f3d7721e7dfe04f541383df1b01d2f107c68a9c45fc1e1e711e27000000000001c9f1d8ad0530357ae6bbd2db9a0af2c050175c36df6c243e964e102f7630105d01b700b64abfa177f24c0500ca3b3e3c6570a06e677be096771b149d249151886f01eb565941c38472195168f8f2099bfa2852eb56467e04dc337895e2105c7460700199e451c57fb0edef48700c409c3781e7dd114ff6c0030501f0766dd03549c9360001cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "015b24b161887c608536a41fef3f10cb9eb513cf8942d99f13d200ca2f0726ed16015873ccea6d03227fbd55ea1c9a53b2832652e2aedee70c34df469ae31df945291f01cc99f99724bf5478b4de071599ef84e16b038fe807ba3781be3f4964a6d49a05017a8605d9978643a1bc1285eba1f6c253e1f6892d8dc4a5e545fc0a436748fe230001905f2723f2dcc0583f0362cafa51a522b4a32271f43634e6718496337f90c938000000000000000177fe87edaf3267ef297756a3b204b4920001f2db983b35a9a122e79b4a90bb2c011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2220000",
|
||||
"hash": "000000000172b493b91a2bf8da492818f469abaf367f0a34c6088d7384bf438d",
|
||||
"time": 1694189265,
|
||||
"saplingTree": "01e1fdb4fbf39470312e2ee698bbadaae6a6f600630704ce828f624092d419eb2b01bb2e754503afcbeba2f5f351cde44316015799935827d50086a09347b9aa72281a019cc2cee08afd6df6cbc2d89a3a7f5381c466fc8718a52e6db6e6fa44063db24000000000017e8553f04eb329f93a9882e2656a7d9772a7d12098932e9fc10b094844d4675701de3681f6095e049db159b1b3f7058a80bf8178e7dcc36de3e3eb11a2abc3584001fc25e0cec13e3f59891b069f75fd51f2caa60f501e23b3afdf6209faf71edf1b00000001d177df23a29c4acfa477b94c55bf3baa5b4f43dc65bf94ed65cf3ac7b2d753300001055bb26ddf3a7a7efa91af3ba8c040cf4f373d366b3bb01a778cc54fc31ab32a01cf666e6ab15724fcad98c1eaf7d0b555e9cfa4132c4152a5946256421eda496101ba391f92d9f6f13ffa0a9be8f09e113dd528f28d64e3fa82a9841c3871f5711e000001d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01e9654f2f2065ea7222b509f367053c8c60335abd1fef4d4a8cc8f1b6e4c2a133011093acb8712c698913d117410839280fe234942db8fec4830c6544b3d97da0361f0000015cfab442f440115599554959ca560451d1da80cfa1fac8031be6c091c9eb79110001bf4c6e2c88cb1b29df8cbe2ab268c40e3d2a2d26155a3ebfc3a9d450954c6d050001b8c991defe192f04816e611cbeff67ba630aba4e68cb9a01d8a15861d6d5460001463c8512a48687e5827657cefbc11b0495302ca787760ef02346bdb91922c10801a23a492db19a8b6962c4f58d478dcc81a3b22f59914a8dbaced764f53e923e3700000177fe87edaf3267ef297756a3b204b4920001f2db983b35a9a122e79b4a90bb2c011c624f6c2160a85fc2821643724e4ecb61ac64738e0fb36a51933a83e8618c260162cb5b712202981670217893d510e9464364f9e7e3084b43f0450d1f91a85e00000001cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2460000",
|
||||
"hash": "0009d9631de2a23970526299576cec163ad78052d42fa0663ed702fc54de97cc",
|
||||
"time": 1691548876,
|
||||
"saplingTree": "012a6b48d681dd51a8a1459128234ac62021e648d051327a20046b9199a19b426e001000017f5c7aafef68bdc253a8bbbe42aa31eae64f1bd41d6b12ce6d3cf9e9f9dea94b0144a41737b6eeb7602f3c0d934867da3c93e0fda389bd9038bc94b121ca20472101b305821622d49b13ca61f1daa7e1bfe0416e10540a2e66966e82217f29682b1d0107667bbf959254b11065a34a18aaeebf5d2d454a92e74642da01b517373f970e01a1189f53f8e689114d04c4762671355fc10730d4b6d2ce33d21d82329d368b4c01a0c35d0c00236839d6bff3ab9c2d80e46b7e58505e65a28dbb212bc1261f1d600001bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01ca16bf908aa97229537c7fbf28645e9badb56ccfc77def82896cf60298e69c26012d592df066e039007ae2f44955f1178839742d07bea95ccab04f97c07f5899231f0000000000015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2470000",
|
||||
"hash": "00121d226a54c97df305952211884f0ec30a946c27dc19413f1bb1aa018a0453",
|
||||
"time": 1692203956,
|
||||
"saplingTree": "01512e2ed4e4700f5f5efbdca083edac1c84c85ce7af5097a9dc4ca3268cdaec32001001148e57e2488fe489b96650e2a101fc03c125917a2f5e53730a6161e5d9ba3a3f0001f011635dacf249b3f02cc7cb93cf9a9cd7647822c872d3770425315f2249ac360113aec83ad64a9fa33e5d3fbd86707e08ec9b78e75eb3de0bd28b40fa33877e300000000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2480000",
|
||||
"hash": "001bfafbaceebd7a1c345f50e5e6cb7b1b407367c6c34dca1e4a8c2e2cc8a454",
|
||||
"time": 1692819217,
|
||||
"saplingTree": "01d4ea948eca1394127f74f6ab5ab04eeb27ac14f861f9a8557856bc2a117fb42d0010011071503fda1c0a2e4f144674354e79ff3ba41a7ffb55f39eab0b36fdea0fd66401e96af949aa669ee122d4323bb8c2a1796dc19329d74e55a91e859db941773e2a00000001aef1b7b2d9b9f3e2812e17fa7d795ad84b60d9fd06595b9894ce98756f4d7a6b000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2490000",
|
||||
"hash": "002ad48693ddf02fb78f4bfc90fd9f4d8e2e42774d4bfbc397b377d6c63d2a91",
|
||||
"time": 1693445978,
|
||||
"saplingTree": "01e2448ca682786985da2e41fbe10f2f7d9cbf84d23a4459d65e9eee26d126652200100167cfb6ece69f4aca04044e6a5951ebd80565f90b60abb94b7bfc42b3d5e12c2200000001fd827e648ed7b0395b82aa4320658f173aab4081f0d247849aa51c16aed7271501aef1b7b2d9b9f3e2812e17fa7d795ad84b60d9fd06595b9894ce98756f4d7a6b000154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "014f625ee6067b31aaca9bcf48c2c661224c496269b3f349a35162cb02a5f5be3d01a49aafd2a80b3b994051027a99c656c261264a3a070371457b224319154cf83a1f0000013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2500000",
|
||||
"hash": "0027edd1f6961b523961a1cd56838f9bbe802089c2611926c0e7921ff0cf1a7e",
|
||||
"time": 1694114813,
|
||||
"saplingTree": "011fc7d5b799f4cadb4858413ebbdbed68d70c01ef9b7e843c56beb69e8624a30a017dca56a3b2a449cf5e04774d5a1b68f5fcc293090d65b2f517e6135195c181281001843d2b87c6a97e9b51379342fa9d83ec3606529ee751b1ce38538a5eb42e2e090000000000017b1f428de6cb942a20c1529e0aa66245e3991b7f21f50d34516c1fa5c3d3133d0154d60824b16456e8ed2e3048e753db8668bde77c1468b5a7b9a70271c0f9841e01bbdae644b3bb219bd5ad8a87dd81995655e01288b969e03ca5ca89ddc37f3d4f0000015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "011697b177a3a10f36dfaa186c740924a7380e0b370232b8ae827b1034f0bc8a1e019b5d03643d9f7836adabe446f6bac21a153cbfbf2a1ad336601edd42a31add111f01b0c73b0fd3a20b4a20aa61bdc190afea942e6fee5c9c3dbe6cc8d51c7bdf2d1301e5ff33a9e5a1f277f5c34ffe5df13dd6a84006baed346f7faee6cd40b2347c05013839317da76a88fd08c16a5b1d101389cac4f582ec836730699fc6702ecd9f37018bbc1597e6f9f34a015d97886d804bf1bebd641dd85d22abd08864eb2c19f51e00015fed2e5a871eec20a4fd60ded5d0de7563474e1476f893903e74de1066763d1e01231910a25079ed80322b6bad36a26e5eecbdf5fc94bd7ae2f1780076331b052100000178a68d439d45f555fd4fe08eeeab4545091b53d73da118b6084042c4a4c44d3a00013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue